Merge pull request #18998 from ChayimFriedman2/exclude

fix: Make `rust-analyzer.files.excludeDirs` work, actually
This commit is contained in:
Lukas Wirth 2025-02-11 11:41:54 +00:00 committed by GitHub
commit 78e7515a30
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 262 additions and 131 deletions

View file

@ -94,7 +94,9 @@ pub fn load_workspace(
let contents = loader.load_sync(path);
let path = vfs::VfsPath::from(path.to_path_buf());
vfs.set_file_contents(path.clone(), contents);
vfs.file_id(&path)
vfs.file_id(&path).and_then(|(file_id, excluded)| {
(excluded == vfs::FileExcluded::No).then_some(file_id)
})
},
extra_env,
);

View file

@ -84,10 +84,10 @@ config_data! {
completion_snippets_custom: FxHashMap<String, SnippetDef> = Config::completion_snippets_default(),
/// These directories will be ignored by rust-analyzer. They are
/// These paths (file/directories) will be ignored by rust-analyzer. They are
/// relative to the workspace root, and globs are not supported. You may
/// also need to add the folders to Code's `files.watcherExclude`.
files_excludeDirs: Vec<Utf8PathBuf> = vec![],
files_exclude | files_excludeDirs: Vec<Utf8PathBuf> = vec![],
@ -1792,7 +1792,7 @@ impl Config {
fn discovered_projects(&self) -> Vec<ManifestOrProjectJson> {
let exclude_dirs: Vec<_> =
self.files_excludeDirs().iter().map(|p| self.root_path.join(p)).collect();
self.files_exclude().iter().map(|p| self.root_path.join(p)).collect();
let mut projects = vec![];
for fs_proj in &self.discovered_projects_from_filesystem {
@ -1914,10 +1914,14 @@ impl Config {
}
_ => FilesWatcher::Server,
},
exclude: self.files_excludeDirs().iter().map(|it| self.root_path.join(it)).collect(),
exclude: self.excluded().collect(),
}
}
pub fn excluded(&self) -> impl Iterator<Item = AbsPathBuf> + use<'_> {
self.files_exclude().iter().map(|it| self.root_path.join(it))
}
pub fn notifications(&self) -> NotificationsConfig {
NotificationsConfig {
cargo_toml_not_found: self.notifications_cargoTomlNotFound().to_owned(),

View file

@ -650,7 +650,8 @@ impl GlobalStateSnapshot {
RwLockReadGuard::map(self.vfs.read(), |(it, _)| it)
}
pub(crate) fn url_to_file_id(&self, url: &Url) -> anyhow::Result<FileId> {
/// Returns `None` if the file was excluded.
pub(crate) fn url_to_file_id(&self, url: &Url) -> anyhow::Result<Option<FileId>> {
url_to_file_id(&self.vfs_read(), url)
}
@ -658,7 +659,8 @@ impl GlobalStateSnapshot {
file_id_to_url(&self.vfs_read(), id)
}
pub(crate) fn vfs_path_to_file_id(&self, vfs_path: &VfsPath) -> anyhow::Result<FileId> {
/// Returns `None` if the file was excluded.
pub(crate) fn vfs_path_to_file_id(&self, vfs_path: &VfsPath) -> anyhow::Result<Option<FileId>> {
vfs_path_to_file_id(&self.vfs_read(), vfs_path)
}
@ -750,14 +752,21 @@ pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url {
url_from_abs_path(path)
}
pub(crate) fn url_to_file_id(vfs: &vfs::Vfs, url: &Url) -> anyhow::Result<FileId> {
/// Returns `None` if the file was excluded.
pub(crate) fn url_to_file_id(vfs: &vfs::Vfs, url: &Url) -> anyhow::Result<Option<FileId>> {
let path = from_proto::vfs_path(url)?;
let res = vfs.file_id(&path).ok_or_else(|| anyhow::format_err!("file not found: {path}"))?;
Ok(res)
vfs_path_to_file_id(vfs, &path)
}
pub(crate) fn vfs_path_to_file_id(vfs: &vfs::Vfs, vfs_path: &VfsPath) -> anyhow::Result<FileId> {
let res =
/// Returns `None` if the file was excluded.
pub(crate) fn vfs_path_to_file_id(
vfs: &vfs::Vfs,
vfs_path: &VfsPath,
) -> anyhow::Result<Option<FileId>> {
let (file_id, excluded) =
vfs.file_id(vfs_path).ok_or_else(|| anyhow::format_err!("file not found: {vfs_path}"))?;
Ok(res)
match excluded {
vfs::FileExcluded::Yes => Ok(None),
vfs::FileExcluded::No => Ok(Some(file_id)),
}
}

View file

@ -22,6 +22,7 @@ use crate::{
mem_docs::DocumentData,
reload,
target_spec::TargetSpec,
try_default,
};
pub(crate) fn handle_cancel(state: &mut GlobalState, params: CancelParams) -> anyhow::Result<()> {
@ -74,6 +75,14 @@ pub(crate) fn handle_did_open_text_document(
tracing::error!("duplicate DidOpenTextDocument: {}", path);
}
if let Some(abs_path) = path.as_path() {
if state.config.excluded().any(|excluded| abs_path.starts_with(&excluded)) {
tracing::trace!("opened excluded file {abs_path}");
state.vfs.write().0.insert_excluded_file(path);
return Ok(());
}
}
let contents = params.text_document.text.into_bytes();
state.vfs.write().0.set_file_contents(path, Some(contents));
if state.config.discover_workspace_config().is_some() {
@ -127,7 +136,8 @@ pub(crate) fn handle_did_close_text_document(
tracing::error!("orphan DidCloseTextDocument: {}", path);
}
if let Some(file_id) = state.vfs.read().0.file_id(&path) {
// Clear diagnostics also for excluded files, just in case.
if let Some((file_id, _)) = state.vfs.read().0.file_id(&path) {
state.diagnostics.clear_native_for(file_id);
}
@ -146,7 +156,7 @@ pub(crate) fn handle_did_save_text_document(
) -> anyhow::Result<()> {
if let Ok(vfs_path) = from_proto::vfs_path(&params.text_document.uri) {
let snap = state.snapshot();
let file_id = snap.vfs_path_to_file_id(&vfs_path)?;
let file_id = try_default!(snap.vfs_path_to_file_id(&vfs_path)?);
let sr = snap.analysis.source_root_id(file_id)?;
if state.config.script_rebuild_on_save(Some(sr)) && state.build_deps_changed {
@ -290,7 +300,7 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
let _p = tracing::info_span!("run_flycheck").entered();
let file_id = state.vfs.read().0.file_id(&vfs_path);
if let Some(file_id) = file_id {
if let Some((file_id, vfs::FileExcluded::No)) = file_id {
let world = state.snapshot();
let invocation_strategy_once = state.config.flycheck(None).invocation_strategy_once();
let may_flycheck_workspace = state.config.flycheck_workspace(None);

View file

@ -53,6 +53,7 @@ use crate::{
},
target_spec::{CargoTargetSpec, TargetSpec},
test_runner::{CargoTestHandle, TestTarget},
try_default,
};
pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> anyhow::Result<()> {
@ -83,7 +84,8 @@ pub(crate) fn handle_analyzer_status(
let mut file_id = None;
if let Some(tdi) = params.text_document {
match from_proto::file_id(&snap, &tdi.uri) {
Ok(it) => file_id = Some(it),
Ok(Some(it)) => file_id = Some(it),
Ok(None) => {}
Err(_) => format_to!(buf, "file {} not found in vfs", tdi.uri),
}
}
@ -141,7 +143,7 @@ pub(crate) fn handle_view_syntax_tree(
params: lsp_ext::ViewSyntaxTreeParams,
) -> anyhow::Result<String> {
let _p = tracing::info_span!("handle_view_syntax_tree").entered();
let id = from_proto::file_id(&snap, &params.text_document.uri)?;
let id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let res = snap.analysis.view_syntax_tree(id)?;
Ok(res)
}
@ -151,7 +153,7 @@ pub(crate) fn handle_view_hir(
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<String> {
let _p = tracing::info_span!("handle_view_hir").entered();
let position = from_proto::file_position(&snap, params)?;
let position = try_default!(from_proto::file_position(&snap, params)?);
let res = snap.analysis.view_hir(position)?;
Ok(res)
}
@ -161,7 +163,7 @@ pub(crate) fn handle_view_mir(
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<String> {
let _p = tracing::info_span!("handle_view_mir").entered();
let position = from_proto::file_position(&snap, params)?;
let position = try_default!(from_proto::file_position(&snap, params)?);
let res = snap.analysis.view_mir(position)?;
Ok(res)
}
@ -171,7 +173,7 @@ pub(crate) fn handle_interpret_function(
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<String> {
let _p = tracing::info_span!("handle_interpret_function").entered();
let position = from_proto::file_position(&snap, params)?;
let position = try_default!(from_proto::file_position(&snap, params)?);
let res = snap.analysis.interpret_function(position)?;
Ok(res)
}
@ -180,7 +182,7 @@ pub(crate) fn handle_view_file_text(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentIdentifier,
) -> anyhow::Result<String> {
let file_id = from_proto::file_id(&snap, &params.uri)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.uri)?);
Ok(snap.analysis.file_text(file_id)?.to_string())
}
@ -189,7 +191,7 @@ pub(crate) fn handle_view_item_tree(
params: lsp_ext::ViewItemTreeParams,
) -> anyhow::Result<String> {
let _p = tracing::info_span!("handle_view_item_tree").entered();
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let res = snap.analysis.view_item_tree(file_id)?;
Ok(res)
}
@ -315,7 +317,7 @@ pub(crate) fn handle_expand_macro(
params: lsp_ext::ExpandMacroParams,
) -> anyhow::Result<Option<lsp_ext::ExpandedMacro>> {
let _p = tracing::info_span!("handle_expand_macro").entered();
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let line_index = snap.file_line_index(file_id)?;
let offset = from_proto::offset(&line_index, params.position)?;
@ -328,7 +330,7 @@ pub(crate) fn handle_selection_range(
params: lsp_types::SelectionRangeParams,
) -> anyhow::Result<Option<Vec<lsp_types::SelectionRange>>> {
let _p = tracing::info_span!("handle_selection_range").entered();
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let line_index = snap.file_line_index(file_id)?;
let res: anyhow::Result<Vec<lsp_types::SelectionRange>> = params
.positions
@ -371,7 +373,7 @@ pub(crate) fn handle_matching_brace(
params: lsp_ext::MatchingBraceParams,
) -> anyhow::Result<Vec<Position>> {
let _p = tracing::info_span!("handle_matching_brace").entered();
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let line_index = snap.file_line_index(file_id)?;
params
.positions
@ -395,7 +397,7 @@ pub(crate) fn handle_join_lines(
) -> anyhow::Result<Vec<lsp_types::TextEdit>> {
let _p = tracing::info_span!("handle_join_lines").entered();
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let config = snap.config.join_lines();
let line_index = snap.file_line_index(file_id)?;
@ -419,7 +421,7 @@ pub(crate) fn handle_on_enter(
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
let _p = tracing::info_span!("handle_on_enter").entered();
let position = from_proto::file_position(&snap, params)?;
let position = try_default!(from_proto::file_position(&snap, params)?);
let edit = match snap.analysis.on_enter(position)? {
None => return Ok(None),
Some(it) => it,
@ -439,7 +441,8 @@ pub(crate) fn handle_on_type_formatting(
return Ok(None);
}
let mut position = from_proto::file_position(&snap, params.text_document_position)?;
let mut position =
try_default!(from_proto::file_position(&snap, params.text_document_position)?);
let line_index = snap.file_line_index(position.file_id)?;
// in `ide`, the `on_type` invariant is that
@ -465,32 +468,33 @@ pub(crate) fn handle_on_type_formatting(
Ok(Some(change))
}
pub(crate) fn empty_diagnostic_report() -> lsp_types::DocumentDiagnosticReportResult {
lsp_types::DocumentDiagnosticReportResult::Report(lsp_types::DocumentDiagnosticReport::Full(
lsp_types::RelatedFullDocumentDiagnosticReport {
related_documents: None,
full_document_diagnostic_report: lsp_types::FullDocumentDiagnosticReport {
result_id: Some("rust-analyzer".to_owned()),
items: vec![],
},
},
))
}
pub(crate) fn handle_document_diagnostics(
snap: GlobalStateSnapshot,
params: lsp_types::DocumentDiagnosticParams,
) -> anyhow::Result<lsp_types::DocumentDiagnosticReportResult> {
let empty = || {
lsp_types::DocumentDiagnosticReportResult::Report(
lsp_types::DocumentDiagnosticReport::Full(
lsp_types::RelatedFullDocumentDiagnosticReport {
related_documents: None,
full_document_diagnostic_report: lsp_types::FullDocumentDiagnosticReport {
result_id: Some("rust-analyzer".to_owned()),
items: vec![],
},
},
),
)
let file_id = match from_proto::file_id(&snap, &params.text_document.uri)? {
Some(it) => it,
None => return Ok(empty_diagnostic_report()),
};
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let source_root = snap.analysis.source_root_id(file_id)?;
if !snap.analysis.is_local_source_root(source_root)? {
return Ok(empty());
return Ok(empty_diagnostic_report());
}
let config = snap.config.diagnostics(Some(source_root));
if !config.enabled {
return Ok(empty());
return Ok(empty_diagnostic_report());
}
let line_index = snap.file_line_index(file_id)?;
let supports_related = snap.config.text_document_diagnostic_related_document_support();
@ -546,7 +550,7 @@ pub(crate) fn handle_document_symbol(
params: lsp_types::DocumentSymbolParams,
) -> anyhow::Result<Option<lsp_types::DocumentSymbolResponse>> {
let _p = tracing::info_span!("handle_document_symbol").entered();
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let line_index = snap.file_line_index(file_id)?;
let mut parents: Vec<(lsp_types::DocumentSymbol, Option<usize>)> = Vec::new();
@ -760,7 +764,7 @@ pub(crate) fn handle_will_rename_files(
}
})
.filter_map(|(file_id, new_name)| {
snap.analysis.will_rename_file(file_id, &new_name).ok()?
snap.analysis.will_rename_file(file_id?, &new_name).ok()?
})
.collect();
@ -782,7 +786,8 @@ pub(crate) fn handle_goto_definition(
params: lsp_types::GotoDefinitionParams,
) -> anyhow::Result<Option<lsp_types::GotoDefinitionResponse>> {
let _p = tracing::info_span!("handle_goto_definition").entered();
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let position =
try_default!(from_proto::file_position(&snap, params.text_document_position_params)?);
let nav_info = match snap.analysis.goto_definition(position)? {
None => return Ok(None),
Some(it) => it,
@ -797,7 +802,10 @@ pub(crate) fn handle_goto_declaration(
params: lsp_types::request::GotoDeclarationParams,
) -> anyhow::Result<Option<lsp_types::request::GotoDeclarationResponse>> {
let _p = tracing::info_span!("handle_goto_declaration").entered();
let position = from_proto::file_position(&snap, params.text_document_position_params.clone())?;
let position = try_default!(from_proto::file_position(
&snap,
params.text_document_position_params.clone()
)?);
let nav_info = match snap.analysis.goto_declaration(position)? {
None => return handle_goto_definition(snap, params),
Some(it) => it,
@ -812,7 +820,8 @@ pub(crate) fn handle_goto_implementation(
params: lsp_types::request::GotoImplementationParams,
) -> anyhow::Result<Option<lsp_types::request::GotoImplementationResponse>> {
let _p = tracing::info_span!("handle_goto_implementation").entered();
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let position =
try_default!(from_proto::file_position(&snap, params.text_document_position_params)?);
let nav_info = match snap.analysis.goto_implementation(position)? {
None => return Ok(None),
Some(it) => it,
@ -827,7 +836,8 @@ pub(crate) fn handle_goto_type_definition(
params: lsp_types::request::GotoTypeDefinitionParams,
) -> anyhow::Result<Option<lsp_types::request::GotoTypeDefinitionResponse>> {
let _p = tracing::info_span!("handle_goto_type_definition").entered();
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let position =
try_default!(from_proto::file_position(&snap, params.text_document_position_params)?);
let nav_info = match snap.analysis.goto_type_definition(position)? {
None => return Ok(None),
Some(it) => it,
@ -880,7 +890,7 @@ pub(crate) fn handle_parent_module(
}
// check if invoked at the crate root
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let crate_id = match snap.analysis.crates_for(file_id)?.first() {
Some(&crate_id) => crate_id,
None => return Ok(None),
@ -904,7 +914,7 @@ pub(crate) fn handle_parent_module(
}
// locate parent module by semantics
let position = from_proto::file_position(&snap, params)?;
let position = try_default!(from_proto::file_position(&snap, params)?);
let navs = snap.analysis.parent_module(position)?;
let res = to_proto::goto_definition_response(&snap, None, navs)?;
Ok(Some(res))
@ -915,7 +925,7 @@ pub(crate) fn handle_runnables(
params: lsp_ext::RunnablesParams,
) -> anyhow::Result<Vec<lsp_ext::Runnable>> {
let _p = tracing::info_span!("handle_runnables").entered();
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let source_root = snap.analysis.source_root_id(file_id).ok();
let line_index = snap.file_line_index(file_id)?;
let offset = params.position.and_then(|it| from_proto::offset(&line_index, it).ok());
@ -1035,7 +1045,7 @@ pub(crate) fn handle_related_tests(
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<Vec<lsp_ext::TestInfo>> {
let _p = tracing::info_span!("handle_related_tests").entered();
let position = from_proto::file_position(&snap, params)?;
let position = try_default!(from_proto::file_position(&snap, params)?);
let tests = snap.analysis.related_tests(position, None)?;
let mut res = Vec::new();
@ -1053,7 +1063,8 @@ pub(crate) fn handle_completion(
lsp_types::CompletionParams { text_document_position, context,.. }: lsp_types::CompletionParams,
) -> anyhow::Result<Option<lsp_types::CompletionResponse>> {
let _p = tracing::info_span!("handle_completion").entered();
let mut position = from_proto::file_position(&snap, text_document_position.clone())?;
let mut position =
try_default!(from_proto::file_position(&snap, text_document_position.clone())?);
let line_index = snap.file_line_index(position.file_id)?;
let completion_trigger_character =
context.and_then(|ctx| ctx.trigger_character).and_then(|s| s.chars().next());
@ -1102,7 +1113,8 @@ pub(crate) fn handle_completion_resolve(
let resolve_data: lsp_ext::CompletionResolveData = serde_json::from_value(data)?;
let file_id = from_proto::file_id(&snap, &resolve_data.position.text_document.uri)?;
let file_id = from_proto::file_id(&snap, &resolve_data.position.text_document.uri)?
.expect("we never provide completions for excluded files");
let line_index = snap.file_line_index(file_id)?;
// FIXME: We should fix up the position when retrying the cancelled request instead
let Ok(offset) = from_proto::offset(&line_index, resolve_data.position.position) else {
@ -1185,7 +1197,7 @@ pub(crate) fn handle_folding_range(
params: FoldingRangeParams,
) -> anyhow::Result<Option<Vec<FoldingRange>>> {
let _p = tracing::info_span!("handle_folding_range").entered();
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let folds = snap.analysis.folding_ranges(file_id)?;
let text = snap.analysis.file_text(file_id)?;
let line_index = snap.file_line_index(file_id)?;
@ -1202,7 +1214,8 @@ pub(crate) fn handle_signature_help(
params: lsp_types::SignatureHelpParams,
) -> anyhow::Result<Option<lsp_types::SignatureHelp>> {
let _p = tracing::info_span!("handle_signature_help").entered();
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let position =
try_default!(from_proto::file_position(&snap, params.text_document_position_params)?);
let help = match snap.analysis.signature_help(position)? {
Some(it) => it,
None => return Ok(None),
@ -1221,7 +1234,7 @@ pub(crate) fn handle_hover(
PositionOrRange::Position(position) => Range::new(position, position),
PositionOrRange::Range(range) => range,
};
let file_range = from_proto::file_range(&snap, &params.text_document, range)?;
let file_range = try_default!(from_proto::file_range(&snap, &params.text_document, range)?);
let hover = snap.config.hover();
let info = match snap.analysis.hover(&hover, file_range)? {
@ -1255,7 +1268,7 @@ pub(crate) fn handle_prepare_rename(
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<Option<PrepareRenameResponse>> {
let _p = tracing::info_span!("handle_prepare_rename").entered();
let position = from_proto::file_position(&snap, params)?;
let position = try_default!(from_proto::file_position(&snap, params)?);
let change = snap.analysis.prepare_rename(position)?.map_err(to_proto::rename_error)?;
@ -1269,7 +1282,7 @@ pub(crate) fn handle_rename(
params: RenameParams,
) -> anyhow::Result<Option<WorkspaceEdit>> {
let _p = tracing::info_span!("handle_rename").entered();
let position = from_proto::file_position(&snap, params.text_document_position)?;
let position = try_default!(from_proto::file_position(&snap, params.text_document_position)?);
let mut change =
snap.analysis.rename(position, &params.new_name)?.map_err(to_proto::rename_error)?;
@ -1304,7 +1317,7 @@ pub(crate) fn handle_references(
params: lsp_types::ReferenceParams,
) -> anyhow::Result<Option<Vec<Location>>> {
let _p = tracing::info_span!("handle_references").entered();
let position = from_proto::file_position(&snap, params.text_document_position)?;
let position = try_default!(from_proto::file_position(&snap, params.text_document_position)?);
let exclude_imports = snap.config.find_all_refs_exclude_imports();
let exclude_tests = snap.config.find_all_refs_exclude_tests();
@ -1375,9 +1388,9 @@ pub(crate) fn handle_code_action(
return Ok(None);
}
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let line_index = snap.file_line_index(file_id)?;
let frange = from_proto::file_range(&snap, &params.text_document, params.range)?;
let frange = try_default!(from_proto::file_range(&snap, &params.text_document, params.range)?);
let source_root = snap.analysis.source_root_id(file_id)?;
let mut assists_config = snap.config.assist(Some(source_root));
@ -1455,7 +1468,8 @@ pub(crate) fn handle_code_action_resolve(
return Err(invalid_params_error("code action without data".to_owned()).into());
};
let file_id = from_proto::file_id(&snap, &params.code_action_params.text_document.uri)?;
let file_id = from_proto::file_id(&snap, &params.code_action_params.text_document.uri)?
.expect("we never provide code actions for excluded files");
if snap.file_version(file_id) != params.version {
return Err(invalid_params_error("stale code action".to_owned()).into());
}
@ -1551,7 +1565,7 @@ pub(crate) fn handle_code_lens(
return Ok(Some(Vec::default()));
}
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let target_spec = TargetSpec::for_file(&snap, file_id)?;
let annotations = snap.analysis.annotations(
@ -1613,7 +1627,8 @@ pub(crate) fn handle_document_highlight(
params: lsp_types::DocumentHighlightParams,
) -> anyhow::Result<Option<Vec<lsp_types::DocumentHighlight>>> {
let _p = tracing::info_span!("handle_document_highlight").entered();
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let position =
try_default!(from_proto::file_position(&snap, params.text_document_position_params)?);
let line_index = snap.file_line_index(position.file_id)?;
let source_root = snap.analysis.source_root_id(position.file_id)?;
@ -1639,12 +1654,12 @@ pub(crate) fn handle_ssr(
params: lsp_ext::SsrParams,
) -> anyhow::Result<lsp_types::WorkspaceEdit> {
let _p = tracing::info_span!("handle_ssr").entered();
let selections = params
let selections = try_default!(params
.selections
.iter()
.map(|range| from_proto::file_range(&snap, &params.position.text_document, *range))
.collect::<Result<Vec<_>, _>>()?;
let position = from_proto::file_position(&snap, params.position)?;
.collect::<Result<Option<Vec<_>>, _>>()?);
let position = try_default!(from_proto::file_position(&snap, params.position)?);
let source_change = snap.analysis.structural_search_replace(
&params.query,
params.parse_only,
@ -1660,11 +1675,11 @@ pub(crate) fn handle_inlay_hints(
) -> anyhow::Result<Option<Vec<InlayHint>>> {
let _p = tracing::info_span!("handle_inlay_hints").entered();
let document_uri = &params.text_document.uri;
let FileRange { file_id, range } = from_proto::file_range(
let FileRange { file_id, range } = try_default!(from_proto::file_range(
&snap,
&TextDocumentIdentifier::new(document_uri.to_owned()),
params.range,
)?;
)?);
let line_index = snap.file_line_index(file_id)?;
let range = TextRange::new(
range.start().min(line_index.index.len()),
@ -1744,7 +1759,8 @@ pub(crate) fn handle_call_hierarchy_prepare(
params: CallHierarchyPrepareParams,
) -> anyhow::Result<Option<Vec<CallHierarchyItem>>> {
let _p = tracing::info_span!("handle_call_hierarchy_prepare").entered();
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let position =
try_default!(from_proto::file_position(&snap, params.text_document_position_params)?);
let nav_info = match snap.analysis.call_hierarchy(position)? {
None => return Ok(None),
@ -1769,7 +1785,7 @@ pub(crate) fn handle_call_hierarchy_incoming(
let item = params.item;
let doc = TextDocumentIdentifier::new(item.uri);
let frange = from_proto::file_range(&snap, &doc, item.selection_range)?;
let frange = try_default!(from_proto::file_range(&snap, &doc, item.selection_range)?);
let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
let config = snap.config.call_hierarchy();
@ -1807,7 +1823,7 @@ pub(crate) fn handle_call_hierarchy_outgoing(
let item = params.item;
let doc = TextDocumentIdentifier::new(item.uri);
let frange = from_proto::file_range(&snap, &doc, item.selection_range)?;
let frange = try_default!(from_proto::file_range(&snap, &doc, item.selection_range)?);
let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
let line_index = snap.file_line_index(fpos.file_id)?;
@ -1842,7 +1858,7 @@ pub(crate) fn handle_semantic_tokens_full(
) -> anyhow::Result<Option<SemanticTokensResult>> {
let _p = tracing::info_span!("handle_semantic_tokens_full").entered();
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let text = snap.analysis.file_text(file_id)?;
let line_index = snap.file_line_index(file_id)?;
@ -1872,7 +1888,7 @@ pub(crate) fn handle_semantic_tokens_full_delta(
) -> anyhow::Result<Option<SemanticTokensFullDeltaResult>> {
let _p = tracing::info_span!("handle_semantic_tokens_full_delta").entered();
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let text = snap.analysis.file_text(file_id)?;
let line_index = snap.file_line_index(file_id)?;
@ -1915,7 +1931,7 @@ pub(crate) fn handle_semantic_tokens_range(
) -> anyhow::Result<Option<SemanticTokensRangeResult>> {
let _p = tracing::info_span!("handle_semantic_tokens_range").entered();
let frange = from_proto::file_range(&snap, &params.text_document, params.range)?;
let frange = try_default!(from_proto::file_range(&snap, &params.text_document, params.range)?);
let text = snap.analysis.file_text(frange.file_id)?;
let line_index = snap.file_line_index(frange.file_id)?;
@ -1940,7 +1956,7 @@ pub(crate) fn handle_open_docs(
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<ExternalDocsResponse> {
let _p = tracing::info_span!("handle_open_docs").entered();
let position = from_proto::file_position(&snap, params)?;
let position = try_default!(from_proto::file_position(&snap, params)?);
let ws_and_sysroot = snap.workspaces.iter().find_map(|ws| match &ws.kind {
ProjectWorkspaceKind::Cargo { cargo, .. }
@ -1982,7 +1998,7 @@ pub(crate) fn handle_open_cargo_toml(
params: lsp_ext::OpenCargoTomlParams,
) -> anyhow::Result<Option<lsp_types::GotoDefinitionResponse>> {
let _p = tracing::info_span!("handle_open_cargo_toml").entered();
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let cargo_spec = match TargetSpec::for_file(&snap, file_id)? {
Some(TargetSpec::Cargo(it)) => it,
@ -2000,8 +2016,8 @@ pub(crate) fn handle_move_item(
params: lsp_ext::MoveItemParams,
) -> anyhow::Result<Vec<lsp_ext::SnippetTextEdit>> {
let _p = tracing::info_span!("handle_move_item").entered();
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let range = from_proto::file_range(&snap, &params.text_document, params.range)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let range = try_default!(from_proto::file_range(&snap, &params.text_document, params.range)?);
let direction = match params.direction {
lsp_ext::MoveItemDirection::Up => ide::Direction::Up,
@ -2022,7 +2038,7 @@ pub(crate) fn handle_view_recursive_memory_layout(
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<Option<lsp_ext::RecursiveMemoryLayout>> {
let _p = tracing::info_span!("handle_view_recursive_memory_layout").entered();
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let file_id = try_default!(from_proto::file_id(&snap, &params.text_document.uri)?);
let line_index = snap.file_line_index(file_id)?;
let offset = from_proto::offset(&line_index, params.position)?;
@ -2210,7 +2226,7 @@ fn run_rustfmt(
text_document: TextDocumentIdentifier,
range: Option<lsp_types::Range>,
) -> anyhow::Result<Option<Vec<lsp_types::TextEdit>>> {
let file_id = from_proto::file_id(snap, &text_document.uri)?;
let file_id = try_default!(from_proto::file_id(snap, &text_document.uri)?);
let file = snap.analysis.file_text(file_id)?;
// Determine the edition of the crate the file belongs to (if there's multiple, we pick the
@ -2275,7 +2291,7 @@ fn run_rustfmt(
.into());
}
let frange = from_proto::file_range(snap, &text_document, range)?;
let frange = try_default!(from_proto::file_range(snap, &text_document, range)?);
let start_line = line_index.index.line_col(frange.range.start()).line;
let end_line = line_index.index.line_col(frange.range.end()).line;
@ -2417,15 +2433,15 @@ pub(crate) fn internal_testing_fetch_config(
state: GlobalStateSnapshot,
params: InternalTestingFetchConfigParams,
) -> anyhow::Result<Option<InternalTestingFetchConfigResponse>> {
let source_root = params
.text_document
.map(|it| {
let source_root = match params.text_document {
Some(it) => Some(
state
.analysis
.source_root_id(from_proto::file_id(&state, &it.uri)?)
.map_err(anyhow::Error::from)
})
.transpose()?;
.source_root_id(try_default!(from_proto::file_id(&state, &it.uri)?))
.map_err(anyhow::Error::from)?,
),
None => None,
};
Ok(Some(match params.config {
InternalTestingFetchConfigOption::AssistEmitMustUse => {
InternalTestingFetchConfigResponse::AssistEmitMustUse(

View file

@ -25,6 +25,14 @@ use vfs::{AbsPathBuf, VfsPath};
use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
#[track_caller]
fn file_id(vfs: &vfs::Vfs, path: &VfsPath) -> vfs::FileId {
match vfs.file_id(path) {
Some((file_id, vfs::FileExcluded::No)) => file_id,
None | Some((_, vfs::FileExcluded::Yes)) => panic!("can't find virtual file for {path}"),
}
}
#[test]
fn integrated_highlighting_benchmark() {
if std::env::var("RUN_SLOW_BENCHES").is_err() {
@ -62,7 +70,7 @@ fn integrated_highlighting_benchmark() {
let file_id = {
let file = workspace_to_load.join(file);
let path = VfsPath::from(AbsPathBuf::assert(file));
vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {path}"))
file_id(&vfs, &path)
};
{
@ -130,7 +138,7 @@ fn integrated_completion_benchmark() {
let file_id = {
let file = workspace_to_load.join(file);
let path = VfsPath::from(AbsPathBuf::assert(file));
vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {path}"))
file_id(&vfs, &path)
};
// kick off parsing and index population
@ -324,7 +332,7 @@ fn integrated_diagnostics_benchmark() {
let file_id = {
let file = workspace_to_load.join(file);
let path = VfsPath::from(AbsPathBuf::assert(file));
vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {path}"))
file_id(&vfs, &path)
};
let diagnostics_config = DiagnosticsConfig {

View file

@ -173,3 +173,14 @@ fn completion_item_hash(item: &CompletionItem, is_ref_completion: bool) -> [u8;
hasher.finalize()
}
#[doc(hidden)]
macro_rules! try_default_ {
($it:expr $(,)?) => {
match $it {
Some(it) => it,
None => return Ok(Default::default()),
}
};
}
pub(crate) use try_default_ as try_default;

View file

@ -9,7 +9,7 @@ use vfs::AbsPathBuf;
use crate::{
global_state::GlobalStateSnapshot,
line_index::{LineIndex, PositionEncoding},
lsp_ext,
lsp_ext, try_default,
};
pub(crate) fn abs_path(url: &lsp_types::Url) -> anyhow::Result<AbsPathBuf> {
@ -61,37 +61,44 @@ pub(crate) fn text_range(
}
}
pub(crate) fn file_id(snap: &GlobalStateSnapshot, url: &lsp_types::Url) -> anyhow::Result<FileId> {
/// Returns `None` if the file was excluded.
pub(crate) fn file_id(
snap: &GlobalStateSnapshot,
url: &lsp_types::Url,
) -> anyhow::Result<Option<FileId>> {
snap.url_to_file_id(url)
}
/// Returns `None` if the file was excluded.
pub(crate) fn file_position(
snap: &GlobalStateSnapshot,
tdpp: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<FilePosition> {
let file_id = file_id(snap, &tdpp.text_document.uri)?;
) -> anyhow::Result<Option<FilePosition>> {
let file_id = try_default!(file_id(snap, &tdpp.text_document.uri)?);
let line_index = snap.file_line_index(file_id)?;
let offset = offset(&line_index, tdpp.position)?;
Ok(FilePosition { file_id, offset })
Ok(Some(FilePosition { file_id, offset }))
}
/// Returns `None` if the file was excluded.
pub(crate) fn file_range(
snap: &GlobalStateSnapshot,
text_document_identifier: &lsp_types::TextDocumentIdentifier,
range: lsp_types::Range,
) -> anyhow::Result<FileRange> {
) -> anyhow::Result<Option<FileRange>> {
file_range_uri(snap, &text_document_identifier.uri, range)
}
/// Returns `None` if the file was excluded.
pub(crate) fn file_range_uri(
snap: &GlobalStateSnapshot,
document: &lsp_types::Url,
range: lsp_types::Range,
) -> anyhow::Result<FileRange> {
let file_id = file_id(snap, document)?;
) -> anyhow::Result<Option<FileRange>> {
let file_id = try_default!(file_id(snap, document)?);
let line_index = snap.file_line_index(file_id)?;
let range = text_range(&line_index, range)?;
Ok(FileRange { file_id, range })
Ok(Some(FileRange { file_id, range }))
}
pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind> {
@ -108,6 +115,7 @@ pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind>
Some(assist_kind)
}
/// Returns `None` if the file was excluded.
pub(crate) fn annotation(
snap: &GlobalStateSnapshot,
range: lsp_types::Range,
@ -121,7 +129,7 @@ pub(crate) fn annotation(
return Ok(None);
}
let pos @ FilePosition { file_id, .. } =
file_position(snap, params.text_document_position_params)?;
try_default!(file_position(snap, params.text_document_position_params)?);
let line_index = snap.file_line_index(file_id)?;
Ok(Annotation {
@ -133,7 +141,7 @@ pub(crate) fn annotation(
if snap.url_file_version(&params.text_document.uri) != Some(data.version) {
return Ok(None);
}
let pos @ FilePosition { file_id, .. } = file_position(snap, params)?;
let pos @ FilePosition { file_id, .. } = try_default!(file_position(snap, params)?);
let line_index = snap.file_line_index(file_id)?;
Ok(Annotation {

View file

@ -27,7 +27,10 @@ use crate::{
FetchWorkspaceResponse, GlobalState,
},
hack_recover_crate_name,
handlers::dispatch::{NotificationDispatcher, RequestDispatcher},
handlers::{
dispatch::{NotificationDispatcher, RequestDispatcher},
request::empty_diagnostic_report,
},
lsp::{
from_proto, to_proto,
utils::{notification_is, Progress},
@ -548,6 +551,9 @@ impl GlobalState {
self.mem_docs
.iter()
.map(|path| vfs.file_id(path).unwrap())
.filter_map(|(file_id, excluded)| {
(excluded == vfs::FileExcluded::No).then_some(file_id)
})
.filter(|&file_id| {
let source_root = db.file_source_root(file_id);
// Only publish diagnostics for files in the workspace, not from crates.io deps
@ -632,6 +638,9 @@ impl GlobalState {
.mem_docs
.iter()
.map(|path| self.vfs.read().0.file_id(path).unwrap())
.filter_map(|(file_id, excluded)| {
(excluded == vfs::FileExcluded::No).then_some(file_id)
})
.filter(|&file_id| {
let source_root = db.file_source_root(file_id);
!db.source_root(source_root).is_library
@ -879,7 +888,10 @@ impl GlobalState {
self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |sender| {
let _p = tracing::info_span!("GlobalState::check_if_indexed").entered();
tracing::debug!(?uri, "handling uri");
let id = from_proto::file_id(&snap, &uri).expect("unable to get FileId");
let Some(id) = from_proto::file_id(&snap, &uri).expect("unable to get FileId")
else {
return;
};
if let Ok(crates) = &snap.analysis.crates_for(id) {
if crates.is_empty() {
if snap.config.discover_workspace_config().is_some() {
@ -987,13 +999,14 @@ impl GlobalState {
);
for diag in diagnostics {
match url_to_file_id(&self.vfs.read().0, &diag.url) {
Ok(file_id) => self.diagnostics.add_check_diagnostic(
Ok(Some(file_id)) => self.diagnostics.add_check_diagnostic(
id,
&package_id,
file_id,
diag.diagnostic,
diag.fix,
),
Ok(None) => {}
Err(err) => {
error!(
"flycheck {id}: File with cargo diagnostic not found in VFS: {}",
@ -1115,17 +1128,7 @@ impl GlobalState {
.on_latency_sensitive::<NO_RETRY, lsp_request::SemanticTokensRangeRequest>(handlers::handle_semantic_tokens_range)
// FIXME: Some of these NO_RETRY could be retries if the file they are interested didn't change.
// All other request handlers
.on_with_vfs_default::<lsp_request::DocumentDiagnosticRequest>(handlers::handle_document_diagnostics, || lsp_types::DocumentDiagnosticReportResult::Report(
lsp_types::DocumentDiagnosticReport::Full(
lsp_types::RelatedFullDocumentDiagnosticReport {
related_documents: None,
full_document_diagnostic_report: lsp_types::FullDocumentDiagnosticReport {
result_id: Some("rust-analyzer".to_owned()),
items: vec![],
},
},
),
), || lsp_server::ResponseError {
.on_with_vfs_default::<lsp_request::DocumentDiagnosticRequest>(handlers::handle_document_diagnostics, empty_diagnostic_report, || lsp_server::ResponseError {
code: lsp_server::ErrorCode::ServerCancelled as i32,
message: "server cancelled the request".to_owned(),
data: serde_json::to_value(lsp_types::DiagnosticServerCancellationData {

View file

@ -705,7 +705,9 @@ impl GlobalState {
let load = |path: &AbsPath| {
let vfs_path = vfs::VfsPath::from(path.to_path_buf());
self.crate_graph_file_dependencies.insert(vfs_path.clone());
vfs.file_id(&vfs_path)
vfs.file_id(&vfs_path).and_then(|(file_id, excluded)| {
(excluded == vfs::FileExcluded::No).then_some(file_id)
})
};
ws_to_crate_graph(&self.workspaces, self.config.extra_env(None), load)

View file

@ -1438,6 +1438,40 @@ pub fn foo() {}
name = "bar"
version = "0.0.0"
[dependencies]
foo = { path = "../foo" }
//- /bar/src/lib.rs
"#,
)
.root("foo")
.root("bar")
.root("baz")
.with_config(json!({
"files": {
"exclude": ["foo"]
}
}))
.server()
.wait_until_workspace_is_loaded();
server.request::<WorkspaceSymbolRequest>(Default::default(), json!([]));
let server = Project::with_fixture(
r#"
//- /foo/Cargo.toml
[package]
name = "foo"
version = "0.0.0"
//- /foo/src/lib.rs
pub fn foo() {}
//- /bar/Cargo.toml
[package]
name = "bar"
version = "0.0.0"
//- /bar/src/lib.rs
pub fn bar() {}
@ -1454,7 +1488,7 @@ version = "0.0.0"
.root("baz")
.with_config(json!({
"files": {
"excludeDirs": ["foo", "bar"]
"exclude": ["foo", "bar"]
}
}))
.server()

View file

@ -280,8 +280,9 @@ impl NotifyActor {
return false;
}
root == path
|| dirs.exclude.iter().chain(&dirs.include).all(|it| it != path)
// We want to filter out subdirectories that are roots themselves, because they will be visited separately.
dirs.exclude.iter().all(|it| it != path)
&& (root == path || dirs.include.iter().all(|it| it != path))
});
let files = walkdir.filter_map(|it| it.ok()).filter_map(|entry| {

View file

@ -100,6 +100,9 @@ pub enum FileState {
Exists(u64),
/// The file is deleted.
Deleted,
/// The file was specifically excluded by the user. We still include excluded files
/// when they're opened (without their contents).
Excluded,
}
/// Changed file in the [`Vfs`].
@ -164,10 +167,22 @@ pub enum ChangeKind {
Delete,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileExcluded {
Yes,
No,
}
impl Vfs {
/// Id of the given path if it exists in the `Vfs` and is not deleted.
pub fn file_id(&self, path: &VfsPath) -> Option<FileId> {
self.interner.get(path).filter(|&it| matches!(self.get(it), FileState::Exists(_)))
pub fn file_id(&self, path: &VfsPath) -> Option<(FileId, FileExcluded)> {
let file_id = self.interner.get(path)?;
let file_state = self.get(file_id);
match file_state {
FileState::Exists(_) => Some((file_id, FileExcluded::No)),
FileState::Deleted => None,
FileState::Excluded => Some((file_id, FileExcluded::Yes)),
}
}
/// File path corresponding to the given `file_id`.
@ -216,6 +231,7 @@ impl Vfs {
}
Change::Modify(v, new_hash)
}
(FileState::Excluded, _) => return false,
};
let mut set_data = |change_kind| {
@ -297,6 +313,13 @@ impl Vfs {
fn get(&self, file_id: FileId) -> FileState {
self.data[file_id.0 as usize]
}
/// We cannot ignore excluded files, because this will lead to errors when the client
/// requests semantic information for them, so we instead mark them specially.
pub fn insert_excluded_file(&mut self, path: VfsPath) {
let file_id = self.alloc_file_id(path);
self.data[file_id.0 as usize] = FileState::Excluded;
}
}
impl fmt::Debug for Vfs {