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