go.rs

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