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