use std::collections::HashSet;
use std::path::PathBuf;
use std::time::Instant;
use lsp_types::{
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
DidSaveTextDocumentParams, MessageType,
};
use salsa::{Durability, Setter};
use super::conversions::apply_content_change;
use super::global_state::GlobalState;
use super::uri_ext::UriExt;
use crate::lsp::DocumentState;
pub(crate) fn load_project_files(
gs: &mut GlobalState,
salsa_file: crate::salsa::FileText,
salsa_config: crate::salsa::FileConfig,
root_path: PathBuf,
) -> HashSet<PathBuf> {
gs.salsa
.load_referenced_files(salsa_file, salsa_config, root_path)
}
pub(crate) fn reload_open_documents_referenced_files(gs: &mut GlobalState) {
let open_docs: Vec<(crate::salsa::FileText, crate::salsa::FileConfig, PathBuf)> = gs
.document_map
.values()
.filter_map(|state| {
let path = crate::salsa::Db::path_of_id(&gs.salsa, state.file_id)?;
Some((state.salsa_file, state.salsa_config, path))
})
.collect();
let open_paths: HashSet<PathBuf> = gs
.document_map
.values()
.filter_map(|state| crate::salsa::Db::path_of_id(&gs.salsa, state.file_id))
.collect();
let mut referenced: HashSet<PathBuf> = HashSet::new();
for (salsa_file, salsa_config, path) in open_docs {
referenced.extend(load_project_files(gs, salsa_file, salsa_config, path));
}
for path in referenced {
if open_paths.contains(&path) {
continue;
}
gs.salsa
.resync_cached_file_from_disk(&path, Durability::MEDIUM);
}
}
pub(crate) fn reload_open_documents_config(gs: &mut GlobalState) {
let entries: Vec<(String, lsp_types::Uri)> = gs
.document_map
.keys()
.filter_map(|uri_str| Some((uri_str.clone(), uri_str.parse().ok()?)))
.collect();
for (uri_str, uri) in entries {
let new_config = gs.load_config_notifying(&uri);
let interned = gs.intern_config(new_config);
if let Some(state) = gs.document_map_mut().get_mut(&uri_str) {
state.salsa_config = interned;
}
}
}
pub(crate) fn did_open(gs: &mut GlobalState, params: DidOpenTextDocumentParams) {
let uri = params.text_document.uri.clone();
let uri_string = uri.to_string();
let text = params.text_document.text.clone();
log::debug!("did_open uri={uri_string}, bytes={}", text.len());
let start = Instant::now();
let config = gs.load_config_notifying(&uri);
let doc_path = uri.to_file_path().map(|p| p.into_owned());
let salsa_file = match doc_path.clone() {
Some(path) => {
gs.salsa
.update_file_text_with_durability(path, text.clone(), Durability::LOW)
}
None => gs
.salsa
.create_in_memory_file(text.clone(), Durability::LOW),
};
let salsa_config = gs.intern_config(config.clone());
let file_id = gs
.salsa
.file_id_for_input(salsa_file)
.expect("just-registered document input has a FileId");
if gs.runtime_settings.experimental_incremental_parsing {
gs.salsa.reparse_admit(salsa_file, salsa_config);
}
gs.document_map_mut().insert(
uri_string.clone(),
DocumentState {
file_id,
salsa_file,
salsa_config,
},
);
if let Some(path) = doc_path.as_ref() {
load_project_files(gs, salsa_file, salsa_config, path.clone());
}
gs.sender
.log_message(MessageType::INFO, format!("Opened document: {uri_string}"));
gs.arm_settle_external(uri);
log::debug!("did_open complete in {:?}", start.elapsed());
}
pub(crate) fn did_change(gs: &mut GlobalState, params: DidChangeTextDocumentParams) {
let uri = params.text_document.uri.clone();
let uri_string = uri.to_string();
let change_count = params.content_changes.len();
log::debug!("did_change uri={uri_string}, changes={change_count}");
let start = Instant::now();
let config = gs.load_config_notifying(&uri);
let Some((salsa_file, previous_config)) = gs
.document_map
.get(&uri_string)
.map(|doc| (doc.salsa_file, doc.salsa_config))
else {
return;
};
let mut updated_text = salsa_file.content_or_empty(&gs.salsa).to_string();
for change in params.content_changes.iter() {
updated_text = apply_content_change(&updated_text, change);
}
let doc_path_for_salsa = uri.to_file_path().map(|p| p.into_owned());
if let Some(path) = doc_path_for_salsa.as_ref() {
gs.salsa.update_file_text(path.clone(), updated_text);
} else {
salsa_file
.set_text(&mut gs.salsa)
.with_durability(Durability::LOW)
.to(Some(std::sync::Arc::from(updated_text)));
}
let interned_config = gs.intern_config(config);
if let Some(doc_state) = gs.document_map_mut().get_mut(&uri_string) {
doc_state.salsa_config = interned_config;
} else {
return;
}
if gs.runtime_settings.experimental_incremental_parsing && interned_config != previous_config {
gs.salsa.reparse_admit(salsa_file, interned_config);
}
gs.arm_settle();
log::debug!(
"did_change complete (state) in {:?}; settle armed",
start.elapsed()
);
}
pub(crate) fn did_save(gs: &mut GlobalState, params: DidSaveTextDocumentParams) {
let uri = params.text_document.uri;
if let Some((salsa_file, salsa_config, Some(path))) =
gs.document_map.get(&uri.to_string()).map(|doc| {
(
doc.salsa_file,
doc.salsa_config,
crate::salsa::Db::path_of_id(&gs.salsa, doc.file_id),
)
})
{
load_project_files(gs, salsa_file, salsa_config, path);
}
gs.arm_settle_external(uri);
}
pub(crate) fn did_close(gs: &mut GlobalState, params: DidCloseTextDocumentParams) {
let uri = params.text_document.uri.clone();
let uri_string = uri.to_string();
if let Some(state) = gs.document_map.get(&uri_string) {
let salsa_file = state.salsa_file;
gs.salsa.reparse_retire_file(salsa_file);
}
gs.document_map_mut().remove(&uri_string);
gs.diagnostics
.drop_uri(&uri, &gs.sender, gs.supports_pull_diagnostics);
let states: Vec<DocumentState> = gs.document_map.values().cloned().collect();
let mut retained = HashSet::new();
for state in states {
let Some(path) = crate::salsa::Db::path_of_id(&gs.salsa, state.file_id) else {
continue;
};
let tracked = load_project_files(gs, state.salsa_file, state.salsa_config, path);
retained.extend(tracked);
}
for cached in gs.salsa.cached_file_paths() {
if retained.contains(&cached) {
continue;
}
let _ = gs.salsa.evict_file_text(&cached);
}
gs.arm_settle();
}