1use anyhow::{anyhow, Result};
2use async_trait::async_trait;
3use futures::StreamExt;
4use gpui2::{AsyncAppContext, Task};
5pub use language2::*;
6use lazy_static::lazy_static;
7use lsp2::LanguageServerBinary;
8use regex::Regex;
9use smol::{fs, process};
10use std::{
11 any::Any,
12 ffi::{OsStr, OsString},
13 ops::Range,
14 path::PathBuf,
15 str,
16 sync::{
17 atomic::{AtomicBool, Ordering::SeqCst},
18 Arc,
19 },
20};
21use util::{fs::remove_matching, github::latest_github_release, ResultExt};
22
23fn server_binary_arguments() -> Vec<OsString> {
24 vec!["-mode=stdio".into()]
25}
26
27#[derive(Copy, Clone)]
28pub struct GoLspAdapter;
29
30lazy_static! {
31 static ref GOPLS_VERSION_REGEX: Regex = Regex::new(r"\d+\.\d+\.\d+").unwrap();
32}
33
34#[async_trait]
35impl super::LspAdapter for GoLspAdapter {
36 async fn name(&self) -> LanguageServerName {
37 LanguageServerName("gopls".into())
38 }
39
40 fn short_name(&self) -> &'static str {
41 "gopls"
42 }
43
44 async fn fetch_latest_server_version(
45 &self,
46 delegate: &dyn LspAdapterDelegate,
47 ) -> Result<Box<dyn 'static + Send + Any>> {
48 let release = latest_github_release("golang/tools", false, delegate.http_client()).await?;
49 let version: Option<String> = release.name.strip_prefix("gopls/v").map(str::to_string);
50 if version.is_none() {
51 log::warn!(
52 "couldn't infer gopls version from github release name '{}'",
53 release.name
54 );
55 }
56 Ok(Box::new(version) as Box<_>)
57 }
58
59 fn will_fetch_server(
60 &self,
61 delegate: &Arc<dyn LspAdapterDelegate>,
62 cx: &mut AsyncAppContext,
63 ) -> Option<Task<Result<()>>> {
64 static DID_SHOW_NOTIFICATION: AtomicBool = AtomicBool::new(false);
65
66 const NOTIFICATION_MESSAGE: &str =
67 "Could not install the Go language server `gopls`, because `go` was not found.";
68
69 let delegate = delegate.clone();
70 Some(cx.spawn(|cx| async move {
71 let install_output = process::Command::new("go").args(["version"]).output().await;
72 if install_output.is_err() {
73 if DID_SHOW_NOTIFICATION
74 .compare_exchange(false, true, SeqCst, SeqCst)
75 .is_ok()
76 {
77 cx.update(|cx| {
78 delegate.show_notification(NOTIFICATION_MESSAGE, cx);
79 })?
80 }
81 return Err(anyhow!("cannot install gopls"));
82 }
83 Ok(())
84 }))
85 }
86
87 async fn fetch_server_binary(
88 &self,
89 version: Box<dyn 'static + Send + Any>,
90 container_dir: PathBuf,
91 delegate: &dyn LspAdapterDelegate,
92 ) -> Result<LanguageServerBinary> {
93 let version = version.downcast::<Option<String>>().unwrap();
94 let this = *self;
95
96 if let Some(version) = *version {
97 let binary_path = container_dir.join(&format!("gopls_{version}"));
98 if let Ok(metadata) = fs::metadata(&binary_path).await {
99 if metadata.is_file() {
100 remove_matching(&container_dir, |entry| {
101 entry != binary_path && entry.file_name() != Some(OsStr::new("gobin"))
102 })
103 .await;
104
105 return Ok(LanguageServerBinary {
106 path: binary_path.to_path_buf(),
107 arguments: server_binary_arguments(),
108 });
109 }
110 }
111 } else if let Some(path) = this
112 .cached_server_binary(container_dir.clone(), delegate)
113 .await
114 {
115 return Ok(path);
116 }
117
118 let gobin_dir = container_dir.join("gobin");
119 fs::create_dir_all(&gobin_dir).await?;
120 let install_output = process::Command::new("go")
121 .env("GO111MODULE", "on")
122 .env("GOBIN", &gobin_dir)
123 .args(["install", "golang.org/x/tools/gopls@latest"])
124 .output()
125 .await?;
126 if !install_output.status.success() {
127 Err(anyhow!("failed to install gopls. Is go installed?"))?;
128 }
129
130 let installed_binary_path = gobin_dir.join("gopls");
131 let version_output = process::Command::new(&installed_binary_path)
132 .arg("version")
133 .output()
134 .await
135 .map_err(|e| anyhow!("failed to run installed gopls binary {:?}", e))?;
136 let version_stdout = str::from_utf8(&version_output.stdout)
137 .map_err(|_| anyhow!("gopls version produced invalid utf8"))?;
138 let version = GOPLS_VERSION_REGEX
139 .find(version_stdout)
140 .ok_or_else(|| anyhow!("failed to parse gopls version output"))?
141 .as_str();
142 let binary_path = container_dir.join(&format!("gopls_{version}"));
143 fs::rename(&installed_binary_path, &binary_path).await?;
144
145 Ok(LanguageServerBinary {
146 path: binary_path.to_path_buf(),
147 arguments: server_binary_arguments(),
148 })
149 }
150
151 async fn cached_server_binary(
152 &self,
153 container_dir: PathBuf,
154 _: &dyn LspAdapterDelegate,
155 ) -> Option<LanguageServerBinary> {
156 get_cached_server_binary(container_dir).await
157 }
158
159 async fn installation_test_binary(
160 &self,
161 container_dir: PathBuf,
162 ) -> Option<LanguageServerBinary> {
163 get_cached_server_binary(container_dir)
164 .await
165 .map(|mut binary| {
166 binary.arguments = vec!["--help".into()];
167 binary
168 })
169 }
170
171 async fn label_for_completion(
172 &self,
173 completion: &lsp2::CompletionItem,
174 language: &Arc<Language>,
175 ) -> Option<CodeLabel> {
176 let label = &completion.label;
177
178 // Gopls returns nested fields and methods as completions.
179 // To syntax highlight these, combine their final component
180 // with their detail.
181 let name_offset = label.rfind('.').unwrap_or(0);
182
183 match completion.kind.zip(completion.detail.as_ref()) {
184 Some((lsp2::CompletionItemKind::MODULE, detail)) => {
185 let text = format!("{label} {detail}");
186 let source = Rope::from(format!("import {text}").as_str());
187 let runs = language.highlight_text(&source, 7..7 + text.len());
188 return Some(CodeLabel {
189 text,
190 runs,
191 filter_range: 0..label.len(),
192 });
193 }
194 Some((
195 lsp2::CompletionItemKind::CONSTANT | lsp2::CompletionItemKind::VARIABLE,
196 detail,
197 )) => {
198 let text = format!("{label} {detail}");
199 let source =
200 Rope::from(format!("var {} {}", &text[name_offset..], detail).as_str());
201 let runs = adjust_runs(
202 name_offset,
203 language.highlight_text(&source, 4..4 + text.len()),
204 );
205 return Some(CodeLabel {
206 text,
207 runs,
208 filter_range: 0..label.len(),
209 });
210 }
211 Some((lsp2::CompletionItemKind::STRUCT, _)) => {
212 let text = format!("{label} struct {{}}");
213 let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());
214 let runs = adjust_runs(
215 name_offset,
216 language.highlight_text(&source, 5..5 + text.len()),
217 );
218 return Some(CodeLabel {
219 text,
220 runs,
221 filter_range: 0..label.len(),
222 });
223 }
224 Some((lsp2::CompletionItemKind::INTERFACE, _)) => {
225 let text = format!("{label} interface {{}}");
226 let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());
227 let runs = adjust_runs(
228 name_offset,
229 language.highlight_text(&source, 5..5 + text.len()),
230 );
231 return Some(CodeLabel {
232 text,
233 runs,
234 filter_range: 0..label.len(),
235 });
236 }
237 Some((lsp2::CompletionItemKind::FIELD, detail)) => {
238 let text = format!("{label} {detail}");
239 let source =
240 Rope::from(format!("type T struct {{ {} }}", &text[name_offset..]).as_str());
241 let runs = adjust_runs(
242 name_offset,
243 language.highlight_text(&source, 16..16 + text.len()),
244 );
245 return Some(CodeLabel {
246 text,
247 runs,
248 filter_range: 0..label.len(),
249 });
250 }
251 Some((
252 lsp2::CompletionItemKind::FUNCTION | lsp2::CompletionItemKind::METHOD,
253 detail,
254 )) => {
255 if let Some(signature) = detail.strip_prefix("func") {
256 let text = format!("{label}{signature}");
257 let source = Rope::from(format!("func {} {{}}", &text[name_offset..]).as_str());
258 let runs = adjust_runs(
259 name_offset,
260 language.highlight_text(&source, 5..5 + text.len()),
261 );
262 return Some(CodeLabel {
263 filter_range: 0..label.len(),
264 text,
265 runs,
266 });
267 }
268 }
269 _ => {}
270 }
271 None
272 }
273
274 async fn label_for_symbol(
275 &self,
276 name: &str,
277 kind: lsp2::SymbolKind,
278 language: &Arc<Language>,
279 ) -> Option<CodeLabel> {
280 let (text, filter_range, display_range) = match kind {
281 lsp2::SymbolKind::METHOD | lsp2::SymbolKind::FUNCTION => {
282 let text = format!("func {} () {{}}", name);
283 let filter_range = 5..5 + name.len();
284 let display_range = 0..filter_range.end;
285 (text, filter_range, display_range)
286 }
287 lsp2::SymbolKind::STRUCT => {
288 let text = format!("type {} struct {{}}", name);
289 let filter_range = 5..5 + name.len();
290 let display_range = 0..text.len();
291 (text, filter_range, display_range)
292 }
293 lsp2::SymbolKind::INTERFACE => {
294 let text = format!("type {} interface {{}}", name);
295 let filter_range = 5..5 + name.len();
296 let display_range = 0..text.len();
297 (text, filter_range, display_range)
298 }
299 lsp2::SymbolKind::CLASS => {
300 let text = format!("type {} T", name);
301 let filter_range = 5..5 + name.len();
302 let display_range = 0..filter_range.end;
303 (text, filter_range, display_range)
304 }
305 lsp2::SymbolKind::CONSTANT => {
306 let text = format!("const {} = nil", name);
307 let filter_range = 6..6 + name.len();
308 let display_range = 0..filter_range.end;
309 (text, filter_range, display_range)
310 }
311 lsp2::SymbolKind::VARIABLE => {
312 let text = format!("var {} = nil", name);
313 let filter_range = 4..4 + name.len();
314 let display_range = 0..filter_range.end;
315 (text, filter_range, display_range)
316 }
317 lsp2::SymbolKind::MODULE => {
318 let text = format!("package {}", name);
319 let filter_range = 8..8 + name.len();
320 let display_range = 0..filter_range.end;
321 (text, filter_range, display_range)
322 }
323 _ => return None,
324 };
325
326 Some(CodeLabel {
327 runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
328 text: text[display_range].to_string(),
329 filter_range,
330 })
331 }
332}
333
334async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
335 (|| async move {
336 let mut last_binary_path = None;
337 let mut entries = fs::read_dir(&container_dir).await?;
338 while let Some(entry) = entries.next().await {
339 let entry = entry?;
340 if entry.file_type().await?.is_file()
341 && entry
342 .file_name()
343 .to_str()
344 .map_or(false, |name| name.starts_with("gopls_"))
345 {
346 last_binary_path = Some(entry.path());
347 }
348 }
349
350 if let Some(path) = last_binary_path {
351 Ok(LanguageServerBinary {
352 path,
353 arguments: server_binary_arguments(),
354 })
355 } else {
356 Err(anyhow!("no cached binary"))
357 }
358 })()
359 .await
360 .log_err()
361}
362
363fn adjust_runs(
364 delta: usize,
365 mut runs: Vec<(Range<usize>, HighlightId)>,
366) -> Vec<(Range<usize>, HighlightId)> {
367 for (range, _) in &mut runs {
368 range.start += delta;
369 range.end += delta;
370 }
371 runs
372}
373
374#[cfg(test)]
375mod tests {
376 use super::*;
377 use crate::languages::language;
378 use gpui2::Hsla;
379 use theme2::SyntaxTheme;
380
381 #[gpui2::test]
382 async fn test_go_label_for_completion() {
383 let language = language(
384 "go",
385 tree_sitter_go::language(),
386 Some(Arc::new(GoLspAdapter)),
387 )
388 .await;
389
390 let theme = SyntaxTheme::new_test([
391 ("type", Hsla::default()),
392 ("keyword", Hsla::default()),
393 ("function", Hsla::default()),
394 ("number", Hsla::default()),
395 ("property", Hsla::default()),
396 ]);
397 language.set_theme(&theme);
398
399 let grammar = language.grammar().unwrap();
400 let highlight_function = grammar.highlight_id_for_name("function").unwrap();
401 let highlight_type = grammar.highlight_id_for_name("type").unwrap();
402 let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
403 let highlight_number = grammar.highlight_id_for_name("number").unwrap();
404 let highlight_field = grammar.highlight_id_for_name("property").unwrap();
405
406 assert_eq!(
407 language
408 .label_for_completion(&lsp2::CompletionItem {
409 kind: Some(lsp2::CompletionItemKind::FUNCTION),
410 label: "Hello".to_string(),
411 detail: Some("func(a B) c.D".to_string()),
412 ..Default::default()
413 })
414 .await,
415 Some(CodeLabel {
416 text: "Hello(a B) c.D".to_string(),
417 filter_range: 0..5,
418 runs: vec![
419 (0..5, highlight_function),
420 (8..9, highlight_type),
421 (13..14, highlight_type),
422 ],
423 })
424 );
425
426 // Nested methods
427 assert_eq!(
428 language
429 .label_for_completion(&lsp2::CompletionItem {
430 kind: Some(lsp2::CompletionItemKind::METHOD),
431 label: "one.two.Three".to_string(),
432 detail: Some("func() [3]interface{}".to_string()),
433 ..Default::default()
434 })
435 .await,
436 Some(CodeLabel {
437 text: "one.two.Three() [3]interface{}".to_string(),
438 filter_range: 0..13,
439 runs: vec![
440 (8..13, highlight_function),
441 (17..18, highlight_number),
442 (19..28, highlight_keyword),
443 ],
444 })
445 );
446
447 // Nested fields
448 assert_eq!(
449 language
450 .label_for_completion(&lsp2::CompletionItem {
451 kind: Some(lsp2::CompletionItemKind::FIELD),
452 label: "two.Three".to_string(),
453 detail: Some("a.Bcd".to_string()),
454 ..Default::default()
455 })
456 .await,
457 Some(CodeLabel {
458 text: "two.Three a.Bcd".to_string(),
459 filter_range: 0..9,
460 runs: vec![(4..9, highlight_field), (12..15, highlight_type)],
461 })
462 );
463 }
464}