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