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 ) -> Result<Box<dyn 'static + Any + Send>> {
48 Ok(Box::new(
49 self.node
50 .npm_package_latest_version("yaml-language-server")
51 .await?,
52 ) as Box<_>)
53 }
54
55 async fn check_if_user_installed(
56 &self,
57 delegate: &dyn LspAdapterDelegate,
58 _: Option<Toolchain>,
59 _: &AsyncApp,
60 ) -> Option<LanguageServerBinary> {
61 let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
62 let env = delegate.shell_env().await;
63
64 Some(LanguageServerBinary {
65 path,
66 env: Some(env),
67 arguments: vec!["--stdio".into()],
68 })
69 }
70
71 async fn fetch_server_binary(
72 &self,
73 latest_version: Box<dyn 'static + Send + Any>,
74 container_dir: PathBuf,
75 _: &dyn LspAdapterDelegate,
76 ) -> Result<LanguageServerBinary> {
77 let latest_version = latest_version.downcast::<String>().unwrap();
78 let server_path = container_dir.join(SERVER_PATH);
79
80 self.node
81 .npm_install_packages(
82 &container_dir,
83 &[(Self::PACKAGE_NAME, latest_version.as_str())],
84 )
85 .await?;
86
87 Ok(LanguageServerBinary {
88 path: self.node.binary_path().await?,
89 env: None,
90 arguments: server_binary_arguments(&server_path),
91 })
92 }
93
94 async fn check_if_version_installed(
95 &self,
96 version: &(dyn 'static + Send + Any),
97 container_dir: &PathBuf,
98 _: &dyn LspAdapterDelegate,
99 ) -> Option<LanguageServerBinary> {
100 let version = version.downcast_ref::<String>().unwrap();
101 let server_path = container_dir.join(SERVER_PATH);
102
103 let should_install_language_server = self
104 .node
105 .should_install_npm_package(
106 Self::PACKAGE_NAME,
107 &server_path,
108 container_dir,
109 VersionStrategy::Latest(version),
110 )
111 .await;
112
113 if should_install_language_server {
114 None
115 } else {
116 Some(LanguageServerBinary {
117 path: self.node.binary_path().await.ok()?,
118 env: None,
119 arguments: server_binary_arguments(&server_path),
120 })
121 }
122 }
123
124 async fn cached_server_binary(
125 &self,
126 container_dir: PathBuf,
127 _: &dyn LspAdapterDelegate,
128 ) -> Option<LanguageServerBinary> {
129 get_cached_server_binary(container_dir, &self.node).await
130 }
131
132 async fn workspace_configuration(
133 self: Arc<Self>,
134 _: &dyn Fs,
135 delegate: &Arc<dyn LspAdapterDelegate>,
136 _: Option<Toolchain>,
137 cx: &mut AsyncApp,
138 ) -> Result<Value> {
139 let location = SettingsLocation {
140 worktree_id: delegate.worktree_id(),
141 path: delegate.worktree_root_path(),
142 };
143
144 let tab_size = cx.update(|cx| {
145 AllLanguageSettings::get(Some(location), cx)
146 .language(Some(location), Some(&"YAML".into()), cx)
147 .tab_size
148 })?;
149
150 let mut options = serde_json::json!({
151 "[yaml]": {"editor.tabSize": tab_size},
152 "yaml": {"format": {"enable": true}}
153 });
154
155 let project_options = cx.update(|cx| {
156 language_server_settings(delegate.as_ref(), &Self::SERVER_NAME, cx)
157 .and_then(|s| s.settings.clone())
158 })?;
159 if let Some(override_options) = project_options {
160 merge_json_value_into(override_options, &mut options);
161 }
162 Ok(options)
163 }
164}
165
166async fn get_cached_server_binary(
167 container_dir: PathBuf,
168 node: &NodeRuntime,
169) -> Option<LanguageServerBinary> {
170 maybe!(async {
171 let mut last_version_dir = None;
172 let mut entries = fs::read_dir(&container_dir).await?;
173 while let Some(entry) = entries.next().await {
174 let entry = entry?;
175 if entry.file_type().await?.is_dir() {
176 last_version_dir = Some(entry.path());
177 }
178 }
179 let last_version_dir = last_version_dir.context("no cached binary")?;
180 let server_path = last_version_dir.join(SERVER_PATH);
181 anyhow::ensure!(
182 server_path.exists(),
183 "missing executable in directory {last_version_dir:?}"
184 );
185 Ok(LanguageServerBinary {
186 path: node.binary_path().await?,
187 env: None,
188 arguments: server_binary_arguments(&server_path),
189 })
190 })
191 .await
192 .log_err()
193}