1use anyhow::{Context as _, Result};
2use async_trait::async_trait;
3use futures::StreamExt;
4use gpui::AsyncApp;
5use language::{LspAdapter, LspAdapterDelegate, Toolchain, language_settings::AllLanguageSettings};
6use lsp::{LanguageServerBinary, LanguageServerName};
7use node_runtime::{NodeRuntime, VersionStrategy};
8use project::{Fs, lsp_store::language_server_settings};
9use serde_json::Value;
10use settings::{Settings, SettingsLocation};
11use smol::fs;
12use std::{
13 any::Any,
14 ffi::OsString,
15 path::{Path, PathBuf},
16 sync::Arc,
17};
18use util::{ResultExt, maybe, merge_json_value_into};
19
20const SERVER_PATH: &str = "node_modules/yaml-language-server/bin/yaml-language-server";
21
22fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
23 vec![server_path.into(), "--stdio".into()]
24}
25
26pub struct YamlLspAdapter {
27 node: NodeRuntime,
28}
29
30impl YamlLspAdapter {
31 const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("yaml-language-server");
32 const PACKAGE_NAME: &str = "yaml-language-server";
33 pub fn new(node: NodeRuntime) -> Self {
34 YamlLspAdapter { node }
35 }
36}
37
38#[async_trait(?Send)]
39impl LspAdapter for YamlLspAdapter {
40 fn name(&self) -> LanguageServerName {
41 Self::SERVER_NAME
42 }
43
44 async fn fetch_latest_server_version(
45 &self,
46 _: &dyn LspAdapterDelegate,
47 _: &AsyncApp,
48 ) -> Result<Box<dyn 'static + Any + Send>> {
49 Ok(Box::new(
50 self.node
51 .npm_package_latest_version("yaml-language-server")
52 .await?,
53 ) as Box<_>)
54 }
55
56 async fn check_if_user_installed(
57 &self,
58 delegate: &dyn LspAdapterDelegate,
59 _: Option<Toolchain>,
60 _: &AsyncApp,
61 ) -> Option<LanguageServerBinary> {
62 let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
63 let env = delegate.shell_env().await;
64
65 Some(LanguageServerBinary {
66 path,
67 env: Some(env),
68 arguments: vec!["--stdio".into()],
69 })
70 }
71
72 async fn fetch_server_binary(
73 &self,
74 latest_version: Box<dyn 'static + Send + Any>,
75 container_dir: PathBuf,
76 _: &dyn LspAdapterDelegate,
77 ) -> Result<LanguageServerBinary> {
78 let latest_version = latest_version.downcast::<String>().unwrap();
79 let server_path = container_dir.join(SERVER_PATH);
80
81 self.node
82 .npm_install_packages(
83 &container_dir,
84 &[(Self::PACKAGE_NAME, latest_version.as_str())],
85 )
86 .await?;
87
88 Ok(LanguageServerBinary {
89 path: self.node.binary_path().await?,
90 env: None,
91 arguments: server_binary_arguments(&server_path),
92 })
93 }
94
95 async fn check_if_version_installed(
96 &self,
97 version: &(dyn 'static + Send + Any),
98 container_dir: &PathBuf,
99 _: &dyn LspAdapterDelegate,
100 ) -> Option<LanguageServerBinary> {
101 let version = version.downcast_ref::<String>().unwrap();
102 let server_path = container_dir.join(SERVER_PATH);
103
104 let should_install_language_server = self
105 .node
106 .should_install_npm_package(
107 Self::PACKAGE_NAME,
108 &server_path,
109 container_dir,
110 VersionStrategy::Latest(version),
111 )
112 .await;
113
114 if should_install_language_server {
115 None
116 } else {
117 Some(LanguageServerBinary {
118 path: self.node.binary_path().await.ok()?,
119 env: None,
120 arguments: server_binary_arguments(&server_path),
121 })
122 }
123 }
124
125 async fn cached_server_binary(
126 &self,
127 container_dir: PathBuf,
128 _: &dyn LspAdapterDelegate,
129 ) -> Option<LanguageServerBinary> {
130 get_cached_server_binary(container_dir, &self.node).await
131 }
132
133 async fn workspace_configuration(
134 self: Arc<Self>,
135 _: &dyn Fs,
136 delegate: &Arc<dyn LspAdapterDelegate>,
137 _: Option<Toolchain>,
138 cx: &mut AsyncApp,
139 ) -> Result<Value> {
140 let location = SettingsLocation {
141 worktree_id: delegate.worktree_id(),
142 path: delegate.worktree_root_path(),
143 };
144
145 let tab_size = cx.update(|cx| {
146 AllLanguageSettings::get(Some(location), cx)
147 .language(Some(location), Some(&"YAML".into()), cx)
148 .tab_size
149 })?;
150
151 let mut options = serde_json::json!({
152 "[yaml]": {"editor.tabSize": tab_size},
153 "yaml": {"format": {"enable": true}}
154 });
155
156 let project_options = cx.update(|cx| {
157 language_server_settings(delegate.as_ref(), &Self::SERVER_NAME, cx)
158 .and_then(|s| s.settings.clone())
159 })?;
160 if let Some(override_options) = project_options {
161 merge_json_value_into(override_options, &mut options);
162 }
163 Ok(options)
164 }
165}
166
167async fn get_cached_server_binary(
168 container_dir: PathBuf,
169 node: &NodeRuntime,
170) -> Option<LanguageServerBinary> {
171 maybe!(async {
172 let mut last_version_dir = None;
173 let mut entries = fs::read_dir(&container_dir).await?;
174 while let Some(entry) = entries.next().await {
175 let entry = entry?;
176 if entry.file_type().await?.is_dir() {
177 last_version_dir = Some(entry.path());
178 }
179 }
180 let last_version_dir = last_version_dir.context("no cached binary")?;
181 let server_path = last_version_dir.join(SERVER_PATH);
182 anyhow::ensure!(
183 server_path.exists(),
184 "missing executable in directory {last_version_dir:?}"
185 );
186 Ok(LanguageServerBinary {
187 path: node.binary_path().await?,
188 env: None,
189 arguments: server_binary_arguments(&server_path),
190 })
191 })
192 .await
193 .log_err()
194}