go.rs

  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(Self::SERVER_NAME.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}