Skip to content

Commit 89057db

Browse files
committed
Add a UnindexedProject notification and a corresponding setting.
1 parent 311a5e9 commit 89057db

File tree

13 files changed

+245
-9
lines changed

13 files changed

+245
-9
lines changed

crates/rust-analyzer/src/config.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,9 @@ config_data! {
461461
/// Whether to show `can't find Cargo.toml` error message.
462462
notifications_cargoTomlNotFound: bool = "true",
463463

464+
/// Whether to send an UnindexedProject notification to the client.
465+
notifications_unindexedProject: bool = "false",
466+
464467
/// How many worker threads in the main loop. The default `null` means to pick automatically.
465468
numThreads: Option<usize> = "null",
466469

@@ -721,6 +724,7 @@ pub enum FilesWatcher {
721724
#[derive(Debug, Clone)]
722725
pub struct NotificationsConfig {
723726
pub cargo_toml_not_found: bool,
727+
pub unindexed_project: bool,
724728
}
725729

726730
#[derive(Debug, Clone)]
@@ -1193,7 +1197,10 @@ impl Config {
11931197
}
11941198

11951199
pub fn notifications(&self) -> NotificationsConfig {
1196-
NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound }
1200+
NotificationsConfig {
1201+
cargo_toml_not_found: self.data.notifications_cargoTomlNotFound,
1202+
unindexed_project: self.data.notifications_unindexedProject,
1203+
}
11971204
}
11981205

11991206
pub fn cargo_autoreload(&self) -> bool {

crates/rust-analyzer/src/global_state.rs

+16-5
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use crate::{
3232
mem_docs::MemDocs,
3333
op_queue::OpQueue,
3434
reload,
35-
task_pool::TaskPool,
35+
task_pool::{TaskPool, TaskQueue},
3636
};
3737

3838
// Enforces drop order
@@ -53,7 +53,7 @@ type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>;
5353
/// Note that this struct has more than one impl in various modules!
5454
#[doc(alias = "GlobalMess")]
5555
pub(crate) struct GlobalState {
56-
sender: Sender<lsp_server::Message>,
56+
pub(crate) sender: Sender<lsp_server::Message>,
5757
req_queue: ReqQueue,
5858

5959
pub(crate) task_pool: Handle<TaskPool<Task>, Receiver<Task>>,
@@ -83,7 +83,7 @@ pub(crate) struct GlobalState {
8383
pub(crate) last_flycheck_error: Option<String>,
8484

8585
// VFS
86-
pub(crate) loader: Handle<Box<dyn vfs::loader::Handle>, Receiver<vfs::loader::Message>>,
86+
pub(crate) loader: Handle<Box<dyn vfs::loader::Handle + Send>, Receiver<vfs::loader::Message>>,
8787
pub(crate) vfs: Arc<RwLock<(vfs::Vfs, IntMap<FileId, LineEndings>)>>,
8888
pub(crate) vfs_config_version: u32,
8989
pub(crate) vfs_progress_config_version: u32,
@@ -125,6 +125,10 @@ pub(crate) struct GlobalState {
125125
OpQueue<(), (Arc<Vec<ProjectWorkspace>>, Vec<anyhow::Result<WorkspaceBuildScripts>>)>,
126126
pub(crate) fetch_proc_macros_queue: OpQueue<Vec<ProcMacroPaths>, bool>,
127127
pub(crate) prime_caches_queue: OpQueue,
128+
129+
/// a deferred task queue. this should only be used if the enqueued Task
130+
/// can only run *after* [`GlobalState::process_changes`] has been called.
131+
pub(crate) task_queue: TaskQueue,
128132
}
129133

130134
/// An immutable snapshot of the world's state at a point in time.
@@ -149,7 +153,7 @@ impl GlobalState {
149153
let (sender, receiver) = unbounded::<vfs::loader::Message>();
150154
let handle: vfs_notify::NotifyHandle =
151155
vfs::loader::Handle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
152-
let handle = Box::new(handle) as Box<dyn vfs::loader::Handle>;
156+
let handle = Box::new(handle) as Box<dyn vfs::loader::Handle + Send>;
153157
Handle { handle, receiver }
154158
};
155159

@@ -164,6 +168,11 @@ impl GlobalState {
164168
Handle { handle, receiver }
165169
};
166170

171+
let task_queue = {
172+
let (sender, receiver) = unbounded();
173+
TaskQueue { sender, receiver }
174+
};
175+
167176
let mut analysis_host = AnalysisHost::new(config.lru_parse_query_capacity());
168177
if let Some(capacities) = config.lru_query_capacities() {
169178
analysis_host.update_lru_capacities(capacities);
@@ -207,6 +216,8 @@ impl GlobalState {
207216
fetch_proc_macros_queue: OpQueue::default(),
208217

209218
prime_caches_queue: OpQueue::default(),
219+
220+
task_queue,
210221
};
211222
// Apply any required database inputs from the config.
212223
this.update_configuration(config);
@@ -429,7 +440,7 @@ impl GlobalState {
429440
self.req_queue.incoming.is_completed(&request.id)
430441
}
431442

432-
fn send(&self, message: lsp_server::Message) {
443+
pub(crate) fn send(&self, message: lsp_server::Message) {
433444
self.sender.send(message).unwrap()
434445
}
435446
}

crates/rust-analyzer/src/handlers/notification.rs

+8
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,15 @@ pub(crate) fn handle_did_open_text_document(
6464
if already_exists {
6565
tracing::error!("duplicate DidOpenTextDocument: {}", path);
6666
}
67+
6768
state.vfs.write().0.set_file_contents(path, Some(params.text_document.text.into_bytes()));
69+
if state.config.notifications().unindexed_project {
70+
tracing::debug!("queuing task");
71+
let _ = state
72+
.task_queue
73+
.sender
74+
.send(crate::main_loop::QueuedTask::CheckIfIndexed(params.text_document.uri));
75+
}
6876
}
6977
Ok(())
7078
}

crates/rust-analyzer/src/lsp/ext.rs

+13
Original file line numberDiff line numberDiff line change
@@ -696,3 +696,16 @@ pub struct CompletionImport {
696696
pub struct ClientCommandOptions {
697697
pub commands: Vec<String>,
698698
}
699+
700+
pub enum UnindexedProject {}
701+
702+
impl Notification for UnindexedProject {
703+
type Params = UnindexedProjectParams;
704+
const METHOD: &'static str = "rust-analyzer/unindexedProject";
705+
}
706+
707+
#[derive(Deserialize, Serialize, Debug)]
708+
#[serde(rename_all = "camelCase")]
709+
pub struct UnindexedProjectParams {
710+
pub text_documents: Vec<TextDocumentIdentifier>,
711+
}

crates/rust-analyzer/src/main_loop.rs

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! The main loop of `rust-analyzer` responsible for dispatching LSP
22
//! requests/replies and notifications back to the client.
3+
use crate::lsp::ext;
34
use std::{
45
fmt,
56
time::{Duration, Instant},
@@ -56,10 +57,16 @@ pub fn main_loop(config: Config, connection: Connection) -> anyhow::Result<()> {
5657
enum Event {
5758
Lsp(lsp_server::Message),
5859
Task(Task),
60+
QueuedTask(QueuedTask),
5961
Vfs(vfs::loader::Message),
6062
Flycheck(flycheck::Message),
6163
}
6264

65+
#[derive(Debug)]
66+
pub(crate) enum QueuedTask {
67+
CheckIfIndexed(lsp_types::Url),
68+
}
69+
6370
#[derive(Debug)]
6471
pub(crate) enum Task {
6572
Response(lsp_server::Response),
@@ -104,6 +111,7 @@ impl fmt::Debug for Event {
104111
match self {
105112
Event::Lsp(it) => fmt::Debug::fmt(it, f),
106113
Event::Task(it) => fmt::Debug::fmt(it, f),
114+
Event::QueuedTask(it) => fmt::Debug::fmt(it, f),
107115
Event::Vfs(it) => fmt::Debug::fmt(it, f),
108116
Event::Flycheck(it) => fmt::Debug::fmt(it, f),
109117
}
@@ -182,6 +190,9 @@ impl GlobalState {
182190
recv(self.task_pool.receiver) -> task =>
183191
Some(Event::Task(task.unwrap())),
184192

193+
recv(self.task_queue.receiver) -> task =>
194+
Some(Event::QueuedTask(task.unwrap())),
195+
185196
recv(self.fmt_pool.receiver) -> task =>
186197
Some(Event::Task(task.unwrap())),
187198

@@ -199,7 +210,7 @@ impl GlobalState {
199210
let _p = profile::span("GlobalState::handle_event");
200211

201212
let event_dbg_msg = format!("{event:?}");
202-
tracing::debug!("{:?} handle_event({})", loop_start, event_dbg_msg);
213+
tracing::debug!(?loop_start, ?event, "handle_event");
203214
if tracing::enabled!(tracing::Level::INFO) {
204215
let task_queue_len = self.task_pool.handle.len();
205216
if task_queue_len > 0 {
@@ -214,6 +225,10 @@ impl GlobalState {
214225
lsp_server::Message::Notification(not) => self.on_notification(not)?,
215226
lsp_server::Message::Response(resp) => self.complete_request(resp),
216227
},
228+
Event::QueuedTask(task) => {
229+
let _p = profile::span("GlobalState::handle_event/queued_task");
230+
self.handle_queued_task(task);
231+
}
217232
Event::Task(task) => {
218233
let _p = profile::span("GlobalState::handle_event/task");
219234
let mut prime_caches_progress = Vec::new();
@@ -607,6 +622,35 @@ impl GlobalState {
607622
}
608623
}
609624

625+
fn handle_queued_task(&mut self, task: QueuedTask) {
626+
match task {
627+
QueuedTask::CheckIfIndexed(uri) => {
628+
let snap = self.snapshot();
629+
630+
let sender = self.sender.clone();
631+
632+
self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |_| {
633+
tracing::debug!(?uri, "handling uri");
634+
let id = from_proto::file_id(&snap, &uri).expect("unable to get FileId");
635+
if let Ok(crates) = &snap.analysis.crates_for(id) {
636+
if crates.is_empty() {
637+
let not = lsp_server::Notification::new(
638+
ext::UnindexedProject::METHOD.to_string(),
639+
ext::UnindexedProjectParams {
640+
text_documents: vec![lsp_types::TextDocumentIdentifier { uri }],
641+
},
642+
);
643+
sender.send(not.into()).expect("unable to send notification");
644+
tracing::debug!("sent notification");
645+
} else {
646+
tracing::debug!(?uri, "is indexed");
647+
}
648+
}
649+
});
650+
}
651+
}
652+
}
653+
610654
fn handle_flycheck_msg(&mut self, message: flycheck::Message) {
611655
match message {
612656
flycheck::Message::AddDiagnostic { id, workspace_root, diagnostic } => {

crates/rust-analyzer/src/task_pool.rs

+11
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
use crossbeam_channel::Sender;
55
use stdx::thread::{Pool, ThreadIntent};
66

7+
use crate::main_loop::QueuedTask;
8+
79
pub(crate) struct TaskPool<T> {
810
sender: Sender<T>,
911
pool: Pool,
@@ -40,3 +42,12 @@ impl<T> TaskPool<T> {
4042
self.pool.len()
4143
}
4244
}
45+
46+
/// `TaskQueue`, like its name suggests, queues tasks.
47+
///
48+
/// This should only be used used if a task must run after [`GlobalState::process_changes`]
49+
/// has been called.
50+
pub(crate) struct TaskQueue {
51+
pub(crate) sender: crossbeam_channel::Sender<QueuedTask>,
52+
pub(crate) receiver: crossbeam_channel::Receiver<QueuedTask>,
53+
}

crates/rust-analyzer/tests/slow-tests/main.rs

+61-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use lsp_types::{
2929
PartialResultParams, Position, Range, RenameFilesParams, TextDocumentItem,
3030
TextDocumentPositionParams, WorkDoneProgressParams,
3131
};
32-
use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams};
32+
use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams, UnindexedProject};
3333
use serde_json::json;
3434
use test_utils::skip_slow_tests;
3535

@@ -588,6 +588,66 @@ fn main() {{}}
588588
);
589589
}
590590

591+
#[test]
592+
fn test_opening_a_file_outside_of_indexed_workspace() {
593+
if skip_slow_tests() {
594+
return;
595+
}
596+
597+
let tmp_dir = TestDir::new();
598+
let path = tmp_dir.path();
599+
600+
let project = json!({
601+
"roots": [path],
602+
"crates": [ {
603+
"root_module": path.join("src/crate_one/lib.rs"),
604+
"deps": [],
605+
"edition": "2015",
606+
"cfg": [ "cfg_atom_1", "feature=\"cfg_1\""],
607+
} ]
608+
});
609+
610+
let code = format!(
611+
r#"
612+
//- /rust-project.json
613+
{project}
614+
615+
//- /src/crate_one/lib.rs
616+
mod bar;
617+
618+
fn main() {{}}
619+
"#,
620+
);
621+
622+
let server = Project::with_fixture(&code)
623+
.tmp_dir(tmp_dir)
624+
.with_config(serde_json::json!({
625+
"notifications": {
626+
"unindexedProject": true
627+
},
628+
}))
629+
.server()
630+
.wait_until_workspace_is_loaded();
631+
632+
let uri = server.doc_id(&format!("src/crate_two/lib.rs")).uri;
633+
server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
634+
text_document: TextDocumentItem {
635+
uri: uri.clone(),
636+
language_id: "rust".to_string(),
637+
version: 0,
638+
text: "/// Docs\nfn foo() {}".to_string(),
639+
},
640+
});
641+
let expected = json!({
642+
"textDocuments": [
643+
{
644+
"uri": uri
645+
}
646+
]
647+
});
648+
server.expect_notification::<UnindexedProject>(expected);
649+
}
650+
591651
#[test]
592652
fn diagnostics_dont_block_typing() {
593653
if skip_slow_tests() {

crates/rust-analyzer/tests/slow-tests/support.rs

+34
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,40 @@ impl Server {
198198
self.send_notification(r)
199199
}
200200

201+
pub(crate) fn expect_notification<N>(&self, expected: Value)
202+
where
203+
N: lsp_types::notification::Notification,
204+
N::Params: Serialize,
205+
{
206+
while let Some(Message::Notification(actual)) =
207+
recv_timeout(&self.client.receiver).unwrap_or_else(|_| panic!("timed out"))
208+
{
209+
if actual.method == N::METHOD {
210+
let actual = actual
211+
.clone()
212+
.extract::<Value>(N::METHOD)
213+
.expect("was not able to extract notification");
214+
215+
tracing::debug!(?actual, "got notification");
216+
if let Some((expected_part, actual_part)) = find_mismatch(&expected, &actual) {
217+
panic!(
218+
"JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n",
219+
to_string_pretty(&expected).unwrap(),
220+
to_string_pretty(&actual).unwrap(),
221+
to_string_pretty(expected_part).unwrap(),
222+
to_string_pretty(actual_part).unwrap(),
223+
);
224+
} else {
225+
tracing::debug!("sucessfully matched notification");
226+
return;
227+
}
228+
} else {
229+
continue;
230+
}
231+
}
232+
panic!("never got expected notification");
233+
}
234+
201235
#[track_caller]
202236
pub(crate) fn request<R>(&self, params: R::Params, expected_resp: Value)
203237
where

0 commit comments

Comments
 (0)