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