From 8af3d6367ecead0abf80e697176f697d97c25215 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Thu, 9 Mar 2023 15:06:26 -0500 Subject: [PATCH 01/12] This commit add Cargo-style project discovery for Buck and Bazel users. This feature requires the user to add a command that generates a `rust-project.json` from a set of files. Project discovery can be invoked in two ways: 1. At extension activation time, which includes the generated `rust-project.json` as part of the linkedProjects argument in InitializeParams 2. Through a new command titled "Add current file to workspace", which makes use of a new, rust-analyzer specific LSP request that adds the workspace without erasing any existing workspaces. I think that the command-running functionality _could_ merit being placed into its own extension (and expose it via extension contribution points), if only provide build-system idiomatic progress reporting and status handling, but I haven't (yet) made an extension that does this. --- crates/project-model/src/cfg_flag.rs | 13 ++++ crates/project-model/src/project_json.rs | 21 ++++-- crates/rust-analyzer/src/config.rs | 10 ++- crates/rust-analyzer/src/handlers.rs | 17 +++++ crates/rust-analyzer/src/lsp_ext.rs | 15 ++++ crates/rust-analyzer/src/main_loop.rs | 1 + editors/code/package.json | 18 ++++- editors/code/src/commands.ts | 19 ++++- editors/code/src/config.ts | 4 ++ editors/code/src/ctx.ts | 42 ++++++++--- editors/code/src/lsp_ext.ts | 6 ++ editors/code/src/main.ts | 9 +-- editors/code/src/rust_project.ts | 91 ++++++++++++++++++++++++ editors/code/src/util.ts | 17 +++++ 14 files changed, 258 insertions(+), 25 deletions(-) create mode 100644 editors/code/src/rust_project.ts diff --git a/crates/project-model/src/cfg_flag.rs b/crates/project-model/src/cfg_flag.rs index c134b78ab3a..2a4767970c0 100644 --- a/crates/project-model/src/cfg_flag.rs +++ b/crates/project-model/src/cfg_flag.rs @@ -4,6 +4,7 @@ use std::{fmt, str::FromStr}; use cfg::CfgOptions; +use serde::Serialize; #[derive(Clone, Eq, PartialEq, Debug)] pub enum CfgFlag { @@ -38,6 +39,18 @@ fn deserialize(deserializer: D) -> Result } } +impl Serialize for CfgFlag { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + CfgFlag::Atom(s) => serializer.serialize_str(s), + CfgFlag::KeyValue { .. } => serializer.serialize_str(&format!("{}", &self)), + } + } +} + impl Extend for CfgOptions { fn extend>(&mut self, iter: T) { for cfg_flag in iter { diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs index 4b2448e47f1..0f779e5307e 100644 --- a/crates/project-model/src/project_json.rs +++ b/crates/project-model/src/project_json.rs @@ -54,7 +54,7 @@ use base_db::{CrateDisplayName, CrateId, CrateName, Dependency, Edition}; use paths::{AbsPath, AbsPathBuf}; use rustc_hash::FxHashMap; -use serde::{de, Deserialize}; +use serde::{de, ser, Deserialize, Serialize}; use crate::cfg_flag::CfgFlag; @@ -171,14 +171,14 @@ pub fn path(&self) -> &AbsPath { } } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct ProjectJsonData { sysroot: Option, sysroot_src: Option, crates: Vec, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] struct CrateData { display_name: Option, root_module: PathBuf, @@ -200,7 +200,7 @@ struct CrateData { repository: Option, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename = "edition")] enum EditionData { #[serde(rename = "2015")] @@ -221,16 +221,16 @@ fn from(data: EditionData) -> Self { } } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] struct DepData { /// Identifies a crate by position in the crates array. #[serde(rename = "crate")] krate: usize, - #[serde(deserialize_with = "deserialize_crate_name")] + #[serde(deserialize_with = "deserialize_crate_name", serialize_with = "serialize_crate_name")] name: CrateName, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] struct CrateSource { include_dirs: Vec, exclude_dirs: Vec, @@ -243,3 +243,10 @@ fn deserialize_crate_name<'de, D>(de: D) -> Result let name = String::deserialize(de)?; CrateName::new(&name).map_err(|err| de::Error::custom(format!("invalid crate name: {err:?}"))) } + +fn serialize_crate_name(crate_name: &CrateName, serializer: S) -> Result +where + S: ser::Serializer, +{ + crate_name.serialize(serializer) +} diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 75233dbb2ab..05ad7ab4c4a 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -272,7 +272,6 @@ struct ConfigData { /// The warnings will be indicated by a blue squiggly underline in code /// and a blue icon in the `Problems Panel`. diagnostics_warningsAsInfo: Vec = "[]", - /// These directories will be ignored by rust-analyzer. They are /// relative to the workspace root, and globs are not supported. You may /// also need to add the folders to Code's `files.watcherExclude`. @@ -895,6 +894,15 @@ pub fn linked_projects(&self) -> Vec { } } + pub fn add_linked_projects(&mut self, linked_projects: Vec) { + let mut linked_projects = linked_projects + .into_iter() + .map(ManifestOrProjectJson::ProjectJson) + .collect::>(); + + self.data.linkedProjects.append(&mut linked_projects); + } + pub fn did_save_text_document_dynamic_registration(&self) -> bool { let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?); caps.did_save == Some(true) && caps.dynamic_registration == Some(true) diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 32ac9a42dec..c38addd5987 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -5,6 +5,7 @@ use std::{ io::Write as _, process::{self, Stdio}, + sync::Arc, }; use anyhow::Context; @@ -46,6 +47,22 @@ pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> Result<()> { state.proc_macro_clients.clear(); state.proc_macro_changed = false; + + state.fetch_workspaces_queue.request_op("reload workspace request".to_string()); + state.fetch_build_data_queue.request_op("reload workspace request".to_string()); + Ok(()) +} + +pub(crate) fn handle_add_project( + state: &mut GlobalState, + params: lsp_ext::AddProjectParams, +) -> Result<()> { + state.proc_macro_clients.clear(); + state.proc_macro_changed = false; + + let config = Arc::make_mut(&mut state.config); + config.add_linked_projects(params.project); + state.fetch_workspaces_queue.request_op("reload workspace request".to_string()); state.fetch_build_data_queue.request_op("reload workspace request".to_string()); Ok(()) diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index c7b513db981..e6caebe3537 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -9,6 +9,7 @@ notification::Notification, CodeActionKind, DocumentOnTypeFormattingParams, PartialResultParams, Position, Range, TextDocumentIdentifier, WorkDoneProgressParams, }; +use project_model::ProjectJsonData; use serde::{Deserialize, Serialize}; use crate::line_index::PositionEncoding; @@ -51,6 +52,20 @@ impl Request for ReloadWorkspace { const METHOD: &'static str = "rust-analyzer/reloadWorkspace"; } +pub enum AddProject {} + +impl Request for AddProject { + type Params = AddProjectParams; + type Result = (); + const METHOD: &'static str = "rust-analyzer/addProject"; +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct AddProjectParams { + pub project: Vec, +} + pub enum SyntaxTree {} impl Request for SyntaxTree { diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index dd0804b4398..1cc771552a9 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -625,6 +625,7 @@ fn on_request(&mut self, req: Request) { .on_sync_mut::(handlers::handle_workspace_reload) .on_sync_mut::(handlers::handle_memory_usage) .on_sync_mut::(handlers::handle_shuffle_crate_graph) + .on_sync_mut::(handlers::handle_add_project) .on_sync::(handlers::handle_join_lines) .on_sync::(handlers::handle_on_enter) .on_sync::(handlers::handle_selection_range) diff --git a/editors/code/package.json b/editors/code/package.json index a3b1a3107d0..e79ab33726d 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -199,6 +199,11 @@ "title": "Reload workspace", "category": "rust-analyzer" }, + { + "command": "rust-analyzer.addProject", + "title": "Add current file to workspace", + "category": "rust-analyzer" + }, { "command": "rust-analyzer.reload", "title": "Restart server", @@ -447,6 +452,17 @@ "Fill missing expressions with reasonable defaults, `new` or `default` constructors." ] }, + "rust-analyzer.discoverProjectCommand": { + "markdownDescription": "Sets the command that rust-analyzer uses to generate `rust-project.json` files. This command is\n only suggested if a build system like Buck or Bazel is used. The command must accept files as arguements and return \n a rust-project.json over stdout.", + "default": null, + "type": [ + "null", + "array" + ], + "items": { + "type": "string" + } + }, "rust-analyzer.cachePriming.enable": { "markdownDescription": "Warm up caches on project load.", "default": true, @@ -1904,4 +1920,4 @@ } ] } -} +} \ No newline at end of file diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index f4a4579a92c..beff8501dc8 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -3,7 +3,7 @@ import * as lc from "vscode-languageclient"; import * as ra from "./lsp_ext"; import * as path from "path"; -import { Ctx, Cmd, CtxInit } from "./ctx"; +import { Ctx, Cmd, CtxInit, discoverWorkspace } from "./ctx"; import { applySnippetWorkspaceEdit, applySnippetTextEdits } from "./snippets"; import { spawnSync } from "child_process"; import { RunnableQuickPick, selectRunnable, createTask, createArgs } from "./run"; @@ -749,6 +749,23 @@ export function reloadWorkspace(ctx: CtxInit): Cmd { return async () => ctx.client.sendRequest(ra.reloadWorkspace); } +export function addProject(ctx: CtxInit): Cmd { + return async () => { + const discoverProjectCommand = ctx.config.discoverProjectCommand; + if (!discoverProjectCommand) { + return; + } + + let workspaces: JsonProject[] = await Promise.all(vscode.workspace.workspaceFolders!.map(async (folder): Promise => { + return discoverWorkspace(vscode.workspace.textDocuments, discoverProjectCommand, { cwd: folder.uri.fsPath }); + })); + + await ctx.client.sendRequest(ra.addProject, { + project: workspaces + }); + } +} + async function showReferencesImpl( client: LanguageClient | undefined, uri: string, diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 1faa0ad9106..f62843dffa6 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -214,6 +214,10 @@ export class Config { return this.get("trace.extension"); } + get discoverProjectCommand() { + return this.get("discoverProjectCommand") + } + get cargoRunner() { return this.get("cargoRunner"); } diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 1708d47cee7..ba2d4e97af1 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -4,10 +4,11 @@ import * as ra from "./lsp_ext"; import { Config, substituteVSCodeVariables } from "./config"; import { createClient } from "./client"; -import { isRustDocument, isRustEditor, LazyOutputChannel, log, RustEditor } from "./util"; +import { executeDiscoverProject, isRustDocument, isRustEditor, LazyOutputChannel, log, RustEditor } from "./util"; import { ServerStatusParams } from "./lsp_ext"; import { PersistentState } from "./persistent_state"; import { bootstrap } from "./bootstrap"; +import { ExecOptions } from "child_process"; // We only support local folders, not eg. Live Share (`vlsl:` scheme), so don't activate if // only those are in use. We use "Empty" to represent these scenarios @@ -16,12 +17,12 @@ import { bootstrap } from "./bootstrap"; export type Workspace = | { kind: "Empty" } | { - kind: "Workspace Folder"; - } + kind: "Workspace Folder"; + } | { - kind: "Detached Files"; - files: vscode.TextDocument[]; - }; + kind: "Detached Files"; + files: vscode.TextDocument[]; + }; export function fetchWorkspace(): Workspace { const folders = (vscode.workspace.workspaceFolders || []).filter( @@ -35,12 +36,19 @@ export function fetchWorkspace(): Workspace { ? rustDocuments.length === 0 ? { kind: "Empty" } : { - kind: "Detached Files", - files: rustDocuments, - } + kind: "Detached Files", + files: rustDocuments, + } : { kind: "Workspace Folder" }; } +export async function discoverWorkspace(files: readonly vscode.TextDocument[], command: string[], options: ExecOptions): Promise { + const paths = files.map((f) => f.uri.fsPath).join(" "); + const joinedCommand = command.join(" "); + const data = await executeDiscoverProject(`${joinedCommand} -- ${paths}`, options); + return JSON.parse(data) as JsonProject; +} + export type CommandFactory = { enabled: (ctx: CtxInit) => Cmd; disabled?: (ctx: Ctx) => Cmd; @@ -63,6 +71,7 @@ export class Ctx { private state: PersistentState; private commandFactories: Record; private commandDisposables: Disposable[]; + private discoveredWorkspaces: JsonProject[] | undefined; get client() { return this._client; @@ -71,7 +80,7 @@ export class Ctx { constructor( readonly extCtx: vscode.ExtensionContext, commandFactories: Record, - workspace: Workspace + workspace: Workspace, ) { extCtx.subscriptions.push(this); this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); @@ -169,7 +178,18 @@ export class Ctx { }; } - const initializationOptions = substituteVSCodeVariables(rawInitializationOptions); + const discoverProjectCommand = this.config.discoverProjectCommand; + if (discoverProjectCommand) { + let workspaces: JsonProject[] = await Promise.all(vscode.workspace.workspaceFolders!.map(async (folder): Promise => { + return discoverWorkspace(vscode.workspace.textDocuments, discoverProjectCommand, { cwd: folder.uri.fsPath }); + })); + + this.discoveredWorkspaces = workspaces; + } + + let initializationOptions = substituteVSCodeVariables(rawInitializationOptions); + // this appears to be load-bearing, for better or worse. + await initializationOptions.update('linkedProjects', this.discoveredWorkspaces) this._client = await createClient( this.traceOutputChannel, diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 400cd207d41..6c8428aa972 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -43,6 +43,10 @@ export const relatedTests = new lc.RequestType("rust-analyzer/reloadWorkspace"); +export const addProject = new lc.RequestType( + "rust-analyzer/addProject" +) + export const runFlycheck = new lc.NotificationType<{ textDocument: lc.TextDocumentIdentifier | null; }>("rust-analyzer/runFlycheck"); @@ -68,6 +72,8 @@ export const viewItemTree = new lc.RequestType export type AnalyzerStatusParams = { textDocument?: lc.TextDocumentIdentifier }; +export type AddProjectParams = { project: JsonProject[] }; + export type ExpandMacroParams = { textDocument: lc.TextDocumentIdentifier; position: lc.Position; diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 8a2412af849..323aa89ef06 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -24,11 +24,11 @@ export async function activate( vscode.window .showWarningMessage( `You have both the rust-analyzer (rust-lang.rust-analyzer) and Rust (rust-lang.rust) ` + - "plugins enabled. These are known to conflict and cause various functions of " + - "both plugins to not work correctly. You should disable one of them.", + "plugins enabled. These are known to conflict and cause various functions of " + + "both plugins to not work correctly. You should disable one of them.", "Got it" ) - .then(() => {}, console.error); + .then(() => { }, console.error); } const ctx = new Ctx(context, createCommands(), fetchWorkspace()); @@ -146,13 +146,14 @@ function createCommands(): Record { health: "stopped", }); }, - disabled: (_) => async () => {}, + disabled: (_) => async () => { }, }, analyzerStatus: { enabled: commands.analyzerStatus }, memoryUsage: { enabled: commands.memoryUsage }, shuffleCrateGraph: { enabled: commands.shuffleCrateGraph }, reloadWorkspace: { enabled: commands.reloadWorkspace }, + addProject: { enabled: commands.addProject }, matchingBrace: { enabled: commands.matchingBrace }, joinLines: { enabled: commands.joinLines }, parentModule: { enabled: commands.parentModule }, diff --git a/editors/code/src/rust_project.ts b/editors/code/src/rust_project.ts new file mode 100644 index 00000000000..adf0f89c961 --- /dev/null +++ b/editors/code/src/rust_project.ts @@ -0,0 +1,91 @@ +interface JsonProject { + /// Path to the directory with *source code* of + /// sysroot crates. + /// + /// It should point to the directory where std, + /// core, and friends can be found: + /// + /// https://github.com/rust-lang/rust/tree/master/library. + /// + /// If provided, rust-analyzer automatically adds + /// dependencies on sysroot crates. Conversely, + /// if you omit this path, you can specify sysroot + /// dependencies yourself and, for example, have + /// several different "sysroots" in one graph of + /// crates. + sysroot_src?: string; + /// The set of crates comprising the current + /// project. Must include all transitive + /// dependencies as well as sysroot crate (libstd, + /// libcore and such). + crates: Crate[]; +} + +interface Crate { + /// Optional crate name used for display purposes, + /// without affecting semantics. See the `deps` + /// key for semantically-significant crate names. + display_name?: string; + /// Path to the root module of the crate. + root_module: string; + /// Edition of the crate. + edition: "2015" | "2018" | "2021"; + /// Dependencies + deps: Dep[]; + /// Should this crate be treated as a member of + /// current "workspace". + /// + /// By default, inferred from the `root_module` + /// (members are the crates which reside inside + /// the directory opened in the editor). + /// + /// Set this to `false` for things like standard + /// library and 3rd party crates to enable + /// performance optimizations (rust-analyzer + /// assumes that non-member crates don't change). + is_workspace_member?: boolean; + /// Optionally specify the (super)set of `.rs` + /// files comprising this crate. + /// + /// By default, rust-analyzer assumes that only + /// files under `root_module.parent` can belong + /// to a crate. `include_dirs` are included + /// recursively, unless a subdirectory is in + /// `exclude_dirs`. + /// + /// Different crates can share the same `source`. + /// + /// If two crates share an `.rs` file in common, + /// they *must* have the same `source`. + /// rust-analyzer assumes that files from one + /// source can't refer to files in another source. + source?: { + include_dirs: string[], + exclude_dirs: string[], + }, + /// The set of cfgs activated for a given crate, like + /// `["unix", "feature=\"foo\"", "feature=\"bar\""]`. + cfg: string[]; + /// Target triple for this Crate. + /// + /// Used when running `rustc --print cfg` + /// to get target-specific cfgs. + target?: string; + /// Environment variables, used for + /// the `env!` macro + env: { [key: string]: string; }, + + /// Whether the crate is a proc-macro crate. + is_proc_macro: boolean; + /// For proc-macro crates, path to compiled + /// proc-macro (.so file). + proc_macro_dylib_path?: string; +} + +interface Dep { + /// Index of a crate in the `crates` array. + crate: number, + /// Name as should appear in the (implicit) + /// `extern crate name` declaration. + name: string, +} \ No newline at end of file diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts index d93b9caeb16..d2ecdce5b4e 100644 --- a/editors/code/src/util.ts +++ b/editors/code/src/util.ts @@ -150,9 +150,11 @@ export function memoizeAsync( /** Awaitable wrapper around `child_process.exec` */ export function execute(command: string, options: ExecOptions): Promise { + log.info(`running command: ${command}`) return new Promise((resolve, reject) => { exec(command, options, (err, stdout, stderr) => { if (err) { + log.error(err); reject(err); return; } @@ -167,6 +169,21 @@ export function execute(command: string, options: ExecOptions): Promise }); } +export function executeDiscoverProject(command: string, options: ExecOptions): Promise { + log.info(`running command: ${command}`) + return new Promise((resolve, reject) => { + exec(command, options, (err, stdout, _) => { + if (err) { + log.error(err); + reject(err); + return; + } + + resolve(stdout.trimEnd()); + }); + }); +} + export class LazyOutputChannel implements vscode.OutputChannel { constructor(name: string) { this.name = name; From 46e022098feda31c98120ca58b6ce02b45cdedf9 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Thu, 9 Mar 2023 15:27:24 -0500 Subject: [PATCH 02/12] fmt --- editors/code/package.json | 2 +- editors/code/src/commands.ts | 14 +++++---- editors/code/src/config.ts | 2 +- editors/code/src/ctx.ts | 49 +++++++++++++++++++++----------- editors/code/src/lsp_ext.ts | 2 +- editors/code/src/main.ts | 8 +++--- editors/code/src/rust_project.ts | 14 ++++----- editors/code/src/util.ts | 4 +-- 8 files changed, 58 insertions(+), 37 deletions(-) diff --git a/editors/code/package.json b/editors/code/package.json index e79ab33726d..0a3e4103f6e 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -1920,4 +1920,4 @@ } ] } -} \ No newline at end of file +} diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index beff8501dc8..a8ec75a78aa 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -756,14 +756,18 @@ export function addProject(ctx: CtxInit): Cmd { return; } - let workspaces: JsonProject[] = await Promise.all(vscode.workspace.workspaceFolders!.map(async (folder): Promise => { - return discoverWorkspace(vscode.workspace.textDocuments, discoverProjectCommand, { cwd: folder.uri.fsPath }); - })); + const workspaces: JsonProject[] = await Promise.all( + vscode.workspace.workspaceFolders!.map(async (folder): Promise => { + return discoverWorkspace(vscode.workspace.textDocuments, discoverProjectCommand, { + cwd: folder.uri.fsPath, + }); + }) + ); await ctx.client.sendRequest(ra.addProject, { - project: workspaces + project: workspaces, }); - } + }; } async function showReferencesImpl( diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index f62843dffa6..1dae603714d 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -215,7 +215,7 @@ export class Config { } get discoverProjectCommand() { - return this.get("discoverProjectCommand") + return this.get("discoverProjectCommand"); } get cargoRunner() { diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index ba2d4e97af1..5b019d6aeb5 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -4,7 +4,14 @@ import * as ra from "./lsp_ext"; import { Config, substituteVSCodeVariables } from "./config"; import { createClient } from "./client"; -import { executeDiscoverProject, isRustDocument, isRustEditor, LazyOutputChannel, log, RustEditor } from "./util"; +import { + executeDiscoverProject, + isRustDocument, + isRustEditor, + LazyOutputChannel, + log, + RustEditor, +} from "./util"; import { ServerStatusParams } from "./lsp_ext"; import { PersistentState } from "./persistent_state"; import { bootstrap } from "./bootstrap"; @@ -17,12 +24,12 @@ import { ExecOptions } from "child_process"; export type Workspace = | { kind: "Empty" } | { - kind: "Workspace Folder"; - } + kind: "Workspace Folder"; + } | { - kind: "Detached Files"; - files: vscode.TextDocument[]; - }; + kind: "Detached Files"; + files: vscode.TextDocument[]; + }; export function fetchWorkspace(): Workspace { const folders = (vscode.workspace.workspaceFolders || []).filter( @@ -36,13 +43,17 @@ export function fetchWorkspace(): Workspace { ? rustDocuments.length === 0 ? { kind: "Empty" } : { - kind: "Detached Files", - files: rustDocuments, - } + kind: "Detached Files", + files: rustDocuments, + } : { kind: "Workspace Folder" }; } -export async function discoverWorkspace(files: readonly vscode.TextDocument[], command: string[], options: ExecOptions): Promise { +export async function discoverWorkspace( + files: readonly vscode.TextDocument[], + command: string[], + options: ExecOptions +): Promise { const paths = files.map((f) => f.uri.fsPath).join(" "); const joinedCommand = command.join(" "); const data = await executeDiscoverProject(`${joinedCommand} -- ${paths}`, options); @@ -80,7 +91,7 @@ export class Ctx { constructor( readonly extCtx: vscode.ExtensionContext, commandFactories: Record, - workspace: Workspace, + workspace: Workspace ) { extCtx.subscriptions.push(this); this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); @@ -180,16 +191,22 @@ export class Ctx { const discoverProjectCommand = this.config.discoverProjectCommand; if (discoverProjectCommand) { - let workspaces: JsonProject[] = await Promise.all(vscode.workspace.workspaceFolders!.map(async (folder): Promise => { - return discoverWorkspace(vscode.workspace.textDocuments, discoverProjectCommand, { cwd: folder.uri.fsPath }); - })); + const workspaces: JsonProject[] = await Promise.all( + vscode.workspace.workspaceFolders!.map(async (folder): Promise => { + return discoverWorkspace( + vscode.workspace.textDocuments, + discoverProjectCommand, + { cwd: folder.uri.fsPath } + ); + }) + ); this.discoveredWorkspaces = workspaces; } - let initializationOptions = substituteVSCodeVariables(rawInitializationOptions); + const initializationOptions = substituteVSCodeVariables(rawInitializationOptions); // this appears to be load-bearing, for better or worse. - await initializationOptions.update('linkedProjects', this.discoveredWorkspaces) + await initializationOptions.update("linkedProjects", this.discoveredWorkspaces); this._client = await createClient( this.traceOutputChannel, diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 6c8428aa972..942573c0f16 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -45,7 +45,7 @@ export const relatedTests = new lc.RequestType("rust-analyzer/reloadWorkspace"); export const addProject = new lc.RequestType( "rust-analyzer/addProject" -) +); export const runFlycheck = new lc.NotificationType<{ textDocument: lc.TextDocumentIdentifier | null; diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 323aa89ef06..d5de00561b1 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -24,11 +24,11 @@ export async function activate( vscode.window .showWarningMessage( `You have both the rust-analyzer (rust-lang.rust-analyzer) and Rust (rust-lang.rust) ` + - "plugins enabled. These are known to conflict and cause various functions of " + - "both plugins to not work correctly. You should disable one of them.", + "plugins enabled. These are known to conflict and cause various functions of " + + "both plugins to not work correctly. You should disable one of them.", "Got it" ) - .then(() => { }, console.error); + .then(() => {}, console.error); } const ctx = new Ctx(context, createCommands(), fetchWorkspace()); @@ -146,7 +146,7 @@ function createCommands(): Record { health: "stopped", }); }, - disabled: (_) => async () => { }, + disabled: (_) => async () => {}, }, analyzerStatus: { enabled: commands.analyzerStatus }, diff --git a/editors/code/src/rust_project.ts b/editors/code/src/rust_project.ts index adf0f89c961..187a1a96c10 100644 --- a/editors/code/src/rust_project.ts +++ b/editors/code/src/rust_project.ts @@ -60,9 +60,9 @@ interface Crate { /// rust-analyzer assumes that files from one /// source can't refer to files in another source. source?: { - include_dirs: string[], - exclude_dirs: string[], - }, + include_dirs: string[]; + exclude_dirs: string[]; + }; /// The set of cfgs activated for a given crate, like /// `["unix", "feature=\"foo\"", "feature=\"bar\""]`. cfg: string[]; @@ -73,7 +73,7 @@ interface Crate { target?: string; /// Environment variables, used for /// the `env!` macro - env: { [key: string]: string; }, + env: { [key: string]: string }; /// Whether the crate is a proc-macro crate. is_proc_macro: boolean; @@ -84,8 +84,8 @@ interface Crate { interface Dep { /// Index of a crate in the `crates` array. - crate: number, + crate: number; /// Name as should appear in the (implicit) /// `extern crate name` declaration. - name: string, -} \ No newline at end of file + name: string; +} diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts index d2ecdce5b4e..922fbcbcf35 100644 --- a/editors/code/src/util.ts +++ b/editors/code/src/util.ts @@ -150,7 +150,7 @@ export function memoizeAsync( /** Awaitable wrapper around `child_process.exec` */ export function execute(command: string, options: ExecOptions): Promise { - log.info(`running command: ${command}`) + log.info(`running command: ${command}`); return new Promise((resolve, reject) => { exec(command, options, (err, stdout, stderr) => { if (err) { @@ -170,7 +170,7 @@ export function execute(command: string, options: ExecOptions): Promise } export function executeDiscoverProject(command: string, options: ExecOptions): Promise { - log.info(`running command: ${command}`) + log.info(`running command: ${command}`); return new Promise((resolve, reject) => { exec(command, options, (err, stdout, _) => { if (err) { From 1f5c5350898dc18961a810e73d9ef5f80eaf3ae5 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Thu, 9 Mar 2023 15:47:12 -0500 Subject: [PATCH 03/12] remove errant `--` in `executeDiscoverProject` --- editors/code/src/ctx.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 5b019d6aeb5..407d976bab9 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -56,7 +56,7 @@ export async function discoverWorkspace( ): Promise { const paths = files.map((f) => f.uri.fsPath).join(" "); const joinedCommand = command.join(" "); - const data = await executeDiscoverProject(`${joinedCommand} -- ${paths}`, options); + const data = await executeDiscoverProject(`${joinedCommand} ${paths}`, options); return JSON.parse(data) as JsonProject; } From 68d3eaead4100a32e705161ee0136121882949f1 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Thu, 9 Mar 2023 16:14:41 -0500 Subject: [PATCH 04/12] fix typo in package.json. --- editors/code/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/editors/code/package.json b/editors/code/package.json index 0a3e4103f6e..1e8cdb2eaa7 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -453,7 +453,7 @@ ] }, "rust-analyzer.discoverProjectCommand": { - "markdownDescription": "Sets the command that rust-analyzer uses to generate `rust-project.json` files. This command is\n only suggested if a build system like Buck or Bazel is used. The command must accept files as arguements and return \n a rust-project.json over stdout.", + "markdownDescription": "Sets the command that rust-analyzer uses to generate `rust-project.json` files. This command should only be used\n if a build system like Buck or Bazel is also in use. The command must accept files as arguments and return \n a rust-project.json over stdout.", "default": null, "type": [ "null", @@ -1920,4 +1920,4 @@ } ] } -} +} \ No newline at end of file From 7a6e1119af28aeb0c42d3092a7a9957df731c92c Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 10 Mar 2023 10:35:47 -0500 Subject: [PATCH 05/12] Update crates/rust-analyzer/src/handlers.rs Co-authored-by: Lukas Wirth --- crates/rust-analyzer/src/handlers.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index c38addd5987..4ac76557df1 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -63,8 +63,8 @@ pub(crate) fn handle_add_project( let config = Arc::make_mut(&mut state.config); config.add_linked_projects(params.project); - state.fetch_workspaces_queue.request_op("reload workspace request".to_string()); - state.fetch_build_data_queue.request_op("reload workspace request".to_string()); + state.fetch_workspaces_queue.request_op("linked projects changed".to_string()); + state.fetch_build_data_queue.request_op("linked projects changed".to_string()); Ok(()) } From 91371494eec262d6e8966f905497ee2e3c384181 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 10 Mar 2023 13:09:16 -0500 Subject: [PATCH 06/12] move `rust-analyzer.discoverProjectCommand` above `$generated-start` to avoid failing tests --- editors/code/package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/editors/code/package.json b/editors/code/package.json index 1e8cdb2eaa7..1e7e3b6cd5d 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -433,6 +433,17 @@ "default": false, "type": "boolean" }, + "rust-analyzer.discoverProjectCommand": { + "markdownDescription": "Sets the command that rust-analyzer uses to generate `rust-project.json` files. This command should only be used\n if a build system like Buck or Bazel is also in use. The command must accept files as arguments and return \n a rust-project.json over stdout.", + "default": null, + "type": [ + "null", + "array" + ], + "items": { + "type": "string" + } + }, "$generated-start": {}, "rust-analyzer.assist.emitMustUse": { "markdownDescription": "Whether to insert #[must_use] when generating `as_` methods\nfor enum variants.", @@ -452,17 +463,6 @@ "Fill missing expressions with reasonable defaults, `new` or `default` constructors." ] }, - "rust-analyzer.discoverProjectCommand": { - "markdownDescription": "Sets the command that rust-analyzer uses to generate `rust-project.json` files. This command should only be used\n if a build system like Buck or Bazel is also in use. The command must accept files as arguments and return \n a rust-project.json over stdout.", - "default": null, - "type": [ - "null", - "array" - ], - "items": { - "type": "string" - } - }, "rust-analyzer.cachePriming.enable": { "markdownDescription": "Warm up caches on project load.", "default": true, From 8d9bff0c74518d514d59a1638e4717f14caa1d71 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Fri, 10 Mar 2023 19:35:05 -0500 Subject: [PATCH 07/12] Add a workspace config-based approach to reloading discovered projects. --- editors/code/src/client.ts | 8 +++++--- editors/code/src/commands.ts | 12 ++++++++--- editors/code/src/config.ts | 17 +++++++++++---- editors/code/src/ctx.ts | 40 ++++++++++++++++++++++++------------ 4 files changed, 54 insertions(+), 23 deletions(-) diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 62980ca0464..9103ef2f8fd 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -3,10 +3,10 @@ import * as lc from "vscode-languageclient/node"; import * as vscode from "vscode"; import * as ra from "../src/lsp_ext"; import * as Is from "vscode-languageclient/lib/common/utils/is"; -import { assert } from "./util"; +import { assert, log } from "./util"; import * as diagnostics from "./diagnostics"; import { WorkspaceEdit } from "vscode"; -import { Config, substituteVSCodeVariables } from "./config"; +import { Config, prepareVSCodeConfig } from "./config"; import { randomUUID } from "crypto"; export interface Env { @@ -95,7 +95,9 @@ export async function createClient( const resp = await next(params, token); if (resp && Array.isArray(resp)) { return resp.map((val) => { - return substituteVSCodeVariables(val); + return prepareVSCodeConfig(val, (key, cfg) => { + cfg[key] = config.discoveredWorkspaces; + }); }); } else { return resp; diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index a8ec75a78aa..8a953577e99 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -758,14 +758,20 @@ export function addProject(ctx: CtxInit): Cmd { const workspaces: JsonProject[] = await Promise.all( vscode.workspace.workspaceFolders!.map(async (folder): Promise => { - return discoverWorkspace(vscode.workspace.textDocuments, discoverProjectCommand, { + const rustDocuments = vscode.workspace.textDocuments.filter(isRustDocument); + return discoverWorkspace(rustDocuments, discoverProjectCommand, { cwd: folder.uri.fsPath, }); }) ); - await ctx.client.sendRequest(ra.addProject, { - project: workspaces, + ctx.addToDiscoveredWorkspaces(workspaces); + + // this is a workaround to avoid needing writing the `rust-project.json` into + // a workspace-level VS Code-specific settings folder. We'd like to keep the + // `rust-project.json` entirely in-memory. + await ctx.client?.sendNotification(lc.DidChangeConfigurationNotification.type, { + settings: "", }); }; } diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 1dae603714d..75403725479 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -34,6 +34,7 @@ export class Config { constructor(ctx: vscode.ExtensionContext) { this.globalStorageUri = ctx.globalStorageUri; + this.discoveredWorkspaces = []; vscode.workspace.onDidChangeConfiguration( this.onDidChangeConfiguration, this, @@ -55,6 +56,8 @@ export class Config { log.info("Using configuration", Object.fromEntries(cfg)); } + public discoveredWorkspaces: JsonProject[]; + private async onDidChangeConfiguration(event: vscode.ConfigurationChangeEvent) { this.refreshLogging(); @@ -191,7 +194,7 @@ export class Config { * So this getter handles this quirk by not requiring the caller to use postfix `!` */ private get(path: string): T | undefined { - return substituteVSCodeVariables(this.cfg.get(path)); + return prepareVSCodeConfig(this.cfg.get(path)); } get serverPath() { @@ -284,18 +287,24 @@ export class Config { } } -export function substituteVSCodeVariables(resp: T): T { +export function prepareVSCodeConfig( + resp: T, + cb?: (key: Extract, res: { [key: string]: any }) => void +): T { if (Is.string(resp)) { return substituteVSCodeVariableInString(resp) as T; } else if (resp && Is.array(resp)) { return resp.map((val) => { - return substituteVSCodeVariables(val); + return prepareVSCodeConfig(val); }) as T; } else if (resp && typeof resp === "object") { const res: { [key: string]: any } = {}; for (const key in resp) { const val = resp[key]; - res[key] = substituteVSCodeVariables(val); + res[key] = prepareVSCodeConfig(val); + if (cb) { + cb(key, res); + } } return res as T; } diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 407d976bab9..ca30d239c95 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -2,7 +2,7 @@ import * as vscode from "vscode"; import * as lc from "vscode-languageclient/node"; import * as ra from "./lsp_ext"; -import { Config, substituteVSCodeVariables } from "./config"; +import { Config, prepareVSCodeConfig } from "./config"; import { createClient } from "./client"; import { executeDiscoverProject, @@ -54,7 +54,7 @@ export async function discoverWorkspace( command: string[], options: ExecOptions ): Promise { - const paths = files.map((f) => f.uri.fsPath).join(" "); + const paths = files.map((f) => `"${f.uri.fsPath}"`).join(" "); const joinedCommand = command.join(" "); const data = await executeDiscoverProject(`${joinedCommand} ${paths}`, options); return JSON.parse(data) as JsonProject; @@ -71,7 +71,7 @@ export type CtxInit = Ctx & { export class Ctx { readonly statusBar: vscode.StatusBarItem; - readonly config: Config; + config: Config; readonly workspace: Workspace; private _client: lc.LanguageClient | undefined; @@ -82,7 +82,6 @@ export class Ctx { private state: PersistentState; private commandFactories: Record; private commandDisposables: Disposable[]; - private discoveredWorkspaces: JsonProject[] | undefined; get client() { return this._client; @@ -193,20 +192,24 @@ export class Ctx { if (discoverProjectCommand) { const workspaces: JsonProject[] = await Promise.all( vscode.workspace.workspaceFolders!.map(async (folder): Promise => { - return discoverWorkspace( - vscode.workspace.textDocuments, - discoverProjectCommand, - { cwd: folder.uri.fsPath } - ); + const rustDocuments = vscode.workspace.textDocuments.filter(isRustDocument); + return discoverWorkspace(rustDocuments, discoverProjectCommand, { + cwd: folder.uri.fsPath, + }); }) ); - this.discoveredWorkspaces = workspaces; + this.addToDiscoveredWorkspaces(workspaces); } - const initializationOptions = substituteVSCodeVariables(rawInitializationOptions); - // this appears to be load-bearing, for better or worse. - await initializationOptions.update("linkedProjects", this.discoveredWorkspaces); + const initializationOptions = prepareVSCodeConfig( + rawInitializationOptions, + (key, obj) => { + if (key === "linkedProjects") { + obj["linkedProjects"] = this.config.discoveredWorkspaces; + } + } + ); this._client = await createClient( this.traceOutputChannel, @@ -288,6 +291,17 @@ export class Ctx { return this._serverPath; } + addToDiscoveredWorkspaces(workspaces: JsonProject[]) { + for (const workspace of workspaces) { + const index = this.config.discoveredWorkspaces.indexOf(workspace); + if (~index) { + this.config.discoveredWorkspaces[index] = workspace; + } else { + this.config.discoveredWorkspaces.push(workspace); + } + } + } + private updateCommands(forceDisable?: "disable") { this.commandDisposables.forEach((disposable) => disposable.dispose()); this.commandDisposables = []; From 56273b3cf5f480997c18d4edbe2e809e14cfd5c4 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Mon, 13 Mar 2023 13:17:17 -0400 Subject: [PATCH 08/12] Remove `rust-analyzer/addProject` in favor of notifying r-a that configuration has changed --- crates/project-model/src/cfg_flag.rs | 13 ------------- crates/project-model/src/project_json.rs | 21 +++++++-------------- crates/rust-analyzer/src/handlers.rs | 16 ---------------- crates/rust-analyzer/src/lsp_ext.rs | 15 --------------- crates/rust-analyzer/src/main_loop.rs | 1 - editors/code/src/client.ts | 2 +- editors/code/src/lsp_ext.ts | 5 ----- 7 files changed, 8 insertions(+), 65 deletions(-) diff --git a/crates/project-model/src/cfg_flag.rs b/crates/project-model/src/cfg_flag.rs index 2a4767970c0..c134b78ab3a 100644 --- a/crates/project-model/src/cfg_flag.rs +++ b/crates/project-model/src/cfg_flag.rs @@ -4,7 +4,6 @@ use std::{fmt, str::FromStr}; use cfg::CfgOptions; -use serde::Serialize; #[derive(Clone, Eq, PartialEq, Debug)] pub enum CfgFlag { @@ -39,18 +38,6 @@ fn deserialize(deserializer: D) -> Result } } -impl Serialize for CfgFlag { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self { - CfgFlag::Atom(s) => serializer.serialize_str(s), - CfgFlag::KeyValue { .. } => serializer.serialize_str(&format!("{}", &self)), - } - } -} - impl Extend for CfgOptions { fn extend>(&mut self, iter: T) { for cfg_flag in iter { diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs index 0f779e5307e..4b2448e47f1 100644 --- a/crates/project-model/src/project_json.rs +++ b/crates/project-model/src/project_json.rs @@ -54,7 +54,7 @@ use base_db::{CrateDisplayName, CrateId, CrateName, Dependency, Edition}; use paths::{AbsPath, AbsPathBuf}; use rustc_hash::FxHashMap; -use serde::{de, ser, Deserialize, Serialize}; +use serde::{de, Deserialize}; use crate::cfg_flag::CfgFlag; @@ -171,14 +171,14 @@ pub fn path(&self) -> &AbsPath { } } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone)] pub struct ProjectJsonData { sysroot: Option, sysroot_src: Option, crates: Vec, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone)] struct CrateData { display_name: Option, root_module: PathBuf, @@ -200,7 +200,7 @@ struct CrateData { repository: Option, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone)] #[serde(rename = "edition")] enum EditionData { #[serde(rename = "2015")] @@ -221,16 +221,16 @@ fn from(data: EditionData) -> Self { } } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone)] struct DepData { /// Identifies a crate by position in the crates array. #[serde(rename = "crate")] krate: usize, - #[serde(deserialize_with = "deserialize_crate_name", serialize_with = "serialize_crate_name")] + #[serde(deserialize_with = "deserialize_crate_name")] name: CrateName, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone)] struct CrateSource { include_dirs: Vec, exclude_dirs: Vec, @@ -243,10 +243,3 @@ fn deserialize_crate_name<'de, D>(de: D) -> Result let name = String::deserialize(de)?; CrateName::new(&name).map_err(|err| de::Error::custom(format!("invalid crate name: {err:?}"))) } - -fn serialize_crate_name(crate_name: &CrateName, serializer: S) -> Result -where - S: ser::Serializer, -{ - crate_name.serialize(serializer) -} diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 4ac76557df1..d7fc8891324 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -5,7 +5,6 @@ use std::{ io::Write as _, process::{self, Stdio}, - sync::Arc, }; use anyhow::Context; @@ -53,21 +52,6 @@ pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> Result< Ok(()) } -pub(crate) fn handle_add_project( - state: &mut GlobalState, - params: lsp_ext::AddProjectParams, -) -> Result<()> { - state.proc_macro_clients.clear(); - state.proc_macro_changed = false; - - let config = Arc::make_mut(&mut state.config); - config.add_linked_projects(params.project); - - state.fetch_workspaces_queue.request_op("linked projects changed".to_string()); - state.fetch_build_data_queue.request_op("linked projects changed".to_string()); - Ok(()) -} - pub(crate) fn handle_cancel_flycheck(state: &mut GlobalState, _: ()) -> Result<()> { let _p = profile::span("handle_stop_flycheck"); state.flycheck.iter().for_each(|flycheck| flycheck.cancel()); diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index e6caebe3537..c7b513db981 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -9,7 +9,6 @@ notification::Notification, CodeActionKind, DocumentOnTypeFormattingParams, PartialResultParams, Position, Range, TextDocumentIdentifier, WorkDoneProgressParams, }; -use project_model::ProjectJsonData; use serde::{Deserialize, Serialize}; use crate::line_index::PositionEncoding; @@ -52,20 +51,6 @@ impl Request for ReloadWorkspace { const METHOD: &'static str = "rust-analyzer/reloadWorkspace"; } -pub enum AddProject {} - -impl Request for AddProject { - type Params = AddProjectParams; - type Result = (); - const METHOD: &'static str = "rust-analyzer/addProject"; -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct AddProjectParams { - pub project: Vec, -} - pub enum SyntaxTree {} impl Request for SyntaxTree { diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 1cc771552a9..dd0804b4398 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -625,7 +625,6 @@ fn on_request(&mut self, req: Request) { .on_sync_mut::(handlers::handle_workspace_reload) .on_sync_mut::(handlers::handle_memory_usage) .on_sync_mut::(handlers::handle_shuffle_crate_graph) - .on_sync_mut::(handlers::handle_add_project) .on_sync::(handlers::handle_join_lines) .on_sync::(handlers::handle_on_enter) .on_sync::(handlers::handle_selection_range) diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 9103ef2f8fd..03f5d430519 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -3,7 +3,7 @@ import * as lc from "vscode-languageclient/node"; import * as vscode from "vscode"; import * as ra from "../src/lsp_ext"; import * as Is from "vscode-languageclient/lib/common/utils/is"; -import { assert, log } from "./util"; +import { assert } from "./util"; import * as diagnostics from "./diagnostics"; import { WorkspaceEdit } from "vscode"; import { Config, prepareVSCodeConfig } from "./config"; diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 942573c0f16..872d7199b83 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -43,9 +43,6 @@ export const relatedTests = new lc.RequestType("rust-analyzer/reloadWorkspace"); -export const addProject = new lc.RequestType( - "rust-analyzer/addProject" -); export const runFlycheck = new lc.NotificationType<{ textDocument: lc.TextDocumentIdentifier | null; @@ -72,8 +69,6 @@ export const viewItemTree = new lc.RequestType export type AnalyzerStatusParams = { textDocument?: lc.TextDocumentIdentifier }; -export type AddProjectParams = { project: JsonProject[] }; - export type ExpandMacroParams = { textDocument: lc.TextDocumentIdentifier; position: lc.Position; From cb93c12c3114429866329a93e67ec1c5e8f779ff Mon Sep 17 00:00:00 2001 From: David Barsky Date: Mon, 13 Mar 2023 13:33:39 -0400 Subject: [PATCH 09/12] fmt --- editors/code/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editors/code/package.json b/editors/code/package.json index 1e7e3b6cd5d..e2015a67e53 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -1920,4 +1920,4 @@ } ] } -} \ No newline at end of file +} From 78aed305e1980ccf70c2b3f1405db38521a11d54 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Mon, 13 Mar 2023 15:37:58 -0400 Subject: [PATCH 10/12] rename "addProject" command --- editors/code/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editors/code/package.json b/editors/code/package.json index e2015a67e53..c5eb08748bf 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -201,7 +201,7 @@ }, { "command": "rust-analyzer.addProject", - "title": "Add current file to workspace", + "title": "Add current file's crate to workspace", "category": "rust-analyzer" }, { From bd545a1c106484aa4960737c62450927e1bd8ac7 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Tue, 14 Mar 2023 12:49:35 -0400 Subject: [PATCH 11/12] Address review comments --- editors/code/src/client.ts | 4 +++- editors/code/src/config.ts | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 03f5d430519..9a576570beb 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -96,7 +96,9 @@ export async function createClient( if (resp && Array.isArray(resp)) { return resp.map((val) => { return prepareVSCodeConfig(val, (key, cfg) => { - cfg[key] = config.discoveredWorkspaces; + if (key === "linkedProjects") { + cfg[key] = config.discoveredWorkspaces; + } }); }); } else { diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 75403725479..da7c74c28ba 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -287,6 +287,14 @@ export class Config { } } +// the optional `cb?` parameter is meant to be used to add additional +// key/value pairs to the VS Code configuration. This needed for, e.g., +// including a `rust-project.json` into the `linkedProjects` key as part +// of the configuration/InitializationParams _without_ causing VS Code +// configuration to be written out to workspace-level settings. This is +// undesirable behavior because rust-project.json files can be tens of +// thousands of lines of JSON, most of which is not meant for humans +// to interact with. export function prepareVSCodeConfig( resp: T, cb?: (key: Extract, res: { [key: string]: any }) => void From 6e7bc07cdf225aea811f793c2f712f25846b8d20 Mon Sep 17 00:00:00 2001 From: David Barsky Date: Tue, 14 Mar 2023 13:36:21 -0400 Subject: [PATCH 12/12] fix: don't override `linkedProjects` if no workspace was discovered. --- editors/code/src/client.ts | 7 ++++++- editors/code/src/ctx.ts | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 9a576570beb..565cb9c6432 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -96,7 +96,12 @@ export async function createClient( if (resp && Array.isArray(resp)) { return resp.map((val) => { return prepareVSCodeConfig(val, (key, cfg) => { - if (key === "linkedProjects") { + // we only want to set discovered workspaces on the right key + // and if a workspace has been discovered. + if ( + key === "linkedProjects" && + config.discoveredWorkspaces.length > 0 + ) { cfg[key] = config.discoveredWorkspaces; } }); diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index ca30d239c95..ffcfb810a24 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -205,7 +205,9 @@ export class Ctx { const initializationOptions = prepareVSCodeConfig( rawInitializationOptions, (key, obj) => { - if (key === "linkedProjects") { + // we only want to set discovered workspaces on the right key + // and if a workspace has been discovered. + if (key === "linkedProjects" && this.config.discoveredWorkspaces.length > 0) { obj["linkedProjects"] = this.config.discoveredWorkspaces; } }