Skip to content

Commit

Permalink
Add a UnindexedProject notification and a corresponding setting.
Browse files Browse the repository at this point in the history
This commit also adds a slow test for this functionality.
  • Loading branch information
davidbarsky committed Nov 20, 2023
1 parent 255eed4 commit 4391d0a
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 7 deletions.
9 changes: 8 additions & 1 deletion crates/rust-analyzer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,9 @@ config_data! {
/// Whether to show `can't find Cargo.toml` error message.
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.
numThreads: Option<usize> = "null",

Expand Down Expand Up @@ -719,6 +722,7 @@ pub enum FilesWatcher {
#[derive(Debug, Clone)]
pub struct NotificationsConfig {
pub cargo_toml_not_found: bool,
pub unindexed_project: bool,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -1191,7 +1195,10 @@ impl Config {
}

pub fn notifications(&self) -> NotificationsConfig {
NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound }
NotificationsConfig {
cargo_toml_not_found: self.data.notifications_cargoTomlNotFound,
unindexed_project: self.data.notifications_unindexedProject,
}
}

pub fn cargo_autoreload(&self) -> bool {
Expand Down
4 changes: 2 additions & 2 deletions crates/rust-analyzer/src/global_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub(crate) struct GlobalState {
pub(crate) last_flycheck_error: Option<String>,

// VFS
pub(crate) loader: Handle<Box<dyn vfs::loader::Handle>, Receiver<vfs::loader::Message>>,
pub(crate) loader: Handle<Box<dyn vfs::loader::Handle + Send>, Receiver<vfs::loader::Message>>,
pub(crate) vfs: Arc<RwLock<(vfs::Vfs, IntMap<FileId, LineEndings>)>>,
pub(crate) vfs_config_version: u32,
pub(crate) vfs_progress_config_version: u32,
Expand Down Expand Up @@ -149,7 +149,7 @@ impl GlobalState {
let (sender, receiver) = unbounded::<vfs::loader::Message>();
let handle: vfs_notify::NotifyHandle =
vfs::loader::Handle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
let handle = Box::new(handle) as Box<dyn vfs::loader::Handle>;
let handle = Box::new(handle) as Box<dyn vfs::loader::Handle + Send>;
Handle { handle, receiver }
};

Expand Down
13 changes: 12 additions & 1 deletion crates/rust-analyzer/src/handlers/notification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use lsp_types::{
DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
DidOpenTextDocumentParams, DidSaveTextDocumentParams, WorkDoneProgressCancelParams,
};
use stdx::thread::ThreadIntent;
use triomphe::Arc;
use vfs::{AbsPathBuf, ChangeKind, VfsPath};

Expand All @@ -17,6 +18,7 @@ use crate::{
global_state::GlobalState,
lsp::{from_proto, utils::apply_document_changes},
lsp_ext::RunFlycheckParams,
main_loop::Task,
mem_docs::DocumentData,
reload,
};
Expand Down Expand Up @@ -64,7 +66,16 @@ pub(crate) fn handle_did_open_text_document(
if already_exists {
tracing::error!("duplicate DidOpenTextDocument: {}", path);
}
state.vfs.write().0.set_file_contents(path, Some(params.text_document.text.into_bytes()));

let vfs = &mut state.vfs.write().0;
vfs.set_file_contents(path, Some(params.text_document.text.into_bytes()));

if state.config.notifications().unindexed_project {
state.task_pool.handle.spawn(ThreadIntent::Worker, || {
tracing::debug!("dispatching task");
Task::FileIndexState(params.text_document.uri)
});
}
}
Ok(())
}
Expand Down
13 changes: 13 additions & 0 deletions crates/rust-analyzer/src/lsp/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,3 +696,16 @@ pub struct CompletionImport {
pub struct ClientCommandOptions {
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>,
}
25 changes: 24 additions & 1 deletion crates/rust-analyzer/src/main_loop.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! The main loop of `rust-analyzer` responsible for dispatching LSP
//! requests/replies and notifications back to the client.
use crate::lsp::ext;
use std::{
fmt,
time::{Duration, Instant},
Expand Down Expand Up @@ -65,6 +66,7 @@ pub(crate) enum Task {
Response(lsp_server::Response),
Retry(lsp_server::Request),
Diagnostics(Vec<(FileId, Vec<lsp_types::Diagnostic>)>),
FileIndexState(lsp_types::Url),
PrimeCaches(PrimeCachesProgress),
FetchWorkspace(ProjectWorkspaceProgress),
FetchBuildData(BuildDataProgress),
Expand Down Expand Up @@ -199,7 +201,7 @@ impl GlobalState {
let _p = profile::span("GlobalState::handle_event");

let event_dbg_msg = format!("{event:?}");
tracing::debug!("{:?} handle_event({})", loop_start, event_dbg_msg);
tracing::debug!(?loop_start, ?event, "handle_event");
if tracing::enabled!(tracing::Level::INFO) {
let task_queue_len = self.task_pool.handle.len();
if task_queue_len > 0 {
Expand Down Expand Up @@ -486,6 +488,27 @@ impl GlobalState {
// 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(_) => (),
Task::FileIndexState(uri) => {
let _p = profile::span("run_unindexed_project");

tracing::debug!(changes = self.process_changes(), "processes changes");
let snap = self.snapshot();
let id =
crate::lsp::from_proto::file_id(&snap, &uri).expect("Unable to get file ID");

if let Ok(crates) = &snap.analysis.crates_for(id) {
if crates.is_empty() {
tracing::debug!(?uri, "rust-analyzer does not track this file");
self.send_notification::<ext::UnindexedProject>(
ext::UnindexedProjectParams {
text_documents: vec![lsp_types::TextDocumentIdentifier { uri }],
},
);
} else {
tracing::warn!("was unable to get analysis for crate")
}
}
}
Task::Diagnostics(diagnostics_per_file) => {
for (file_id, diagnostics) in diagnostics_per_file {
self.diagnostics.set_native_diagnostics(file_id, diagnostics)
Expand Down
62 changes: 61 additions & 1 deletion crates/rust-analyzer/tests/slow-tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use lsp_types::{
PartialResultParams, Position, Range, RenameFilesParams, TextDocumentItem,
TextDocumentPositionParams, WorkDoneProgressParams,
};
use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams};
use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams, UnindexedProject};
use serde_json::json;
use test_utils::skip_slow_tests;

Expand Down Expand Up @@ -588,6 +588,66 @@ 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(&format!("src/crate_two/lib.rs")).uri;
server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: uri.clone(),
language_id: "rust".to_string(),
version: 0,
text: "/// Docs\nfn foo() {}".to_string(),
},
});
let expected = json!({
"textDocuments": [
{
"uri": uri
}
]
});
server.expect_notification::<UnindexedProject>(expected);
}

#[test]
fn diagnostics_dont_block_typing() {
if skip_slow_tests() {
Expand Down
32 changes: 32 additions & 0 deletions crates/rust-analyzer/tests/slow-tests/support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,38 @@ impl Server {
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");

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 {
return;
}
} else {
continue;
}
}
panic!("never got expected notification");
}

#[track_caller]
pub(crate) fn request<R>(&self, params: R::Params, expected_resp: Value)
where
Expand Down
21 changes: 20 additions & 1 deletion docs/dev/lsp-extensions.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!---
lsp/ext.rs hash: 121482ee911854da
lsp/ext.rs hash: 21cb0f2f547891a2
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:
Expand Down Expand Up @@ -445,6 +445,25 @@ 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.

## 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

**Experimental Client Capability:** `{ "serverStatusNotification": boolean }`
Expand Down
5 changes: 5 additions & 0 deletions docs/user/generated_config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,11 @@ Sets the LRU capacity of the specified queries.
--
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`)::
+
--
Expand Down
5 changes: 5 additions & 0 deletions editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1441,6 +1441,11 @@
"default": true,
"type": "boolean"
},
"rust-analyzer.notifications.unindexedProject": {
"markdownDescription": "Whether to send an UnindexedProject notification to the client.",
"default": false,
"type": "boolean"
},
"rust-analyzer.numThreads": {
"markdownDescription": "How many worker threads in the main loop. The default `null` means to pick automatically.",
"default": null,
Expand Down
13 changes: 13 additions & 0 deletions editors/code/src/ctx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,19 @@ export class Ctx implements RustAnalyzerExtensionApi {
this.outputChannel!.show();
}),
);
this.pushClientCleanup(
this._client.onNotification(ra.unindexedProject, async (params) => {
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;
}
Expand Down
6 changes: 6 additions & 0 deletions editors/code/src/lsp_ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,9 @@ export type RecursiveMemoryLayoutNode = {
export type RecursiveMemoryLayout = {
nodes: RecursiveMemoryLayoutNode[];
};

export const unindexedProject = new lc.NotificationType<UnindexedProjectParams>(
"rust-analyzer/unindexedProject",
);

export type UnindexedProjectParams = { textDocuments: lc.TextDocumentIdentifier[] };

0 comments on commit 4391d0a

Please sign in to comment.