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::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 variables: &TaskVariables,
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 = variables
469 .get(&VariableName::WorktreeRoot)
470 .and_then(|worktree_abs_path| buffer_dir.strip_prefix(worktree_abs_path).ok())
471 .map(|relative_pkg_dir| {
472 if relative_pkg_dir.as_os_str().is_empty() {
473 ".".into()
474 } else {
475 format!("./{}", relative_pkg_dir.to_string_lossy())
476 }
477 })
478 .unwrap_or_else(|| format!("{}", buffer_dir.to_string_lossy()));
479
480 TaskVariables::from_iter(Some((
481 GO_PACKAGE_TASK_VARIABLE.clone(),
482 package_name.to_string(),
483 )))
484 } else {
485 TaskVariables::default()
486 },
487 )
488 }
489
490 fn associated_tasks(&self) -> Option<TaskTemplates> {
491 Some(TaskTemplates(vec![
492 TaskTemplate {
493 label: format!(
494 "go test {} -run {}",
495 GO_PACKAGE_TASK_VARIABLE.template_value(),
496 VariableName::Symbol.template_value(),
497 ),
498 command: "go".into(),
499 args: vec![
500 "test".into(),
501 GO_PACKAGE_TASK_VARIABLE.template_value(),
502 "-run".into(),
503 VariableName::Symbol.template_value(),
504 ],
505 tags: vec!["go-test".to_owned()],
506 ..TaskTemplate::default()
507 },
508 TaskTemplate {
509 label: format!("go test {}", GO_PACKAGE_TASK_VARIABLE.template_value()),
510 command: "go".into(),
511 args: vec!["test".into(), GO_PACKAGE_TASK_VARIABLE.template_value()],
512 ..TaskTemplate::default()
513 },
514 TaskTemplate {
515 label: "go test ./...".into(),
516 command: "go".into(),
517 args: vec!["test".into(), "./...".into()],
518 ..TaskTemplate::default()
519 },
520 TaskTemplate {
521 label: format!("go run {}", GO_PACKAGE_TASK_VARIABLE.template_value(),),
522 command: "go".into(),
523 args: vec!["run".into(), GO_PACKAGE_TASK_VARIABLE.template_value()],
524 tags: vec!["go-main".to_owned()],
525 ..TaskTemplate::default()
526 },
527 ]))
528 }
529}
530
531#[cfg(test)]
532mod tests {
533 use super::*;
534 use crate::language;
535 use gpui::Hsla;
536 use theme::SyntaxTheme;
537
538 #[gpui::test]
539 async fn test_go_label_for_completion() {
540 let adapter = Arc::new(GoLspAdapter);
541 let language = language("go", tree_sitter_go::language());
542
543 let theme = SyntaxTheme::new_test([
544 ("type", Hsla::default()),
545 ("keyword", Hsla::default()),
546 ("function", Hsla::default()),
547 ("number", Hsla::default()),
548 ("property", Hsla::default()),
549 ]);
550 language.set_theme(&theme);
551
552 let grammar = language.grammar().unwrap();
553 let highlight_function = grammar.highlight_id_for_name("function").unwrap();
554 let highlight_type = grammar.highlight_id_for_name("type").unwrap();
555 let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
556 let highlight_number = grammar.highlight_id_for_name("number").unwrap();
557
558 assert_eq!(
559 adapter
560 .label_for_completion(
561 &lsp::CompletionItem {
562 kind: Some(lsp::CompletionItemKind::FUNCTION),
563 label: "Hello".to_string(),
564 detail: Some("func(a B) c.D".to_string()),
565 ..Default::default()
566 },
567 &language
568 )
569 .await,
570 Some(CodeLabel {
571 text: "Hello(a B) c.D".to_string(),
572 filter_range: 0..5,
573 runs: vec![
574 (0..5, highlight_function),
575 (8..9, highlight_type),
576 (13..14, highlight_type),
577 ],
578 })
579 );
580
581 // Nested methods
582 assert_eq!(
583 adapter
584 .label_for_completion(
585 &lsp::CompletionItem {
586 kind: Some(lsp::CompletionItemKind::METHOD),
587 label: "one.two.Three".to_string(),
588 detail: Some("func() [3]interface{}".to_string()),
589 ..Default::default()
590 },
591 &language
592 )
593 .await,
594 Some(CodeLabel {
595 text: "one.two.Three() [3]interface{}".to_string(),
596 filter_range: 0..13,
597 runs: vec![
598 (8..13, highlight_function),
599 (17..18, highlight_number),
600 (19..28, highlight_keyword),
601 ],
602 })
603 );
604
605 // Nested fields
606 assert_eq!(
607 adapter
608 .label_for_completion(
609 &lsp::CompletionItem {
610 kind: Some(lsp::CompletionItemKind::FIELD),
611 label: "two.Three".to_string(),
612 detail: Some("a.Bcd".to_string()),
613 ..Default::default()
614 },
615 &language
616 )
617 .await,
618 Some(CodeLabel {
619 text: "two.Three a.Bcd".to_string(),
620 filter_range: 0..9,
621 runs: vec![(12..15, highlight_type)],
622 })
623 );
624 }
625}