From d9a5490646f68efdb70f84713d3a418a2b2a0b00 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 16 Nov 2020 00:19:04 +0200 Subject: [PATCH 01/10] Start rust-analyzer server for arbitrary rust files --- editors/code/src/client.ts | 2 +- editors/code/src/ctx.ts | 2 +- editors/code/src/main.ts | 81 +++++++++++++++++++++----------------- 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 116f41df6e..131a2f19a7 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -23,7 +23,7 @@ function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownStri return result; } -export function createClient(serverPath: string, cwd: string, extraEnv: Env): lc.LanguageClient { +export function createClient(serverPath: string, cwd: string | undefined, extraEnv: Env): lc.LanguageClient { // '.' Is the fallback if no folder is open // TODO?: Workspace folders support Uri's (eg: file://test.txt). // It might be a good idea to test if the uri points to a file. diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index bd023f803e..9d8620823d 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -22,7 +22,7 @@ export class Ctx { config: Config, extCtx: vscode.ExtensionContext, serverPath: string, - cwd: string, + cwd?: string, ): Promise { const client = createClient(serverPath, cwd, config.serverExtraEnv); diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index aaedc24314..f0f47a75b6 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -7,7 +7,7 @@ import * as commands from './commands'; import { activateInlayHints } from './inlay_hints'; import { Ctx } from './ctx'; import { Config } from './config'; -import { log, assert, isValidExecutable } from './util'; +import { log, assert, isValidExecutable, isRustDocument } from './util'; import { PersistentState } from './persistent_state'; import { fetchRelease, download } from './net'; import { activateTaskProvider } from './tasks'; @@ -28,26 +28,6 @@ export async function activate(context: vscode.ExtensionContext) { } async function tryActivate(context: vscode.ExtensionContext) { - // Register a "dumb" onEnter command for the case where server fails to - // start. - // - // FIXME: refactor command registration code such that commands are - // **always** registered, even if the server does not start. Use API like - // this perhaps? - // - // ```TypeScript - // registerCommand( - // factory: (Ctx) => ((Ctx) => any), - // fallback: () => any = () => vscode.window.showErrorMessage( - // "rust-analyzer is not available" - // ), - // ) - const defaultOnEnter = vscode.commands.registerCommand( - 'rust-analyzer.onEnter', - () => vscode.commands.executeCommand('default:type', { text: '\n' }), - ); - context.subscriptions.push(defaultOnEnter); - const config = new Config(context); const state = new PersistentState(context.globalState); const serverPath = await bootstrap(config, state).catch(err => { @@ -67,14 +47,52 @@ async function tryActivate(context: vscode.ExtensionContext) { const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; if (workspaceFolder === undefined) { - throw new Error("no folder is opened"); + let rustDocuments = vscode.workspace.textDocuments.filter(document => isRustDocument(document)); + if (rustDocuments.length > 0) { + ctx = await Ctx.create(config, context, serverPath); + } else { + throw new Error("no rust files are opened"); + } + } else { + // Note: we try to start the server before we activate type hints so that it + // registers its `onDidChangeDocument` handler before us. + // + // This a horribly, horribly wrong way to deal with this problem. + ctx = await Ctx.create(config, context, serverPath, workspaceFolder.uri.fsPath); + ctx.pushCleanup(activateTaskProvider(workspaceFolder, ctx.config)); } + await initCommonContext(context, ctx); - // Note: we try to start the server before we activate type hints so that it - // registers its `onDidChangeDocument` handler before us. + activateInlayHints(ctx); + warnAboutExtensionConflicts(); + + vscode.workspace.onDidChangeConfiguration( + _ => ctx?.client?.sendNotification('workspace/didChangeConfiguration', { settings: "" }), + null, + ctx.subscriptions, + ); +} + +async function initCommonContext(context: vscode.ExtensionContext, ctx: Ctx) { + // Register a "dumb" onEnter command for the case where server fails to + // start. // - // This a horribly, horribly wrong way to deal with this problem. - ctx = await Ctx.create(config, context, serverPath, workspaceFolder.uri.fsPath); + // FIXME: refactor command registration code such that commands are + // **always** registered, even if the server does not start. Use API like + // this perhaps? + // + // ```TypeScript + // registerCommand( + // factory: (Ctx) => ((Ctx) => any), + // fallback: () => any = () => vscode.window.showErrorMessage( + // "rust-analyzer is not available" + // ), + // ) + const defaultOnEnter = vscode.commands.registerCommand( + 'rust-analyzer.onEnter', + () => vscode.commands.executeCommand('default:type', { text: '\n' }), + ); + context.subscriptions.push(defaultOnEnter); await setContextValue(RUST_PROJECT_CONTEXT_NAME, true); @@ -134,17 +152,6 @@ async function tryActivate(context: vscode.ExtensionContext) { ctx.registerCommand('resolveCodeAction', commands.resolveCodeAction); ctx.registerCommand('applyActionGroup', commands.applyActionGroup); ctx.registerCommand('gotoLocation', commands.gotoLocation); - - ctx.pushCleanup(activateTaskProvider(workspaceFolder, ctx.config)); - - activateInlayHints(ctx); - warnAboutExtensionConflicts(); - - vscode.workspace.onDidChangeConfiguration( - _ => ctx?.client?.sendNotification('workspace/didChangeConfiguration', { settings: "" }), - null, - ctx.subscriptions, - ); } export async function deactivate() { From b3383b06614e5f302a3afa2fc2c177303b5b6ca8 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 23 May 2021 16:22:13 +0300 Subject: [PATCH 02/10] Send detached files info to server via init params --- editors/code/src/client.ts | 15 +++++++++++++-- editors/code/src/ctx.ts | 14 ++++++++++++-- editors/code/src/main.ts | 4 ++-- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 131a2f19a7..cb8beb343e 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -4,6 +4,7 @@ import * as ra from '../src/lsp_ext'; import * as Is from 'vscode-languageclient/lib/common/utils/is'; import { assert } from './util'; import { WorkspaceEdit } from 'vscode'; +import { Workspace } from './ctx'; export interface Env { [name: string]: string; @@ -23,7 +24,7 @@ function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownStri return result; } -export function createClient(serverPath: string, cwd: string | undefined, extraEnv: Env): lc.LanguageClient { +export function createClient(serverPath: string, workspace: Workspace, extraEnv: Env): lc.LanguageClient { // '.' Is the fallback if no folder is open // TODO?: Workspace folders support Uri's (eg: file://test.txt). // It might be a good idea to test if the uri points to a file. @@ -31,6 +32,11 @@ export function createClient(serverPath: string, cwd: string | undefined, extraE const newEnv = Object.assign({}, process.env); Object.assign(newEnv, extraEnv); + let cwd = undefined; + if (workspace.kind == "Workspace Folder") { + cwd = workspace.folder.fsPath; + }; + const run: lc.Executable = { command: serverPath, options: { cwd, env: newEnv }, @@ -43,9 +49,14 @@ export function createClient(serverPath: string, cwd: string | undefined, extraE 'Rust Analyzer Language Server Trace', ); + let initializationOptions = vscode.workspace.getConfiguration("rust-analyzer"); + if (workspace.kind == "Detached files") { + initializationOptions = { "detachedFiles": workspace.files.map(file => file.uri.fsPath), ...initializationOptions }; + } + const clientOptions: lc.LanguageClientOptions = { documentSelector: [{ scheme: 'file', language: 'rust' }], - initializationOptions: vscode.workspace.getConfiguration("rust-analyzer"), + initializationOptions, diagnosticCollectionName: "rustc", traceOutputChannel, middleware: { diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 9d8620823d..dbfb9c6a1d 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -7,6 +7,16 @@ import { createClient } from './client'; import { isRustEditor, RustEditor } from './util'; import { ServerStatusParams } from './lsp_ext'; +export type Workspace = + { + kind: 'Workspace Folder', + folder: vscode.Uri, + } + | { + kind: 'Detached files', + files: vscode.TextDocument[], + }; + export class Ctx { private constructor( readonly config: Config, @@ -22,9 +32,9 @@ export class Ctx { config: Config, extCtx: vscode.ExtensionContext, serverPath: string, - cwd?: string, + workspace: Workspace, ): Promise { - const client = createClient(serverPath, cwd, config.serverExtraEnv); + const client = createClient(serverPath, workspace, config.serverExtraEnv); const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); extCtx.subscriptions.push(statusBar); diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index f0f47a75b6..1a4af548d3 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -49,7 +49,7 @@ async function tryActivate(context: vscode.ExtensionContext) { if (workspaceFolder === undefined) { let rustDocuments = vscode.workspace.textDocuments.filter(document => isRustDocument(document)); if (rustDocuments.length > 0) { - ctx = await Ctx.create(config, context, serverPath); + ctx = await Ctx.create(config, context, serverPath, { kind: 'Detached files', files: rustDocuments }); } else { throw new Error("no rust files are opened"); } @@ -58,7 +58,7 @@ async function tryActivate(context: vscode.ExtensionContext) { // registers its `onDidChangeDocument` handler before us. // // This a horribly, horribly wrong way to deal with this problem. - ctx = await Ctx.create(config, context, serverPath, workspaceFolder.uri.fsPath); + ctx = await Ctx.create(config, context, serverPath, { kind: "Workspace Folder", folder: workspaceFolder.uri }); ctx.pushCleanup(activateTaskProvider(workspaceFolder, ctx.config)); } await initCommonContext(context, ctx); From 695569d9784b4a7d6e91451a0cc354f8bd009b59 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 23 May 2021 20:32:22 +0300 Subject: [PATCH 03/10] Draft detached files retrieval --- crates/project_model/src/lib.rs | 1 + crates/project_model/src/workspace.rs | 3 +++ crates/rust-analyzer/src/bin/main.rs | 2 +- crates/rust-analyzer/src/config.rs | 20 ++++++++++++++++++-- crates/rust-analyzer/src/main_loop.rs | 1 + crates/rust-analyzer/src/reload.rs | 1 + 6 files changed, 25 insertions(+), 3 deletions(-) diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs index 8c6cf94c24..c2fde00d52 100644 --- a/crates/project_model/src/lib.rs +++ b/crates/project_model/src/lib.rs @@ -50,6 +50,7 @@ pub use proc_macro_api::ProcMacroClient; pub enum ProjectManifest { ProjectJson(AbsPathBuf), CargoToml(AbsPathBuf), + DetachedFile(AbsPathBuf), } impl ProjectManifest { diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs index 607e62ea59..5fd6487106 100644 --- a/crates/project_model/src/workspace.rs +++ b/crates/project_model/src/workspace.rs @@ -148,6 +148,9 @@ impl ProjectWorkspace { let rustc_cfg = rustc_cfg::get(Some(&cargo_toml), config.target.as_deref()); ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } } + ProjectManifest::DetachedFile(_) => { + todo!("TODO kb") + } }; Ok(res) diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index f0abb5b15f..7ee35d52b3 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs @@ -214,7 +214,7 @@ fn run_server() -> Result<()> { let discovered = ProjectManifest::discover_all(&workspace_roots); log::info!("discovered projects: {:?}", discovered); - if discovered.is_empty() { + if discovered.is_empty() && config.detached_files().is_empty() { log::error!("failed to find any projects in {:?}", workspace_roots); } diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index b700d025f1..570534c9a0 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -236,6 +236,7 @@ impl Default for ConfigData { pub struct Config { caps: lsp_types::ClientCapabilities, data: ConfigData, + detached_files: Vec, pub discovered_projects: Option>, pub root_path: AbsPathBuf, } @@ -328,13 +329,24 @@ pub struct WorkspaceSymbolConfig { impl Config { pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self { - Config { caps, data: ConfigData::default(), discovered_projects: None, root_path } + Config { + caps, + data: ConfigData::default(), + detached_files: Vec::new(), + discovered_projects: None, + root_path, + } } - pub fn update(&mut self, json: serde_json::Value) { + pub fn update(&mut self, mut json: serde_json::Value) { log::info!("updating config from JSON: {:#}", json); if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) { return; } + self.detached_files = get_field::>(&mut json, "detachedFiles", None, "[]") + .into_iter() + .map(AbsPathBuf::assert) + .map(ProjectManifest::DetachedFile) + .collect(); self.data = ConfigData::from_json(json); } @@ -387,6 +399,10 @@ impl Config { } } + pub fn detached_files(&self) -> &[ProjectManifest] { + &self.detached_files + } + pub fn did_save_text_document_dynamic_registration(&self) -> bool { let caps = try_or!(self.caps.text_document.as_ref()?.synchronization.clone()?, Default::default()); diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index f837b89ddc..cb002f7009 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -103,6 +103,7 @@ impl fmt::Debug for Event { impl GlobalState { fn run(mut self, inbox: Receiver) -> Result<()> { if self.config.linked_projects().is_empty() + && self.config.detached_files().is_empty() && self.config.notifications().cargo_toml_not_found { self.show_message( diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 0ae2758cc7..cfa95275d1 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -146,6 +146,7 @@ impl GlobalState { log::info!("will fetch workspaces"); self.task_pool.handle.spawn_with_sender({ + // TODO kb reload workspace here? let linked_projects = self.config.linked_projects(); let cargo_config = self.config.cargo(); From de090749d9643a8035135092e2546cd0ddb854a6 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 23 May 2021 20:56:54 +0300 Subject: [PATCH 04/10] Drag detached files towards loading --- crates/project_model/src/lib.rs | 1 - crates/project_model/src/sysroot.rs | 4 +- crates/project_model/src/workspace.rs | 73 ++++++++++++++++++++++-- crates/rust-analyzer/src/config.rs | 5 +- crates/rust-analyzer/src/global_state.rs | 1 + crates/rust-analyzer/src/reload.rs | 10 +++- 6 files changed, 83 insertions(+), 11 deletions(-) diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs index c2fde00d52..8c6cf94c24 100644 --- a/crates/project_model/src/lib.rs +++ b/crates/project_model/src/lib.rs @@ -50,7 +50,6 @@ pub use proc_macro_api::ProcMacroClient; pub enum ProjectManifest { ProjectJson(AbsPathBuf), CargoToml(AbsPathBuf), - DetachedFile(AbsPathBuf), } impl ProjectManifest { diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs index 3b0ff506d4..c89e2f6e1b 100644 --- a/crates/project_model/src/sysroot.rs +++ b/crates/project_model/src/sysroot.rs @@ -50,7 +50,9 @@ impl Sysroot { pub fn discover(cargo_toml: &AbsPath) -> Result { log::debug!("Discovering sysroot for {}", cargo_toml.display()); - let current_dir = cargo_toml.parent().unwrap(); + let current_dir = cargo_toml.parent().ok_or_else(|| { + format_err!("Failed to find the parent directory for file {:?}", cargo_toml) + })?; let sysroot_dir = discover_sysroot_dir(current_dir)?; let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir, current_dir)?; let res = Sysroot::load(&sysroot_src_dir)?; diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs index 5fd6487106..ad4c202f20 100644 --- a/crates/project_model/src/workspace.rs +++ b/crates/project_model/src/workspace.rs @@ -4,7 +4,7 @@ use std::{collections::VecDeque, fmt, fs, path::Path, process::Command}; -use anyhow::{Context, Result}; +use anyhow::{format_err, Context, Result}; use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro}; use cargo_workspace::DepKind; use cfg::CfgOptions; @@ -49,6 +49,8 @@ pub enum ProjectWorkspace { }, /// Project workspace was manually specified using a `rust-project.json` file. Json { project: ProjectJson, sysroot: Option, rustc_cfg: Vec }, + /// TODO kb docs + DetachedFiles { files: Vec, sysroot: Sysroot, rustc_cfg: Vec }, } impl fmt::Debug for ProjectWorkspace { @@ -75,6 +77,12 @@ impl fmt::Debug for ProjectWorkspace { debug_struct.field("n_rustc_cfg", &rustc_cfg.len()); debug_struct.finish() } + ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => f + .debug_struct("DetachedFiles") + .field("n_files", &files.len()) + .field("n_sysroot_crates", &sysroot.crates().len()) + .field("n_rustc_cfg", &rustc_cfg.len()) + .finish(), } } } @@ -148,9 +156,6 @@ impl ProjectWorkspace { let rustc_cfg = rustc_cfg::get(Some(&cargo_toml), config.target.as_deref()); ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } } - ProjectManifest::DetachedFile(_) => { - todo!("TODO kb") - } }; Ok(res) @@ -168,6 +173,14 @@ impl ProjectWorkspace { Ok(ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg }) } + pub fn load_detached_files(detached_files: Vec) -> Result { + let sysroot = Sysroot::discover( + &detached_files.first().ok_or_else(|| format_err!("No detached files to load"))?, + )?; + let rustc_cfg = rustc_cfg::get(None, None); + Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg }) + } + /// Returns the roots for the current `ProjectWorkspace` /// The return type contains the path and whether or not /// the root is a member of the current workspace @@ -227,6 +240,19 @@ impl ProjectWorkspace { }) })) .collect(), + ProjectWorkspace::DetachedFiles { files, sysroot, .. } => files + .into_iter() + .map(|detached_file| PackageRoot { + is_member: true, + include: vec![detached_file.clone()], + exclude: Vec::new(), + }) + .chain(sysroot.crates().map(|krate| PackageRoot { + is_member: false, + include: vec![sysroot[krate].root_dir().to_path_buf()], + exclude: Vec::new(), + })) + .collect(), } } @@ -237,6 +263,9 @@ impl ProjectWorkspace { let rustc_package_len = rustc.as_ref().map_or(0, |rc| rc.packages().len()); cargo.packages().len() + sysroot.crates().len() + rustc_package_len } + ProjectWorkspace::DetachedFiles { sysroot, files, .. } => { + sysroot.crates().len() + files.len() + } } } @@ -270,6 +299,9 @@ impl ProjectWorkspace { rustc, rustc.as_ref().zip(build_data).and_then(|(it, map)| map.get(it.workspace_root())), ), + ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => { + detached_files_to_crate_graph(rustc_cfg.clone(), load, files, sysroot) + } }; if crate_graph.patch_cfg_if() { log::debug!("Patched std to depend on cfg-if") @@ -477,6 +509,39 @@ fn cargo_to_crate_graph( crate_graph } +// TODO kb refactor and check for correctness +fn detached_files_to_crate_graph( + rustc_cfg: Vec, + load: &mut dyn FnMut(&AbsPath) -> Option, + detached_files: &[AbsPathBuf], + sysroot: &Sysroot, +) -> CrateGraph { + let _p = profile::span("detached_files_to_crate_graph"); + let mut crate_graph = CrateGraph::default(); + let (public_deps, _libproc_macro) = + sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load); + + let mut cfg_options = CfgOptions::default(); + cfg_options.extend(rustc_cfg); + + for detached_file in detached_files { + let file_id = load(&detached_file).unwrap(); + let detached_file_crate = crate_graph.add_crate_root( + file_id, + Edition::Edition2018, + None, + cfg_options.clone(), + Env::default(), + Vec::new(), + ); + + for (name, krate) in public_deps.iter() { + add_dep(&mut crate_graph, detached_file_crate, name.clone(), *krate); + } + } + crate_graph +} + fn handle_rustc_crates( rustc_workspace: &CargoWorkspace, load: &mut dyn FnMut(&AbsPath) -> Option, diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 570534c9a0..7c02a507cd 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -236,7 +236,7 @@ impl Default for ConfigData { pub struct Config { caps: lsp_types::ClientCapabilities, data: ConfigData, - detached_files: Vec, + detached_files: Vec, pub discovered_projects: Option>, pub root_path: AbsPathBuf, } @@ -345,7 +345,6 @@ impl Config { self.detached_files = get_field::>(&mut json, "detachedFiles", None, "[]") .into_iter() .map(AbsPathBuf::assert) - .map(ProjectManifest::DetachedFile) .collect(); self.data = ConfigData::from_json(json); } @@ -399,7 +398,7 @@ impl Config { } } - pub fn detached_files(&self) -> &[ProjectManifest] { + pub fn detached_files(&self) -> &[AbsPathBuf] { &self.detached_files } diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index 6f2f482c15..6a36d29d4d 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -312,6 +312,7 @@ impl GlobalStateSnapshot { cargo.target_by_root(&path).map(|it| (cargo, it)) } ProjectWorkspace::Json { .. } => None, + ProjectWorkspace::DetachedFiles { .. } => None, }) } } diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index cfa95275d1..7a53e4a8b9 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -146,8 +146,8 @@ impl GlobalState { log::info!("will fetch workspaces"); self.task_pool.handle.spawn_with_sender({ - // TODO kb reload workspace here? let linked_projects = self.config.linked_projects(); + let detached_files = self.config.detached_files().to_vec(); let cargo_config = self.config.cargo(); move |sender| { @@ -162,7 +162,7 @@ impl GlobalState { sender.send(Task::FetchWorkspace(ProjectWorkspaceProgress::Begin)).unwrap(); - let workspaces = linked_projects + let mut workspaces = linked_projects .iter() .map(|project| match project { LinkedProject::ProjectManifest(manifest) => { @@ -181,6 +181,11 @@ impl GlobalState { }) .collect::>(); + if !detached_files.is_empty() { + workspaces + .push(project_model::ProjectWorkspace::load_detached_files(detached_files)); + } + log::info!("did fetch workspaces {:?}", workspaces); sender .send(Task::FetchWorkspace(ProjectWorkspaceProgress::End(workspaces))) @@ -408,6 +413,7 @@ impl GlobalState { _ => None, } } + ProjectWorkspace::DetachedFiles { .. } => None, }) .map(|(id, root)| { let sender = sender.clone(); From 2ca2e24a39594762be80ac11247b231c13a4b2af Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 23 May 2021 23:10:05 +0300 Subject: [PATCH 05/10] Do not add cargo target for detached files only project --- crates/rust-analyzer/src/handlers.rs | 35 +++++++++++++++++----------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index aa12fd94bd..f482104248 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -661,19 +661,28 @@ pub(crate) fn handle_runnables( } } None => { - res.push(lsp_ext::Runnable { - label: "cargo check --workspace".to_string(), - location: None, - kind: lsp_ext::RunnableKind::Cargo, - args: lsp_ext::CargoRunnable { - workspace_root: None, - override_cargo: config.override_cargo, - cargo_args: vec!["check".to_string(), "--workspace".to_string()], - cargo_extra_args: config.cargo_extra_args, - executable_args: Vec::new(), - expect_test: None, - }, - }); + if !snap.config.linked_projects().is_empty() + || !snap + .config + .discovered_projects + .as_ref() + .map(|projects| projects.is_empty()) + .unwrap_or(true) + { + res.push(lsp_ext::Runnable { + label: "cargo check --workspace".to_string(), + location: None, + kind: lsp_ext::RunnableKind::Cargo, + args: lsp_ext::CargoRunnable { + workspace_root: None, + override_cargo: config.override_cargo, + cargo_args: vec!["check".to_string(), "--workspace".to_string()], + cargo_extra_args: config.cargo_extra_args, + executable_args: Vec::new(), + expect_test: None, + }, + }); + } } } Ok(res) From 72594beca46ac4a05b0c54f26a285f5197192b8b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 23 May 2021 23:29:26 +0300 Subject: [PATCH 06/10] Deal with todos --- crates/project_model/src/workspace.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs index ad4c202f20..ede82d88bd 100644 --- a/crates/project_model/src/workspace.rs +++ b/crates/project_model/src/workspace.rs @@ -49,7 +49,8 @@ pub enum ProjectWorkspace { }, /// Project workspace was manually specified using a `rust-project.json` file. Json { project: ProjectJson, sysroot: Option, rustc_cfg: Vec }, - /// TODO kb docs + /// Project with a set of disjoint files, not belonging to any particular workspace. + /// Backed by basic sysroot crates for basic completion and highlighting. DetachedFiles { files: Vec, sysroot: Sysroot, rustc_cfg: Vec }, } @@ -509,7 +510,6 @@ fn cargo_to_crate_graph( crate_graph } -// TODO kb refactor and check for correctness fn detached_files_to_crate_graph( rustc_cfg: Vec, load: &mut dyn FnMut(&AbsPath) -> Option, @@ -525,11 +525,21 @@ fn detached_files_to_crate_graph( cfg_options.extend(rustc_cfg); for detached_file in detached_files { - let file_id = load(&detached_file).unwrap(); + let file_id = match load(&detached_file) { + Some(file_id) => file_id, + None => { + log::error!("Failed to load detached file {:?}", detached_file); + continue; + } + }; + let display_name = detached_file + .file_stem() + .and_then(|os_str| os_str.to_str()) + .map(|file_stem| CrateDisplayName::from_canonical_name(file_stem.to_string())); let detached_file_crate = crate_graph.add_crate_root( file_id, Edition::Edition2018, - None, + display_name, cfg_options.clone(), Env::default(), Vec::new(), From c1f6a5a0b0df92b6fb61aab92fe612d179fbab5d Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 23 May 2021 23:47:58 +0300 Subject: [PATCH 07/10] Fix ts lint errors --- editors/code/src/client.ts | 4 ++-- editors/code/src/ctx.ts | 8 ++++---- editors/code/src/main.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index cb8beb343e..69dbe25353 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -33,7 +33,7 @@ export function createClient(serverPath: string, workspace: Workspace, extraEnv: Object.assign(newEnv, extraEnv); let cwd = undefined; - if (workspace.kind == "Workspace Folder") { + if (workspace.kind === "Workspace Folder") { cwd = workspace.folder.fsPath; }; @@ -50,7 +50,7 @@ export function createClient(serverPath: string, workspace: Workspace, extraEnv: ); let initializationOptions = vscode.workspace.getConfiguration("rust-analyzer"); - if (workspace.kind == "Detached files") { + if (workspace.kind === "Detached Files") { initializationOptions = { "detachedFiles": workspace.files.map(file => file.uri.fsPath), ...initializationOptions }; } diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index dbfb9c6a1d..22c5f62a1c 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -9,12 +9,12 @@ import { ServerStatusParams } from './lsp_ext'; export type Workspace = { - kind: 'Workspace Folder', - folder: vscode.Uri, + kind: 'Workspace Folder'; + folder: vscode.Uri; } | { - kind: 'Detached files', - files: vscode.TextDocument[], + kind: 'Detached Files'; + files: vscode.TextDocument[]; }; export class Ctx { diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 1a4af548d3..b735186fe1 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -47,9 +47,9 @@ async function tryActivate(context: vscode.ExtensionContext) { const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; if (workspaceFolder === undefined) { - let rustDocuments = vscode.workspace.textDocuments.filter(document => isRustDocument(document)); + const rustDocuments = vscode.workspace.textDocuments.filter(document => isRustDocument(document)); if (rustDocuments.length > 0) { - ctx = await Ctx.create(config, context, serverPath, { kind: 'Detached files', files: rustDocuments }); + ctx = await Ctx.create(config, context, serverPath, { kind: 'Detached Files', files: rustDocuments }); } else { throw new Error("no rust files are opened"); } From b5f524c7ff4973d58b5e5ec70765157a6cb0c526 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 24 May 2021 00:09:24 +0300 Subject: [PATCH 08/10] Don't discover workspaces when detached files are given --- crates/rust-analyzer/src/bin/main.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index 7ee35d52b3..6c883dd58b 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs @@ -199,7 +199,7 @@ fn run_server() -> Result<()> { config.update(json); } - if config.linked_projects().is_empty() { + if config.linked_projects().is_empty() && config.detached_files().is_empty() { let workspace_roots = initialize_params .workspace_folders .map(|workspaces| { @@ -214,10 +214,9 @@ fn run_server() -> Result<()> { let discovered = ProjectManifest::discover_all(&workspace_roots); log::info!("discovered projects: {:?}", discovered); - if discovered.is_empty() && config.detached_files().is_empty() { + if discovered.is_empty() { log::error!("failed to find any projects in {:?}", workspace_roots); } - config.discovered_projects = Some(discovered); } From d8c36029cc3961ce742aa6e5d59926c5a7e7170a Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 24 May 2021 14:47:20 +0300 Subject: [PATCH 09/10] Small file error display fix Co-authored-by: Aleksey Kladov --- crates/project_model/src/sysroot.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs index c89e2f6e1b..4e39d6dd3c 100644 --- a/crates/project_model/src/sysroot.rs +++ b/crates/project_model/src/sysroot.rs @@ -51,7 +51,7 @@ impl Sysroot { pub fn discover(cargo_toml: &AbsPath) -> Result { log::debug!("Discovering sysroot for {}", cargo_toml.display()); let current_dir = cargo_toml.parent().ok_or_else(|| { - format_err!("Failed to find the parent directory for file {:?}", cargo_toml) + format_err!("Failed to find the parent directory for {}", cargo_toml.display()) })?; let sysroot_dir = discover_sysroot_dir(current_dir)?; let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir, current_dir)?; From 5c0369b1d0c5351672f2a16e9a0d17beee84bcbe Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 24 May 2021 14:52:57 +0300 Subject: [PATCH 10/10] Add a FIXME --- crates/project_model/src/workspace.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs index ede82d88bd..84990075f7 100644 --- a/crates/project_model/src/workspace.rs +++ b/crates/project_model/src/workspace.rs @@ -49,6 +49,15 @@ pub enum ProjectWorkspace { }, /// Project workspace was manually specified using a `rust-project.json` file. Json { project: ProjectJson, sysroot: Option, rustc_cfg: Vec }, + + // FIXME: The primary limitation of this approach is that the set of detached files needs to be fixed at the beginning. + // That's not the end user experience we should strive for. + // Ideally, you should be able to just open a random detached file in existing cargo projects, and get the basic features working. + // That needs some changes on the salsa-level though. + // In particular, we should split the unified CrateGraph (which currently has maximal durability) into proper crate graph, and a set of ad hoc roots (with minimal durability). + // Then, we need to hide the graph behind the queries such that most queries look only at the proper crate graph, and fall back to ad hoc roots only if there's no results. + // After this, we should be able to tweak the logic in reload.rs to add newly opened files, which don't belong to any existing crates, to the set of the detached files. + // // /// Project with a set of disjoint files, not belonging to any particular workspace. /// Backed by basic sysroot crates for basic completion and highlighting. DetachedFiles { files: Vec, sysroot: Sysroot, rustc_cfg: Vec },