985 lines · 35.8 KB · Rust
RawDownload
use anyhow::{anyhow, bail, Context, Result};use async_compression::futures::bufread::GzipDecoder;use async_trait::async_trait;use futures::{io::BufReader, StreamExt};use gpui::{AppContext, AsyncAppContext};use http::github::{latest_github_release, GitHubLspBinaryVersion};pub use language::*;use language_settings::all_language_settings;use lazy_static::lazy_static;use lsp::LanguageServerBinary;use project::project_settings::{BinarySettings, ProjectSettings};use regex::Regex;use settings::Settings;use smol::fs::{self, File};use std::{    any::Any,    borrow::Cow,    env::consts,    path::{Path, PathBuf},    sync::Arc,};use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};use util::{fs::remove_matching, maybe, ResultExt}; pub struct RustLspAdapter; impl RustLspAdapter {    const SERVER_NAME: &'static str = "rust-analyzer";} #[async_trait(?Send)]impl LspAdapter for RustLspAdapter {    fn name(&self) -> LanguageServerName {        LanguageServerName(Self::SERVER_NAME.into())    }     async fn check_if_user_installed(        &self,        delegate: &dyn LspAdapterDelegate,        cx: &AsyncAppContext,    ) -> Option<LanguageServerBinary> {        let configured_binary = cx.update(|cx| {            ProjectSettings::get_global(cx)                .lsp                .get(Self::SERVER_NAME)                .and_then(|s| s.binary.clone())        });         match configured_binary {            Ok(Some(BinarySettings {                path,                arguments,                path_lookup,            })) => {                let (path, env) = match (path, path_lookup) {                    (Some(path), lookup) => {                        if lookup.is_some() {                            log::warn!(                                "Both `path` and `path_lookup` are set, ignoring `path_lookup`"                            );                        }                        (Some(path.into()), None)                    }                    (None, Some(true)) => {                        let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;                        let env = delegate.shell_env().await;                        (Some(path), Some(env))                    }                    (None, Some(false)) | (None, None) => (None, None),                };                path.map(|path| LanguageServerBinary {                    path,                    arguments: arguments                        .unwrap_or_default()                        .iter()                        .map(|arg| arg.into())                        .collect(),                    env,                })            }            _ => None,        }    }     async fn fetch_latest_server_version(        &self,        delegate: &dyn LspAdapterDelegate,    ) -> Result<Box<dyn 'static + Send + Any>> {        let release = latest_github_release(            "rust-lang/rust-analyzer",            true,            false,            delegate.http_client(),        )        .await?;        let os = match consts::OS {            "macos" => "apple-darwin",            "linux" => "unknown-linux-gnu",            "windows" => "pc-windows-msvc",            other => bail!("Running on unsupported os: {other}"),        };        let asset_name = format!("rust-analyzer-{}-{os}.gz", consts::ARCH);        let asset = release            .assets            .iter()            .find(|asset| asset.name == asset_name)            .with_context(|| format!("no asset found matching `{asset_name:?}`"))?;        Ok(Box::new(GitHubLspBinaryVersion {            name: release.tag_name,            url: asset.browser_download_url.clone(),        }))    }     async fn fetch_server_binary(        &self,        version: Box<dyn 'static + Send + Any>,        container_dir: PathBuf,        delegate: &dyn LspAdapterDelegate,    ) -> Result<LanguageServerBinary> {        let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();        let destination_path = container_dir.join(format!("rust-analyzer-{}", version.name));         if fs::metadata(&destination_path).await.is_err() {            let mut response = delegate                .http_client()                .get(&version.url, Default::default(), true)                .await                .map_err(|err| anyhow!("error downloading release: {}", err))?;            let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));            let mut file = File::create(&destination_path).await?;            futures::io::copy(decompressed_bytes, &mut file).await?;            // todo("windows")            #[cfg(not(windows))]            {                fs::set_permissions(                    &destination_path,                    <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),                )                .await?;            }             remove_matching(&container_dir, |entry| entry != destination_path).await;        }         Ok(LanguageServerBinary {            path: destination_path,            env: None,            arguments: Default::default(),        })    }     async fn cached_server_binary(        &self,        container_dir: PathBuf,        _: &dyn LspAdapterDelegate,    ) -> Option<LanguageServerBinary> {        get_cached_server_binary(container_dir).await    }     async fn installation_test_binary(        &self,        container_dir: PathBuf,    ) -> Option<LanguageServerBinary> {        get_cached_server_binary(container_dir)            .await            .map(|mut binary| {                binary.arguments = vec!["--help".into()];                binary            })    }     fn disk_based_diagnostic_sources(&self) -> Vec<String> {        vec!["rustc".into()]    }     fn disk_based_diagnostics_progress_token(&self) -> Option<String> {        Some("rust-analyzer/flycheck".into())    }     fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {        lazy_static! {            static ref REGEX: Regex = Regex::new("(?m)`([^`]+)\n`$").unwrap();        }         for diagnostic in &mut params.diagnostics {            for message in diagnostic                .related_information                .iter_mut()                .flatten()                .map(|info| &mut info.message)                .chain([&mut diagnostic.message])            {                if let Cow::Owned(sanitized) = REGEX.replace_all(message, "`$1`") {                    *message = sanitized;                }            }        }    }     async fn label_for_completion(        &self,        completion: &lsp::CompletionItem,        language: &Arc<Language>,    ) -> Option<CodeLabel> {        let detail = completion            .label_details            .as_ref()            .and_then(|detail| detail.detail.as_ref())            .or(completion.detail.as_ref())            .map(ToOwned::to_owned);        match completion.kind {            Some(lsp::CompletionItemKind::FIELD) if detail.is_some() => {                let name = &completion.label;                let text = format!("{}: {}", name, detail.unwrap());                let source = Rope::from(format!("struct S {{ {} }}", text).as_str());                let runs = language.highlight_text(&source, 11..11 + text.len());                return Some(CodeLabel {                    text,                    runs,                    filter_range: 0..name.len(),                });            }            Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE)                if detail.is_some()                    && completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) =>            {                let name = &completion.label;                let text = format!("{}: {}", name, detail.unwrap());                let source = Rope::from(format!("let {} = ();", text).as_str());                let runs = language.highlight_text(&source, 4..4 + text.len());                return Some(CodeLabel {                    text,                    runs,                    filter_range: 0..name.len(),                });            }            Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD)                if detail.is_some() =>            {                lazy_static! {                    static ref REGEX: Regex = Regex::new("\\(…?\\)").unwrap();                }                let detail = detail.unwrap();                const FUNCTION_PREFIXES: [&'static str; 2] = ["async fn", "fn"];                let prefix = FUNCTION_PREFIXES                    .iter()                    .find_map(|prefix| detail.strip_prefix(*prefix).map(|suffix| (prefix, suffix)));                // fn keyword should be followed by opening parenthesis.                if let Some((prefix, suffix)) = prefix {                    if suffix.starts_with('(') {                        let text = REGEX.replace(&completion.label, suffix).to_string();                        let source = Rope::from(format!("{prefix} {} {{}}", text).as_str());                        let run_start = prefix.len() + 1;                        let runs =                            language.highlight_text(&source, run_start..run_start + text.len());                        return Some(CodeLabel {                            filter_range: 0..completion.label.find('(').unwrap_or(text.len()),                            text,                            runs,                        });                    }                }            }            Some(kind) => {                let highlight_name = match kind {                    lsp::CompletionItemKind::STRUCT                    | lsp::CompletionItemKind::INTERFACE                    | lsp::CompletionItemKind::ENUM => Some("type"),                    lsp::CompletionItemKind::ENUM_MEMBER => Some("variant"),                    lsp::CompletionItemKind::KEYWORD => Some("keyword"),                    lsp::CompletionItemKind::VALUE | lsp::CompletionItemKind::CONSTANT => {                        Some("constant")                    }                    _ => None,                };                let highlight_id = language.grammar()?.highlight_id_for_name(highlight_name?)?;                let mut label = completion.label.clone();                if let Some(detail) = detail.filter(|detail| detail.starts_with(" (")) {                    use std::fmt::Write;                    write!(label, "{detail}").ok()?;                }                let mut label = CodeLabel::plain(label, None);                label.runs.push((                    0..label.text.rfind('(').unwrap_or(completion.label.len()),                    highlight_id,                ));                return Some(label);            }            _ => {}        }        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!("fn {} () {{}}", name);                let filter_range = 3..3 + name.len();                let display_range = 0..filter_range.end;                (text, filter_range, display_range)            }            lsp::SymbolKind::STRUCT => {                let text = format!("struct {} {{}}", name);                let filter_range = 7..7 + name.len();                let display_range = 0..filter_range.end;                (text, filter_range, display_range)            }            lsp::SymbolKind::ENUM => {                let text = format!("enum {} {{}}", name);                let filter_range = 5..5 + name.len();                let display_range = 0..filter_range.end;                (text, filter_range, display_range)            }            lsp::SymbolKind::INTERFACE => {                let text = format!("trait {} {{}}", name);                let filter_range = 6..6 + name.len();                let display_range = 0..filter_range.end;                (text, filter_range, display_range)            }            lsp::SymbolKind::CONSTANT => {                let text = format!("const {}: () = ();", name);                let filter_range = 6..6 + name.len();                let display_range = 0..filter_range.end;                (text, filter_range, display_range)            }            lsp::SymbolKind::MODULE => {                let text = format!("mod {} {{}}", name);                let filter_range = 4..4 + name.len();                let display_range = 0..filter_range.end;                (text, filter_range, display_range)            }            lsp::SymbolKind::TYPE_PARAMETER => {                let text = format!("type {} {{}}", name);                let filter_range = 5..5 + 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,        })    }} pub(crate) struct RustContextProvider; const RUST_PACKAGE_TASK_VARIABLE: VariableName =    VariableName::Custom(Cow::Borrowed("RUST_PACKAGE")); /// The bin name corresponding to the current file in Cargo.tomlconst RUST_BIN_NAME_TASK_VARIABLE: VariableName =    VariableName::Custom(Cow::Borrowed("RUST_BIN_NAME")); const RUST_MAIN_FUNCTION_TASK_VARIABLE: VariableName =    VariableName::Custom(Cow::Borrowed("_rust_main_function_end")); impl ContextProvider for RustContextProvider {    fn build_context(        &self,        task_variables: &TaskVariables,        location: &Location,        cx: &mut gpui::AppContext,    ) -> Result<TaskVariables> {        let local_abs_path = location            .buffer            .read(cx)            .file()            .and_then(|file| Some(file.as_local()?.abs_path(cx)));         let local_abs_path = local_abs_path.as_deref();         let is_main_function = task_variables            .get(&RUST_MAIN_FUNCTION_TASK_VARIABLE)            .is_some();         if is_main_function {            if let Some((package_name, bin_name)) = local_abs_path                .and_then(|local_abs_path| package_name_and_bin_name_from_abs_path(local_abs_path))            {                return Ok(TaskVariables::from_iter([                    (RUST_PACKAGE_TASK_VARIABLE.clone(), package_name),                    (RUST_BIN_NAME_TASK_VARIABLE.clone(), bin_name),                ]));            }        }         if let Some(package_name) = local_abs_path            .and_then(|local_abs_path| local_abs_path.parent())            .and_then(human_readable_package_name)        {            return Ok(TaskVariables::from_iter([(                RUST_PACKAGE_TASK_VARIABLE.clone(),                package_name,            )]));        }         Ok(TaskVariables::default())    }     fn associated_tasks(        &self,        file: Option<Arc<dyn language::File>>,        cx: &AppContext,    ) -> Option<TaskTemplates> {        const DEFAULT_RUN_NAME_STR: &'static str = "RUST_DEFAULT_PACKAGE_RUN";        let package_to_run = all_language_settings(file.as_ref(), cx)            .language(Some("Rust"))            .tasks            .variables            .get(DEFAULT_RUN_NAME_STR);        let run_task_args = if let Some(package_to_run) = package_to_run {            vec!["run".into(), "-p".into(), package_to_run.clone()]        } else {            vec!["run".into()]        };        Some(TaskTemplates(vec![            TaskTemplate {                label: format!(                    "cargo check -p {}",                    RUST_PACKAGE_TASK_VARIABLE.template_value(),                ),                command: "cargo".into(),                args: vec![                    "check".into(),                    "-p".into(),                    RUST_PACKAGE_TASK_VARIABLE.template_value(),                ],                cwd: Some("$ZED_DIRNAME".to_owned()),                ..TaskTemplate::default()            },            TaskTemplate {                label: "cargo check --workspace --all-targets".into(),                command: "cargo".into(),                args: vec!["check".into(), "--workspace".into(), "--all-targets".into()],                cwd: Some("$ZED_DIRNAME".to_owned()),                ..TaskTemplate::default()            },            TaskTemplate {                label: format!(                    "cargo test -p {} {} -- --nocapture",                    RUST_PACKAGE_TASK_VARIABLE.template_value(),                    VariableName::Symbol.template_value(),                ),                command: "cargo".into(),                args: vec![                    "test".into(),                    "-p".into(),                    RUST_PACKAGE_TASK_VARIABLE.template_value(),                    VariableName::Symbol.template_value(),                    "--".into(),                    "--nocapture".into(),                ],                tags: vec!["rust-test".to_owned()],                cwd: Some("$ZED_DIRNAME".to_owned()),                ..TaskTemplate::default()            },            TaskTemplate {                label: format!(                    "cargo test -p {} {}",                    RUST_PACKAGE_TASK_VARIABLE.template_value(),                    VariableName::Stem.template_value(),                ),                command: "cargo".into(),                args: vec![                    "test".into(),                    "-p".into(),                    RUST_PACKAGE_TASK_VARIABLE.template_value(),                    VariableName::Stem.template_value(),                ],                tags: vec!["rust-mod-test".to_owned()],                cwd: Some("$ZED_DIRNAME".to_owned()),                ..TaskTemplate::default()            },            TaskTemplate {                label: format!(                    "cargo run -p {} --bin {}",                    RUST_PACKAGE_TASK_VARIABLE.template_value(),                    RUST_BIN_NAME_TASK_VARIABLE.template_value(),                ),                command: "cargo".into(),                args: vec![                    "run".into(),                    "-p".into(),                    RUST_PACKAGE_TASK_VARIABLE.template_value(),                    "--bin".into(),                    RUST_BIN_NAME_TASK_VARIABLE.template_value(),                ],                cwd: Some("$ZED_DIRNAME".to_owned()),                tags: vec!["rust-main".to_owned()],                ..TaskTemplate::default()            },

Lines 1–500 of 985.