Auto merge of #17395 - davidbarsky:david/remove-unindexed-project-notification, r=Veykril

chore: remove `UnindexinedProject` notification

This PR is split out from https://github.com/rust-lang/rust-analyzer/pull/17246/ (and contains its changes, which is a little annoying from a review perspective...). I'd like to land this change a week or so after #17246 lands in order to give any users of the unindexed project notification time to adopt migrate.
This commit is contained in:
bors 2024-07-23 17:38:41 +00:00
commit eb5da56d83
15 changed files with 14 additions and 366 deletions

View file

@ -679,9 +679,6 @@ config_data! {
/// Whether to show `can't find Cargo.toml` error message. /// Whether to show `can't find Cargo.toml` error message.
notifications_cargoTomlNotFound: bool = true, notifications_cargoTomlNotFound: bool = true,
/// Whether to send an UnindexedProject notification to the client.
notifications_unindexedProject: bool = false,
/// How many worker threads in the main loop. The default `null` means to pick automatically. /// How many worker threads in the main loop. The default `null` means to pick automatically.
numThreads: Option<NumThreads> = None, numThreads: Option<NumThreads> = None,
@ -1236,7 +1233,6 @@ pub enum FilesWatcher {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct NotificationsConfig { pub struct NotificationsConfig {
pub cargo_toml_not_found: bool, pub cargo_toml_not_found: bool,
pub unindexed_project: bool,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -1800,7 +1796,6 @@ impl Config {
pub fn notifications(&self) -> NotificationsConfig { pub fn notifications(&self) -> NotificationsConfig {
NotificationsConfig { NotificationsConfig {
cargo_toml_not_found: self.notifications_cargoTomlNotFound().to_owned(), cargo_toml_not_found: self.notifications_cargoTomlNotFound().to_owned(),
unindexed_project: self.notifications_unindexedProject().to_owned(),
} }
} }

View file

@ -73,9 +73,7 @@ pub(crate) fn handle_did_open_text_document(
tracing::info!("New file content set {:?}", params.text_document.text); tracing::info!("New file content set {:?}", params.text_document.text);
state.vfs.write().0.set_file_contents(path, Some(params.text_document.text.into_bytes())); state.vfs.write().0.set_file_contents(path, Some(params.text_document.text.into_bytes()));
if state.config.discover_workspace_config().is_some() if state.config.discover_workspace_config().is_some() {
|| state.config.notifications().unindexed_project
{
tracing::debug!("queuing task"); tracing::debug!("queuing task");
let _ = state let _ = state
.deferred_task_queue .deferred_task_queue

View file

@ -834,16 +834,3 @@ pub struct CompletionImport {
pub struct ClientCommandOptions { pub struct ClientCommandOptions {
pub commands: Vec<String>, pub commands: Vec<String>,
} }
pub enum UnindexedProject {}
impl Notification for UnindexedProject {
type Params = UnindexedProjectParams;
const METHOD: &'static str = "rust-analyzer/unindexedProject";
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct UnindexedProjectParams {
pub text_documents: Vec<TextDocumentIdentifier>,
}

View file

@ -90,7 +90,6 @@ pub(crate) enum QueuedTask {
pub(crate) enum Task { pub(crate) enum Task {
Response(lsp_server::Response), Response(lsp_server::Response),
DiscoverLinkedProjects(DiscoverProjectParam), DiscoverLinkedProjects(DiscoverProjectParam),
ClientNotification(lsp_ext::UnindexedProjectParams),
Retry(lsp_server::Request), Retry(lsp_server::Request),
Diagnostics(DiagnosticsGeneration, Vec<(FileId, Vec<lsp_types::Diagnostic>)>), Diagnostics(DiagnosticsGeneration, Vec<(FileId, Vec<lsp_types::Diagnostic>)>),
DiscoverTest(lsp_ext::DiscoverTestResults), DiscoverTest(lsp_ext::DiscoverTestResults),
@ -642,9 +641,6 @@ impl GlobalState {
fn handle_task(&mut self, prime_caches_progress: &mut Vec<PrimeCachesProgress>, task: Task) { fn handle_task(&mut self, prime_caches_progress: &mut Vec<PrimeCachesProgress>, task: Task) {
match task { match task {
Task::Response(response) => self.respond(response), Task::Response(response) => self.respond(response),
Task::ClientNotification(params) => {
self.send_notification::<lsp_ext::UnindexedProject>(params)
}
// Only retry requests that haven't been cancelled. Otherwise we do unnecessary work. // Only retry requests that haven't been cancelled. Otherwise we do unnecessary work.
Task::Retry(req) if !self.is_completed(&req) => self.on_request(req), Task::Retry(req) if !self.is_completed(&req) => self.on_request(req),
Task::Retry(_) => (), Task::Retry(_) => (),
@ -825,12 +821,7 @@ impl GlobalState {
from_proto::abs_path(&uri).expect("Unable to get AbsPath"); from_proto::abs_path(&uri).expect("Unable to get AbsPath");
let arg = DiscoverProjectParam::Path(path); let arg = DiscoverProjectParam::Path(path);
sender.send(Task::DiscoverLinkedProjects(arg)).unwrap(); sender.send(Task::DiscoverLinkedProjects(arg)).unwrap();
} else if snap.config.notifications().unindexed_project { }
let params = lsp_ext::UnindexedProjectParams {
text_documents: vec![lsp_types::TextDocumentIdentifier { uri }],
};
sender.send(Task::ClientNotification(params)).unwrap();
};
} else { } else {
tracing::debug!(?uri, "is indexed"); tracing::debug!(?uri, "is indexed");
} }

View file

@ -27,7 +27,7 @@ use lsp_types::{
InlayHint, InlayHintLabel, InlayHintParams, PartialResultParams, Position, Range, InlayHint, InlayHintLabel, InlayHintParams, PartialResultParams, Position, Range,
RenameFilesParams, TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams, RenameFilesParams, TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams,
}; };
use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams, UnindexedProject}; use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams};
use serde_json::json; use serde_json::json;
use stdx::format_to_acc; use stdx::format_to_acc;
@ -811,66 +811,6 @@ fn main() {{}}
); );
} }
#[test]
fn test_opening_a_file_outside_of_indexed_workspace() {
if skip_slow_tests() {
return;
}
let tmp_dir = TestDir::new();
let path = tmp_dir.path();
let project = json!({
"roots": [path],
"crates": [ {
"root_module": path.join("src/crate_one/lib.rs"),
"deps": [],
"edition": "2015",
"cfg": [ "cfg_atom_1", "feature=\"cfg_1\""],
} ]
});
let code = format!(
r#"
//- /rust-project.json
{project}
//- /src/crate_one/lib.rs
mod bar;
fn main() {{}}
"#,
);
let server = Project::with_fixture(&code)
.tmp_dir(tmp_dir)
.with_config(serde_json::json!({
"notifications": {
"unindexedProject": true
},
}))
.server()
.wait_until_workspace_is_loaded();
let uri = server.doc_id("src/crate_two/lib.rs").uri;
server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: uri.clone(),
language_id: "rust".to_owned(),
version: 0,
text: "/// Docs\nfn foo() {}".to_owned(),
},
});
let expected = json!({
"textDocuments": [
{
"uri": uri
}
]
});
server.expect_notification::<UnindexedProject>(expected);
}
#[test] #[test]
fn diagnostics_dont_block_typing() { fn diagnostics_dont_block_typing() {
if skip_slow_tests() { if skip_slow_tests() {

View file

@ -256,40 +256,6 @@ impl Server {
self.send_notification(r) self.send_notification(r)
} }
pub(crate) fn expect_notification<N>(&self, expected: Value)
where
N: lsp_types::notification::Notification,
N::Params: Serialize,
{
while let Some(Message::Notification(actual)) =
recv_timeout(&self.client.receiver).unwrap_or_else(|_| panic!("timed out"))
{
if actual.method == N::METHOD {
let actual = actual
.clone()
.extract::<Value>(N::METHOD)
.expect("was not able to extract notification");
tracing::debug!(?actual, "got notification");
if let Some((expected_part, actual_part)) = find_mismatch(&expected, &actual) {
panic!(
"JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n",
to_string_pretty(&expected).unwrap(),
to_string_pretty(&actual).unwrap(),
to_string_pretty(expected_part).unwrap(),
to_string_pretty(actual_part).unwrap(),
);
} else {
tracing::debug!("successfully matched notification");
return;
}
} else {
continue;
}
}
panic!("never got expected notification");
}
#[track_caller] #[track_caller]
pub(crate) fn request<R>(&self, params: R::Params, expected_resp: Value) pub(crate) fn request<R>(&self, params: R::Params, expected_resp: Value)
where where

View file

@ -1,5 +1,5 @@
<!--- <!---
lsp/ext.rs hash: 278250dba58cd879 lsp/ext.rs hash: f41950db4c7b3a5a
If you need to change the above hash to make the test pass, please check if you If you need to change the above hash to make the test pass, please check if you
need to adjust this doc as well and ping this issue: need to adjust this doc as well and ping this issue:
@ -616,25 +616,6 @@ Reloads project information (that is, re-executes `cargo metadata`).
Rebuilds build scripts and proc-macros, and runs the build scripts to reseed the build data. Rebuilds build scripts and proc-macros, and runs the build scripts to reseed the build data.
## Unindexed Project
**Experimental Client Capability:** `{ "unindexedProject": boolean }`
**Method:** `rust-analyzer/unindexedProject`
**Notification:**
```typescript
interface UnindexedProjectParams {
/// A list of documents that rust-analyzer has determined are not indexed.
textDocuments: lc.TextDocumentIdentifier[]
}
```
This notification is sent from the server to the client. The client is expected
to determine the appropriate owners of `textDocuments` and update `linkedProjects`
if an owner can be determined successfully.
## Server Status ## Server Status
**Experimental Client Capability:** `{ "serverStatusNotification": boolean }` **Experimental Client Capability:** `{ "serverStatusNotification": boolean }`

View file

@ -830,11 +830,6 @@ Sets the LRU capacity of the specified queries.
-- --
Whether to show `can't find Cargo.toml` error message. Whether to show `can't find Cargo.toml` error message.
-- --
[[rust-analyzer.notifications.unindexedProject]]rust-analyzer.notifications.unindexedProject (default: `false`)::
+
--
Whether to send an UnindexedProject notification to the client.
--
[[rust-analyzer.numThreads]]rust-analyzer.numThreads (default: `null`):: [[rust-analyzer.numThreads]]rust-analyzer.numThreads (default: `null`)::
+ +
-- --

View file

@ -2267,16 +2267,6 @@
} }
} }
}, },
{
"title": "notifications",
"properties": {
"rust-analyzer.notifications.unindexedProject": {
"markdownDescription": "Whether to send an UnindexedProject notification to the client.",
"default": false,
"type": "boolean"
}
}
},
{ {
"title": "general", "title": "general",
"properties": { "properties": {

View file

@ -42,16 +42,7 @@ export async function createClient(
const resp = await next(params, token); const resp = await next(params, token);
if (resp && Array.isArray(resp)) { if (resp && Array.isArray(resp)) {
return resp.map((val) => { return resp.map((val) => {
return prepareVSCodeConfig(val, (key, cfg) => { return prepareVSCodeConfig(val);
// 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;
}
});
}); });
} else { } else {
return resp; return resp;

View file

@ -2,9 +2,9 @@ import * as Is from "vscode-languageclient/lib/common/utils/is";
import * as os from "os"; import * as os from "os";
import * as path from "path"; import * as path from "path";
import * as vscode from "vscode"; import * as vscode from "vscode";
import { type Env, log, unwrapUndefinable, expectNotUndefined } from "./util"; import { expectNotUndefined, log, unwrapUndefinable } from "./util";
import type { JsonProject } from "./rust_project"; import type { Env } from "./util";
import type { Disposable } from "./ctx"; import type { Disposable } from "vscode";
export type RunnableEnvCfgItem = { export type RunnableEnvCfgItem = {
mask?: string; mask?: string;
@ -31,7 +31,6 @@ export class Config {
); );
constructor(disposables: Disposable[]) { constructor(disposables: Disposable[]) {
this.discoveredWorkspaces = [];
vscode.workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, disposables); vscode.workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, disposables);
this.refreshLogging(); this.refreshLogging();
this.configureLanguage(); this.configureLanguage();
@ -52,8 +51,6 @@ export class Config {
log.info("Using configuration", Object.fromEntries(cfg)); log.info("Using configuration", Object.fromEntries(cfg));
} }
public discoveredWorkspaces: JsonProject[];
private async onDidChangeConfiguration(event: vscode.ConfigurationChangeEvent) { private async onDidChangeConfiguration(event: vscode.ConfigurationChangeEvent) {
this.refreshLogging(); this.refreshLogging();
@ -342,18 +339,7 @@ export class Config {
} }
} }
// the optional `cb?` parameter is meant to be used to add additional export function prepareVSCodeConfig<T>(resp: T): T {
// 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<T>(
resp: T,
cb?: (key: Extract<keyof T, string>, res: { [key: string]: any }) => void,
): T {
if (Is.string(resp)) { if (Is.string(resp)) {
return substituteVSCodeVariableInString(resp) as T; return substituteVSCodeVariableInString(resp) as T;
} else if (resp && Is.array<any>(resp)) { } else if (resp && Is.array<any>(resp)) {
@ -365,9 +351,6 @@ export function prepareVSCodeConfig<T>(
for (const key in resp) { for (const key in resp) {
const val = resp[key]; const val = resp[key];
res[key] = prepareVSCodeConfig(val); res[key] = prepareVSCodeConfig(val);
if (cb) {
cb(key, res);
}
} }
return res as T; return res as T;
} }

View file

@ -1,5 +1,5 @@
import * as vscode from "vscode"; import * as vscode from "vscode";
import * as lc from "vscode-languageclient/node"; import type * as lc from "vscode-languageclient/node";
import * as ra from "./lsp_ext"; import * as ra from "./lsp_ext";
import { Config, prepareVSCodeConfig } from "./config"; import { Config, prepareVSCodeConfig } from "./config";
@ -22,11 +22,10 @@ import {
import { execRevealDependency } from "./commands"; import { execRevealDependency } from "./commands";
import { PersistentState } from "./persistent_state"; import { PersistentState } from "./persistent_state";
import { bootstrap } from "./bootstrap"; import { bootstrap } from "./bootstrap";
import type { RustAnalyzerExtensionApi } from "./main";
import type { JsonProject } from "./rust_project";
import { prepareTestExplorer } from "./test_explorer"; import { prepareTestExplorer } from "./test_explorer";
import { spawn } from "node:child_process"; import { spawn } from "node:child_process";
import { text } from "node:stream/consumers"; import { text } from "node:stream/consumers";
import type { RustAnalyzerExtensionApi } from "./main";
// We only support local folders, not eg. Live Share (`vlsl:` scheme), so don't activate if // 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 // only those are in use. We use "Empty" to represent these scenarios
@ -71,7 +70,7 @@ export type CtxInit = Ctx & {
export class Ctx implements RustAnalyzerExtensionApi { export class Ctx implements RustAnalyzerExtensionApi {
readonly statusBar: vscode.StatusBarItem; readonly statusBar: vscode.StatusBarItem;
config: Config; readonly config: Config;
readonly workspace: Workspace; readonly workspace: Workspace;
readonly version: string; readonly version: string;
@ -212,15 +211,6 @@ export class Ctx implements RustAnalyzerExtensionApi {
}; };
let rawInitializationOptions = vscode.workspace.getConfiguration("rust-analyzer"); let rawInitializationOptions = vscode.workspace.getConfiguration("rust-analyzer");
if (this.config.discoverProjectRunner) {
const command = `${this.config.discoverProjectRunner}.discoverWorkspaceCommand`;
log.info(`running command: ${command}`);
const uris = vscode.workspace.textDocuments
.filter(isRustDocument)
.map((document) => document.uri);
const projects: JsonProject[] = await vscode.commands.executeCommand(command, uris);
this.setWorkspaces(projects);
}
if (this.workspace.kind === "Detached Files") { if (this.workspace.kind === "Detached Files") {
rawInitializationOptions = { rawInitializationOptions = {
@ -229,16 +219,7 @@ export class Ctx implements RustAnalyzerExtensionApi {
}; };
} }
const initializationOptions = prepareVSCodeConfig( const initializationOptions = prepareVSCodeConfig(rawInitializationOptions);
rawInitializationOptions,
(key, obj) => {
// 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;
}
},
);
this._client = await createClient( this._client = await createClient(
this.traceOutputChannel, this.traceOutputChannel,
@ -258,23 +239,6 @@ export class Ctx implements RustAnalyzerExtensionApi {
this.outputChannel!.show(); this.outputChannel!.show();
}), }),
); );
this.pushClientCleanup(
this._client.onNotification(ra.unindexedProject, async (params) => {
if (this.config.discoverProjectRunner) {
const command = `${this.config.discoverProjectRunner}.discoverWorkspaceCommand`;
log.info(`running command: ${command}`);
const uris = params.textDocuments.map((doc) =>
vscode.Uri.parse(doc.uri, true),
);
const projects: JsonProject[] = await vscode.commands.executeCommand(
command,
uris,
);
this.setWorkspaces(projects);
await this.notifyRustAnalyzer();
}
}),
);
} }
return this._client; return this._client;
} }
@ -400,19 +364,6 @@ export class Ctx implements RustAnalyzerExtensionApi {
return this.extCtx.subscriptions; return this.extCtx.subscriptions;
} }
setWorkspaces(workspaces: JsonProject[]) {
this.config.discoveredWorkspaces = workspaces;
}
async notifyRustAnalyzer(): Promise<void> {
// 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 this.client?.sendNotification(lc.DidChangeConfigurationNotification.type, {
settings: "",
});
}
private updateCommands(forceDisable?: "disable") { private updateCommands(forceDisable?: "disable") {
this.commandDisposables.forEach((disposable) => disposable.dispose()); this.commandDisposables.forEach((disposable) => disposable.dispose());
this.commandDisposables = []; this.commandDisposables = [];

View file

@ -303,9 +303,3 @@ export type RecursiveMemoryLayoutNode = {
export type RecursiveMemoryLayout = { export type RecursiveMemoryLayout = {
nodes: RecursiveMemoryLayoutNode[]; nodes: RecursiveMemoryLayoutNode[];
}; };
export const unindexedProject = new lc.NotificationType<UnindexedProjectParams>(
"rust-analyzer/unindexedProject",
);
export type UnindexedProjectParams = { textDocuments: lc.TextDocumentIdentifier[] };

View file

@ -6,16 +6,12 @@ import { type CommandFactory, Ctx, fetchWorkspace } from "./ctx";
import * as diagnostics from "./diagnostics"; import * as diagnostics from "./diagnostics";
import { activateTaskProvider } from "./tasks"; import { activateTaskProvider } from "./tasks";
import { setContextValue } from "./util"; import { setContextValue } from "./util";
import type { JsonProject } from "./rust_project";
const RUST_PROJECT_CONTEXT_NAME = "inRustProject"; const RUST_PROJECT_CONTEXT_NAME = "inRustProject";
// This API is not stable and may break in between minor releases.
export interface RustAnalyzerExtensionApi { export interface RustAnalyzerExtensionApi {
// FIXME: this should be non-optional
readonly client?: lc.LanguageClient; readonly client?: lc.LanguageClient;
setWorkspaces(workspaces: JsonProject[]): void;
notifyRustAnalyzer(): Promise<void>;
} }
export async function deactivate() { export async function deactivate() {

View file

@ -1,110 +0,0 @@
export interface JsonProject {
/// Path to the sysroot directory.
///
/// The sysroot is where rustc looks for the
/// crates that are built-in to rust, such as
/// std.
///
/// https://doc.rust-lang.org/rustc/command-line-arguments.html#--sysroot-override-the-system-root
///
/// To see the current value of sysroot, you
/// can query rustc:
///
/// ```
/// $ rustc --print sysroot
/// /Users/yourname/.rustup/toolchains/stable-x86_64-apple-darwin
/// ```
sysroot?: string;
/// Path to the directory with *source code* of
/// sysroot crates.
///
/// By default, this is `lib/rustlib/src/rust/library`
/// relative to the sysroot.
///
/// 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[];
}
export 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;
}
export 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;
}