Skip to content

Commit 8a4bf40

Browse files
authored
Merge pull request #881 from posit-dev/bugfix/index-clear
Clear index on `did_close`
2 parents 6341e01 + d854fbb commit 8a4bf40

File tree

4 files changed

+300
-51
lines changed

4 files changed

+300
-51
lines changed

crates/ark/src/lsp/backend.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ pub(crate) enum LspNotification {
125125
DidChangeTextDocument(DidChangeTextDocumentParams),
126126
DidSaveTextDocument(DidSaveTextDocumentParams),
127127
DidCloseTextDocument(DidCloseTextDocumentParams),
128+
DidCreateFiles(CreateFilesParams),
129+
DidDeleteFiles(DeleteFilesParams),
130+
DidRenameFiles(RenameFilesParams),
128131
}
129132

130133
#[derive(Debug)]
@@ -244,6 +247,18 @@ impl LanguageServer for Backend {
244247
self.notify(LspNotification::DidChangeWatchedFiles(params));
245248
}
246249

250+
async fn did_create_files(&self, params: CreateFilesParams) {
251+
self.notify(LspNotification::DidCreateFiles(params));
252+
}
253+
254+
async fn did_delete_files(&self, params: DeleteFilesParams) {
255+
self.notify(LspNotification::DidDeleteFiles(params));
256+
}
257+
258+
async fn did_rename_files(&self, params: RenameFilesParams) {
259+
self.notify(LspNotification::DidRenameFiles(params));
260+
}
261+
247262
async fn symbol(
248263
&self,
249264
params: WorkspaceSymbolParams,

crates/ark/src/lsp/indexer.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub fn start(folders: Vec<String>) {
7777
for entry in walker.into_iter().filter_entry(|e| filter_entry(e)) {
7878
if let Ok(entry) = entry {
7979
if entry.file_type().is_file() {
80-
if let Err(err) = index_file(entry.path()) {
80+
if let Err(err) = create(entry.path()) {
8181
lsp::log_error!("Can't index file {:?}: {err:?}", entry.path());
8282
}
8383
}
@@ -116,7 +116,7 @@ pub fn map(mut callback: impl FnMut(&Path, &String, &IndexEntry)) {
116116

117117
#[tracing::instrument(level = "trace", skip_all, fields(path = ?path))]
118118
pub fn update(document: &Document, path: &Path) -> anyhow::Result<()> {
119-
clear(path)?;
119+
delete(path)?;
120120
index_document(document, path);
121121
Ok(())
122122
}
@@ -147,7 +147,8 @@ fn index_insert(index: &mut HashMap<String, IndexEntry>, entry: IndexEntry) {
147147
}
148148
}
149149

150-
fn clear(path: &Path) -> anyhow::Result<()> {
150+
#[tracing::instrument(level = "trace")]
151+
pub(crate) fn delete(path: &Path) -> anyhow::Result<()> {
151152
let mut index = WORKSPACE_INDEX.lock().unwrap();
152153
let path = str_from_path(path)?;
153154

@@ -159,6 +160,19 @@ fn clear(path: &Path) -> anyhow::Result<()> {
159160
Ok(())
160161
}
161162

163+
#[tracing::instrument(level = "trace")]
164+
pub(crate) fn rename(old: &Path, new: &Path) -> anyhow::Result<()> {
165+
let mut index = WORKSPACE_INDEX.lock().unwrap();
166+
let old = str_from_path(old)?;
167+
let new = str_from_path(new)?;
168+
169+
if let Some(entries) = index.remove(old) {
170+
index.insert(new.to_string(), entries);
171+
}
172+
173+
Ok(())
174+
}
175+
162176
#[cfg(test)]
163177
pub(crate) fn indexer_clear() {
164178
let mut index = WORKSPACE_INDEX.lock().unwrap();
@@ -208,8 +222,8 @@ pub fn filter_entry(entry: &DirEntry) -> bool {
208222
true
209223
}
210224

211-
fn index_file(path: &Path) -> anyhow::Result<()> {
212-
// only index R files
225+
pub(crate) fn create(path: &Path) -> anyhow::Result<()> {
226+
// Only index R files
213227
let ext = path.extension().unwrap_or_default();
214228
if ext != "r" && ext != "R" {
215229
return Ok(());

crates/ark/src/lsp/main_loop.rs

Lines changed: 174 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,15 @@ impl GlobalState {
295295
LspNotification::DidCloseTextDocument(params) => {
296296
state_handlers::did_close(params, &mut self.lsp_state, &mut self.world)?;
297297
},
298+
LspNotification::DidCreateFiles(params) => {
299+
state_handlers::did_create_files(params, &self.world)?;
300+
},
301+
LspNotification::DidDeleteFiles(params) => {
302+
state_handlers::did_delete_files(params, &mut self.world)?;
303+
},
304+
LspNotification::DidRenameFiles(params) => {
305+
state_handlers::did_rename_files(params, &mut self.world)?;
306+
},
298307
}
299308
},
300309

@@ -686,8 +695,10 @@ pub(crate) enum IndexerQueueTask {
686695

687696
#[derive(Debug)]
688697
pub enum IndexerTask {
689-
Start { folders: Vec<String> },
690-
Update { document: Document, uri: Url },
698+
Create { uri: Url },
699+
Delete { uri: Url },
700+
Rename { uri: Url, new: Url },
701+
Update { uri: Url, document: Document },
691702
}
692703

693704
#[derive(Debug)]
@@ -703,6 +714,27 @@ struct RefreshDiagnosticsResult {
703714
version: Option<i32>,
704715
}
705716

717+
fn summarize_indexer_task(batch: &[IndexerTask]) -> String {
718+
let mut counts = std::collections::HashMap::new();
719+
for task in batch {
720+
let type_name = match task {
721+
IndexerTask::Create { .. } => "Create",
722+
IndexerTask::Delete { .. } => "Delete",
723+
IndexerTask::Rename { .. } => "Rename",
724+
IndexerTask::Update { .. } => "Update",
725+
};
726+
*counts.entry(type_name).or_insert(0) += 1;
727+
}
728+
729+
let mut summary = String::new();
730+
for (task_type, count) in counts.iter() {
731+
use std::fmt::Write;
732+
let _ = write!(summary, "{task_type}: {count} ");
733+
}
734+
735+
summary.trim_end().to_string()
736+
}
737+
706738
static INDEXER_QUEUE: LazyLock<tokio::sync::mpsc::UnboundedSender<IndexerQueueTask>> =
707739
LazyLock::new(|| {
708740
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
@@ -770,44 +802,60 @@ async fn process_indexer_queue(mut rx: mpsc::UnboundedReceiver<IndexerQueueTask>
770802
}
771803

772804
async fn process_indexer_batch(batch: Vec<IndexerTask>) {
773-
// Deduplicate tasks by key. We use a `HashMap` so only the last insertion
774-
// is retained. `Update` tasks use URI as key, `Start` tasks use None (we
775-
// only expect one though). This is effectively a way of cancelling `Update`
776-
// tasks for outdated documents.
777-
let batch: std::collections::HashMap<_, _> = batch
778-
.into_iter()
779-
.map(|task| match &task {
780-
IndexerTask::Update { uri, .. } => (Some(uri.clone()), task),
781-
IndexerTask::Start { .. } => (None, task),
782-
})
783-
.collect();
805+
tracing::trace!(
806+
"Processing {n} indexer tasks ({summary})",
807+
n = batch.len(),
808+
summary = summarize_indexer_task(&batch)
809+
);
810+
811+
let to_path_buf = |uri: &url::Url| {
812+
uri.to_file_path()
813+
.map_err(|_| anyhow!("Failed to convert URI '{uri}' to file path"))
814+
};
784815

785-
let mut handles = Vec::new();
816+
for task in batch {
817+
let result: anyhow::Result<()> = (|| async {
818+
match &task {
819+
IndexerTask::Create { uri } => {
820+
let path = to_path_buf(uri)?;
821+
indexer::create(&path)?;
822+
},
786823

787-
for (_, task) in batch {
788-
handles.push(tokio::task::spawn_blocking(move || match task {
789-
IndexerTask::Start { folders } => {
790-
indexer::start(folders);
791-
},
792-
IndexerTask::Update { document, uri } => {
793-
let result = if let Ok(path) = uri.to_file_path() {
794-
indexer::update(&document, &path)
795-
} else {
796-
Err(anyhow!("Failed to convert URI to file path: {uri}"))
797-
};
798-
if let Err(err) = result {
799-
log::error!("Indexer update failed: {err}");
800-
}
801-
},
802-
}));
803-
}
824+
IndexerTask::Update { uri, document } => {
825+
let path = to_path_buf(uri)?;
826+
indexer::update(&document, &path)?;
827+
},
828+
829+
IndexerTask::Delete { uri } => {
830+
let path = to_path_buf(uri)?;
831+
indexer::delete(&path)?;
832+
},
833+
834+
IndexerTask::Rename {
835+
uri: old_uri,
836+
new: new_uri,
837+
} => {
838+
let old_path = to_path_buf(old_uri)?;
839+
let new_path = to_path_buf(new_uri)?;
840+
841+
indexer::rename(&old_path, &new_path)?;
842+
},
843+
}
804844

805-
for handle in handles {
806-
let _ = handle.await;
845+
Ok(())
846+
})()
847+
.await;
848+
849+
if let Err(err) = result {
850+
tracing::warn!("Can't process indexer task: {err}");
851+
continue;
852+
}
807853
}
808854
}
809855

810856
async fn process_diagnostics_batch(batch: Vec<RefreshDiagnosticsTask>) {
857+
tracing::trace!("Processing {n} diagnostic tasks", n = batch.len());
858+
811859
// Deduplicate tasks by keeping only the last one for each URI. We use a
812860
// `HashMap` so only the last insertion is retained. This is effectively a
813861
// way of cancelling diagnostics tasks for outdated documents.
@@ -850,27 +898,110 @@ async fn process_diagnostics_batch(batch: Vec<RefreshDiagnosticsTask>) {
850898
}
851899

852900
pub(crate) fn index_start(folders: Vec<String>, state: WorldState) {
853-
INDEXER_QUEUE
854-
.send(IndexerQueueTask::Indexer(IndexerTask::Start { folders }))
855-
.unwrap_or_else(|err| lsp::log_error!("Failed to queue initial indexing: {err}"));
901+
lsp::log_info!("Initial indexing started");
902+
903+
let uris: Vec<Url> = folders
904+
.into_iter()
905+
.flat_map(|folder| {
906+
walkdir::WalkDir::new(folder)
907+
.into_iter()
908+
.filter_entry(|e| indexer::filter_entry(e))
909+
.filter_map(|entry| {
910+
let entry = match entry {
911+
Ok(e) => e,
912+
Err(_) => return None,
913+
};
914+
915+
if !entry.file_type().is_file() {
916+
return None;
917+
}
918+
let path = entry.path();
856919

920+
// Only index R files
921+
let ext = path.extension().unwrap_or_default();
922+
if ext != "r" && ext != "R" {
923+
return None;
924+
}
925+
926+
if let Ok(uri) = url::Url::from_file_path(path) {
927+
Some(uri)
928+
} else {
929+
tracing::warn!("Can't convert path to URI: {:?}", path);
930+
None
931+
}
932+
})
933+
})
934+
.collect();
935+
936+
index_create(uris, state);
937+
}
938+
939+
pub(crate) fn index_create(uris: Vec<Url>, state: WorldState) {
940+
for uri in uris {
941+
INDEXER_QUEUE
942+
.send(IndexerQueueTask::Indexer(IndexerTask::Create { uri }))
943+
.unwrap_or_else(|err| crate::lsp::log_error!("Failed to queue index create: {err}"));
944+
}
945+
946+
diagnostics_refresh_all(state);
947+
}
948+
949+
pub(crate) fn index_update(uris: Vec<Url>, state: WorldState) {
950+
for uri in uris {
951+
let document = match state.get_document(&uri) {
952+
Ok(doc) => doc.clone(),
953+
Err(err) => {
954+
tracing::warn!("Can't get document '{uri}' for indexing: {err:?}");
955+
continue;
956+
},
957+
};
958+
959+
INDEXER_QUEUE
960+
.send(IndexerQueueTask::Indexer(IndexerTask::Update {
961+
document,
962+
uri,
963+
}))
964+
.unwrap_or_else(|err| lsp::log_error!("Failed to queue index update: {err}"));
965+
}
966+
967+
// Refresh all diagnostics since the indexer results for one file may affect
968+
// other files
969+
diagnostics_refresh_all(state);
970+
}
971+
972+
pub(crate) fn index_delete(uris: Vec<Url>, state: WorldState) {
973+
for uri in uris {
974+
INDEXER_QUEUE
975+
.send(IndexerQueueTask::Indexer(IndexerTask::Delete { uri }))
976+
.unwrap_or_else(|err| lsp::log_error!("Failed to queue index update: {err}"));
977+
}
978+
979+
// Refresh all diagnostics since the indexer results for one file may affect
980+
// other files
857981
diagnostics_refresh_all(state);
858982
}
859983

860-
pub(crate) fn index_update(uri: Url, document: Document, state: WorldState) {
861-
INDEXER_QUEUE
862-
.send(IndexerQueueTask::Indexer(IndexerTask::Update {
863-
document,
864-
uri: uri.clone(),
865-
}))
866-
.unwrap_or_else(|err| lsp::log_error!("Failed to queue index update: {err}"));
984+
pub(crate) fn index_rename(uris: Vec<(Url, Url)>, state: WorldState) {
985+
for (old, new) in uris {
986+
INDEXER_QUEUE
987+
.send(IndexerQueueTask::Indexer(IndexerTask::Rename {
988+
uri: old,
989+
new,
990+
}))
991+
.unwrap_or_else(|err| lsp::log_error!("Failed to queue index update: {err}"));
992+
}
867993

868994
// Refresh all diagnostics since the indexer results for one file may affect
869995
// other files
870996
diagnostics_refresh_all(state);
871997
}
872998

873999
pub(crate) fn diagnostics_refresh_all(state: WorldState) {
1000+
tracing::trace!(
1001+
"Refreshing diagnostics for {n} documents",
1002+
n = state.documents.len()
1003+
);
1004+
8741005
for (uri, _document) in state.documents.iter() {
8751006
INDEXER_QUEUE
8761007
.send(IndexerQueueTask::Diagnostics(RefreshDiagnosticsTask {

0 commit comments

Comments
 (0)