use std::collections::HashSet;
use std::path::PathBuf;
use std::time::Instant;
use lsp_types::{
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
DidSaveTextDocumentParams, MessageType,
};
use salsa::Durability;
use super::global_state::GlobalState;
use super::uri_ext::UriExt;
use crate::lsp::DocumentState;
use crate::lsp::line_index::LineIndex;
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);
}
}
fn refresh_document_config(gs: &mut GlobalState, uri_str: &str, uri: &lsp_types::Uri) -> bool {
let new_config = gs.load_config_notifying(uri);
let interned = gs.intern_config(new_config);
let Some(previous) = gs.document_map.get(uri_str).map(|state| state.salsa_config) else {
return false;
};
if previous == interned {
return false;
}
let Some(state) = gs.document_map_mut().get_mut(uri_str) else {
return false;
};
state.salsa_config = interned;
let salsa_file = state.salsa_file;
if gs.runtime_settings.experimental_incremental_parsing {
gs.salsa.reparse_admit(salsa_file, interned);
}
true
}
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 {
refresh_document_config(gs, &uri_str, &uri);
}
}
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 Some(salsa_file) = gs.document_map.get(&uri_string).map(|doc| doc.salsa_file) else {
return;
};
let mut index = gs.take_line_index(salsa_file);
for change in params.content_changes.iter() {
match change.range {
Some(range) => {
let index = std::sync::Arc::make_mut(&mut index);
let span = super::conversions::content_change_span(index, range);
index.replace_range(span, &change.text);
}
None => index = std::sync::Arc::new(LineIndex::new(&change.text)),
}
}
let text = index.text_arc();
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(), text);
} else {
gs.salsa
.update_input_text(salsa_file, text, Durability::LOW);
}
gs.store_line_index(salsa_file, index);
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;
refresh_document_config(gs, &uri.to_string(), &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.retire_line_index(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();
}