793 lines · 28.6 KB · Rust
RawDownload
use anyhow::{Context as _, Result};use async_trait::async_trait;use collections::HashMap;use futures::StreamExt;use gpui::{App, AsyncApp, Task};use http_client::github::latest_github_release;pub use language::*;use lsp::{LanguageServerBinary, LanguageServerName};use project::Fs;use regex::Regex;use serde_json::json;use smol::fs;use std::{    any::Any,    borrow::Cow,    ffi::{OsStr, OsString},    ops::Range,    path::PathBuf,    process::Output,    str,    sync::{        Arc, LazyLock,        atomic::{AtomicBool, Ordering::SeqCst},    },};use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};use util::{ResultExt, fs::remove_matching, maybe}; fn server_binary_arguments() -> Vec<OsString> {    vec!["-mode=stdio".into()]} #[derive(Copy, Clone)]pub struct GoLspAdapter; impl GoLspAdapter {    const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("gopls");} static VERSION_REGEX: LazyLock<Regex> =    LazyLock::new(|| Regex::new(r"\d+\.\d+\.\d+").expect("Failed to create VERSION_REGEX")); static GO_ESCAPE_SUBTEST_NAME_REGEX: LazyLock<Regex> = LazyLock::new(|| {    Regex::new(r#"[.*+?^${}()|\[\]\\]"#).expect("Failed to create GO_ESCAPE_SUBTEST_NAME_REGEX")}); const BINARY: &str = if cfg!(target_os = "windows") {    "gopls.exe"} else {    "gopls"}; #[async_trait(?Send)]impl super::LspAdapter for GoLspAdapter {    fn name(&self) -> LanguageServerName {        Self::SERVER_NAME.clone()    }     async fn fetch_latest_server_version(        &self,        delegate: &dyn LspAdapterDelegate,    ) -> Result<Box<dyn 'static + Send + Any>> {        let release =            latest_github_release("golang/tools", false, false, delegate.http_client()).await?;        let version: Option<String> = release.tag_name.strip_prefix("gopls/v").map(str::to_string);        if version.is_none() {            log::warn!(                "couldn't infer gopls version from GitHub release tag name '{}'",                release.tag_name            );        }        Ok(Box::new(version) as Box<_>)    }     async fn check_if_user_installed(        &self,        delegate: &dyn LspAdapterDelegate,        _: Arc<dyn LanguageToolchainStore>,        _: &AsyncApp,    ) -> Option<LanguageServerBinary> {        let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;        Some(LanguageServerBinary {            path,            arguments: server_binary_arguments(),            env: None,        })    }     fn will_fetch_server(        &self,        delegate: &Arc<dyn LspAdapterDelegate>,        cx: &mut AsyncApp,    ) -> Option<Task<Result<()>>> {        static DID_SHOW_NOTIFICATION: AtomicBool = AtomicBool::new(false);         const NOTIFICATION_MESSAGE: &str =            "Could not install the Go language server `gopls`, because `go` was not found.";         let delegate = delegate.clone();        Some(cx.spawn(async move |cx| {            if delegate.which("go".as_ref()).await.is_none() {                if DID_SHOW_NOTIFICATION                    .compare_exchange(false, true, SeqCst, SeqCst)                    .is_ok()                {                    cx.update(|cx| {                        delegate.show_notification(NOTIFICATION_MESSAGE, cx);                    })?                }                anyhow::bail!("cannot install gopls");            }            Ok(())        }))    }     async fn fetch_server_binary(        &self,        version: Box<dyn 'static + Send + Any>,        container_dir: PathBuf,        delegate: &dyn LspAdapterDelegate,    ) -> Result<LanguageServerBinary> {        let go = delegate.which("go".as_ref()).await.unwrap_or("go".into());        let go_version_output = util::command::new_smol_command(&go)            .args(["version"])            .output()            .await            .context("failed to get go version via `go version` command`")?;        let go_version = parse_version_output(&go_version_output)?;        let version = version.downcast::<Option<String>>().unwrap();        let this = *self;         if let Some(version) = *version {            let binary_path = container_dir.join(format!("gopls_{version}_go_{go_version}"));            if let Ok(metadata) = fs::metadata(&binary_path).await {                if metadata.is_file() {                    remove_matching(&container_dir, |entry| {                        entry != binary_path && entry.file_name() != Some(OsStr::new("gobin"))                    })                    .await;                     return Ok(LanguageServerBinary {                        path: binary_path.to_path_buf(),                        arguments: server_binary_arguments(),                        env: None,                    });                }            }        } else if let Some(path) = this            .cached_server_binary(container_dir.clone(), delegate)            .await        {            return Ok(path);        }         let gobin_dir = container_dir.join("gobin");        fs::create_dir_all(&gobin_dir).await?;        let install_output = util::command::new_smol_command(go)            .env("GO111MODULE", "on")            .env("GOBIN", &gobin_dir)            .args(["install", "golang.org/x/tools/gopls@latest"])            .output()            .await?;         if !install_output.status.success() {            log::error!(                "failed to install gopls via `go install`. stdout: {:?}, stderr: {:?}",                String::from_utf8_lossy(&install_output.stdout),                String::from_utf8_lossy(&install_output.stderr)            );            anyhow::bail!(                "failed to install gopls with `go install`. Is `go` installed and in the PATH? Check logs for more information."            );        }         let installed_binary_path = gobin_dir.join(BINARY);        let version_output = util::command::new_smol_command(&installed_binary_path)            .arg("version")            .output()            .await            .context("failed to run installed gopls binary")?;        let gopls_version = parse_version_output(&version_output)?;        let binary_path = container_dir.join(format!("gopls_{gopls_version}_go_{go_version}"));        fs::rename(&installed_binary_path, &binary_path).await?;         Ok(LanguageServerBinary {            path: binary_path.to_path_buf(),            arguments: server_binary_arguments(),            env: None,        })    }     async fn cached_server_binary(        &self,        container_dir: PathBuf,        _: &dyn LspAdapterDelegate,    ) -> Option<LanguageServerBinary> {        get_cached_server_binary(container_dir).await    }     async fn initialization_options(        self: Arc<Self>,        _: &dyn Fs,        _: &Arc<dyn LspAdapterDelegate>,    ) -> Result<Option<serde_json::Value>> {        Ok(Some(json!({            "usePlaceholders": true,            "hints": {                "assignVariableTypes": true,                "compositeLiteralFields": true,                "compositeLiteralTypes": true,                "constantValues": true,                "functionTypeParameters": true,                "parameterNames": true,                "rangeVariableTypes": true            }        })))    }     async fn label_for_completion(        &self,        completion: &lsp::CompletionItem,        language: &Arc<Language>,    ) -> Option<CodeLabel> {        let label = &completion.label;         // Gopls returns nested fields and methods as completions.        // To syntax highlight these, combine their final component        // with their detail.        let name_offset = label.rfind('.').unwrap_or(0);         match completion.kind.zip(completion.detail.as_ref()) {            Some((lsp::CompletionItemKind::MODULE, detail)) => {                let text = format!("{label} {detail}");                let source = Rope::from(format!("import {text}").as_str());                let runs = language.highlight_text(&source, 7..7 + text.len());                let filter_range = completion                    .filter_text                    .as_deref()                    .and_then(|filter_text| {                        text.find(filter_text)                            .map(|start| start..start + filter_text.len())                    })                    .unwrap_or(0..label.len());                return Some(CodeLabel {                    text,                    runs,                    filter_range,                });            }            Some((                lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE,                detail,            )) => {                let text = format!("{label} {detail}");                let source =                    Rope::from(format!("var {} {}", &text[name_offset..], detail).as_str());                let runs = adjust_runs(                    name_offset,                    language.highlight_text(&source, 4..4 + text.len()),                );                let filter_range = completion                    .filter_text                    .as_deref()                    .and_then(|filter_text| {                        text.find(filter_text)                            .map(|start| start..start + filter_text.len())                    })                    .unwrap_or(0..label.len());                return Some(CodeLabel {                    text,                    runs,                    filter_range,                });            }            Some((lsp::CompletionItemKind::STRUCT, _)) => {                let text = format!("{label} struct {{}}");                let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());                let runs = adjust_runs(                    name_offset,                    language.highlight_text(&source, 5..5 + text.len()),                );                let filter_range = completion                    .filter_text                    .as_deref()                    .and_then(|filter_text| {                        text.find(filter_text)                            .map(|start| start..start + filter_text.len())                    })                    .unwrap_or(0..label.len());                return Some(CodeLabel {                    text,                    runs,                    filter_range,                });            }            Some((lsp::CompletionItemKind::INTERFACE, _)) => {                let text = format!("{label} interface {{}}");                let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());                let runs = adjust_runs(                    name_offset,                    language.highlight_text(&source, 5..5 + text.len()),                );                let filter_range = completion                    .filter_text                    .as_deref()                    .and_then(|filter_text| {                        text.find(filter_text)                            .map(|start| start..start + filter_text.len())                    })                    .unwrap_or(0..label.len());                return Some(CodeLabel {                    text,                    runs,                    filter_range,                });            }            Some((lsp::CompletionItemKind::FIELD, detail)) => {                let text = format!("{label} {detail}");                let source =                    Rope::from(format!("type T struct {{ {} }}", &text[name_offset..]).as_str());                let runs = adjust_runs(                    name_offset,                    language.highlight_text(&source, 16..16 + text.len()),                );                let filter_range = completion                    .filter_text                    .as_deref()                    .and_then(|filter_text| {                        text.find(filter_text)                            .map(|start| start..start + filter_text.len())                    })                    .unwrap_or(0..label.len());                return Some(CodeLabel {                    text,                    runs,                    filter_range,                });            }            Some((lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD, detail)) => {                if let Some(signature) = detail.strip_prefix("func") {                    let text = format!("{label}{signature}");                    let source = Rope::from(format!("func {} {{}}", &text[name_offset..]).as_str());                    let runs = adjust_runs(                        name_offset,                        language.highlight_text(&source, 5..5 + text.len()),                    );                    let filter_range = completion                        .filter_text                        .as_deref()                        .and_then(|filter_text| {                            text.find(filter_text)                                .map(|start| start..start + filter_text.len())                        })                        .unwrap_or(0..label.len());                    return Some(CodeLabel {                        filter_range,                        text,                        runs,                    });                }            }            _ => {}        }        None    }     async fn label_for_symbol(        &self,        name: &str,        kind: lsp::SymbolKind,        language: &Arc<Language>,    ) -> Option<CodeLabel> {        let (text, filter_range, display_range) = match kind {            lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {                let text = format!("func {} () {{}}", name);                let filter_range = 5..5 + name.len();                let display_range = 0..filter_range.end;                (text, filter_range, display_range)            }            lsp::SymbolKind::STRUCT => {                let text = format!("type {} struct {{}}", name);                let filter_range = 5..5 + name.len();                let display_range = 0..text.len();                (text, filter_range, display_range)            }            lsp::SymbolKind::INTERFACE => {                let text = format!("type {} interface {{}}", name);                let filter_range = 5..5 + name.len();                let display_range = 0..text.len();                (text, filter_range, display_range)            }            lsp::SymbolKind::CLASS => {                let text = format!("type {} T", name);                let filter_range = 5..5 + name.len();                let display_range = 0..filter_range.end;                (text, filter_range, display_range)            }            lsp::SymbolKind::CONSTANT => {                let text = format!("const {} = nil", name);                let filter_range = 6..6 + name.len();                let display_range = 0..filter_range.end;                (text, filter_range, display_range)            }            lsp::SymbolKind::VARIABLE => {                let text = format!("var {} = nil", name);                let filter_range = 4..4 + name.len();                let display_range = 0..filter_range.end;                (text, filter_range, display_range)            }            lsp::SymbolKind::MODULE => {                let text = format!("package {}", name);                let filter_range = 8..8 + name.len();                let display_range = 0..filter_range.end;                (text, filter_range, display_range)            }            _ => return None,        };         Some(CodeLabel {            runs: language.highlight_text(&text.as_str().into(), display_range.clone()),            text: text[display_range].to_string(),            filter_range,        })    }     fn diagnostic_message_to_markdown(&self, message: &str) -> Option<String> {        static REGEX: LazyLock<Regex> =            LazyLock::new(|| Regex::new(r"(?m)\n\s*").expect("Failed to create REGEX"));        Some(REGEX.replace_all(message, "\n\n").to_string())    }} fn parse_version_output(output: &Output) -> Result<&str> {    let version_stdout =        str::from_utf8(&output.stdout).context("version command produced invalid utf8 output")?;     let version = VERSION_REGEX        .find(version_stdout)        .with_context(|| format!("failed to parse version output '{version_stdout}'"))?        .as_str();     Ok(version)} async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {    maybe!(async {        let mut last_binary_path = None;        let mut entries = fs::read_dir(&container_dir).await?;        while let Some(entry) = entries.next().await {            let entry = entry?;            if entry.file_type().await?.is_file()                && entry                    .file_name()                    .to_str()                    .map_or(false, |name| name.starts_with("gopls_"))            {                last_binary_path = Some(entry.path());            }        }         let path = last_binary_path.context("no cached binary")?;        anyhow::Ok(LanguageServerBinary {            path,            arguments: server_binary_arguments(),            env: None,        })    })    .await    .log_err()} fn adjust_runs(    delta: usize,    mut runs: Vec<(Range<usize>, HighlightId)>,) -> Vec<(Range<usize>, HighlightId)> {    for (range, _) in &mut runs {        range.start += delta;        range.end += delta;    }    runs} pub(crate) struct GoContextProvider; const GO_PACKAGE_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("GO_PACKAGE"));const GO_MODULE_ROOT_TASK_VARIABLE: VariableName =    VariableName::Custom(Cow::Borrowed("GO_MODULE_ROOT"));const GO_SUBTEST_NAME_TASK_VARIABLE: VariableName =    VariableName::Custom(Cow::Borrowed("GO_SUBTEST_NAME")); impl ContextProvider for GoContextProvider {    fn build_context(        &self,        variables: &TaskVariables,        location: ContextLocation<'_>,        _: Option<HashMap<String, String>>,        _: Arc<dyn LanguageToolchainStore>,        cx: &mut gpui::App,    ) -> Task<Result<TaskVariables>> {        let local_abs_path = location

Lines 1–500 of 793.