Build settings index in parallel for the native server (#12299)

## Summary

This PR updates the server to build the settings index in parallel using
similar logic as `python_files_in_path`.

This should help with https://github.com/astral-sh/ruff/issues/11366 but
ideally we would want to build it lazily.

## Test Plan

`cargo insta test`
This commit is contained in:
Dhruv Manilawala 2024-07-15 15:27:54 +05:30 committed by GitHub
parent b9a8cd390f
commit ecd4b4d943
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 82 additions and 62 deletions

View file

@ -1,3 +1,9 @@
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use ignore::{WalkBuilder, WalkState};
use ruff_linter::{
display_settings, fs::normalize_path_to, settings::types::FilePattern,
settings::types::PreviewMode,
@ -8,12 +14,6 @@ use ruff_workspace::{
pyproject::{find_user_settings_toml, settings_toml},
resolver::{ConfigurationTransformer, Relativity},
};
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
sync::Arc,
};
use walkdir::WalkDir;
use crate::session::settings::{ConfigurationPreference, ResolvedEditorSettings};
@ -30,7 +30,7 @@ pub struct RuffSettings {
}
pub(super) struct RuffSettingsIndex {
/// Index from folder to the resoled ruff settings.
/// Index from folder to the resolved ruff settings.
index: BTreeMap<PathBuf, Arc<RuffSettings>>,
fallback: Arc<RuffSettings>,
}
@ -100,6 +100,7 @@ impl RuffSettings {
impl RuffSettingsIndex {
pub(super) fn new(root: &Path, editor_settings: &ResolvedEditorSettings) -> Self {
let mut index = BTreeMap::default();
let mut respect_gitignore = true;
// Add any settings from above the workspace root.
for directory in root.ancestors() {
@ -112,6 +113,7 @@ impl RuffSettingsIndex {
continue;
};
respect_gitignore = settings.file_resolver.respect_gitignore;
index.insert(
directory.to_path_buf(),
Arc::new(RuffSettings {
@ -126,70 +128,88 @@ impl RuffSettingsIndex {
}
// Add any settings within the workspace itself
let mut walker = WalkDir::new(root).into_iter();
let mut builder = WalkBuilder::new(root);
builder.standard_filters(respect_gitignore);
builder.hidden(false);
builder.threads(
std::thread::available_parallelism()
.map_or(1, std::num::NonZeroUsize::get)
.min(12),
);
let walker = builder.build_parallel();
while let Some(entry) = walker.next() {
let Ok(entry) = entry else {
continue;
};
let index = std::sync::RwLock::new(index);
walker.run(|| {
Box::new(|result| {
let Ok(entry) = result else {
return WalkState::Continue;
};
// Skip non-directories.
if !entry.file_type().is_dir() {
continue;
}
let directory = entry.into_path();
// If the directory is excluded from the workspace, skip it.
if let Some(file_name) = directory.file_name() {
if let Some((_, settings)) = index
.range(..directory.clone())
.rfind(|(path, _)| directory.starts_with(path))
// Skip non-directories.
if !entry
.file_type()
.is_some_and(|file_type| file_type.is_dir())
{
if match_exclusion(&directory, file_name, &settings.file_resolver.exclude) {
tracing::debug!("Ignored path via `exclude`: {}", directory.display());
return WalkState::Continue;
}
walker.skip_current_dir();
continue;
} else if match_exclusion(
&directory,
file_name,
&settings.file_resolver.extend_exclude,
) {
tracing::debug!(
"Ignored path via `extend-exclude`: {}",
directory.display()
);
let directory = entry.into_path();
tracing::debug!("Visiting: {}", directory.display());
walker.skip_current_dir();
continue;
// If the directory is excluded from the workspace, skip it.
if let Some(file_name) = directory.file_name() {
if let Some((_, settings)) = index
.read()
.unwrap()
.range(..directory.clone())
.rfind(|(path, _)| directory.starts_with(path))
{
if match_exclusion(&directory, file_name, &settings.file_resolver.exclude) {
tracing::debug!("Ignored path via `exclude`: {}", directory.display());
return WalkState::Continue;
} else if match_exclusion(
&directory,
file_name,
&settings.file_resolver.extend_exclude,
) {
tracing::debug!(
"Ignored path via `extend-exclude`: {}",
directory.display()
);
return WalkState::Continue;
}
}
}
}
if let Some(pyproject) = settings_toml(&directory).ok().flatten() {
let Ok(settings) = ruff_workspace::resolver::resolve_root_settings(
&pyproject,
Relativity::Parent,
&EditorConfigurationTransformer(editor_settings, root),
) else {
continue;
};
index.insert(
directory,
Arc::new(RuffSettings {
path: Some(pyproject),
file_resolver: settings.file_resolver,
linter: settings.linter,
formatter: settings.formatter,
}),
);
}
}
if let Some(pyproject) = settings_toml(&directory).ok().flatten() {
let Ok(settings) = ruff_workspace::resolver::resolve_root_settings(
&pyproject,
Relativity::Parent,
&EditorConfigurationTransformer(editor_settings, root),
) else {
return WalkState::Continue;
};
index.write().unwrap().insert(
directory,
Arc::new(RuffSettings {
path: Some(pyproject),
file_resolver: settings.file_resolver,
linter: settings.linter,
formatter: settings.formatter,
}),
);
}
WalkState::Continue
})
});
let fallback = Arc::new(RuffSettings::fallback(editor_settings, root));
Self { index, fallback }
Self {
index: index.into_inner().unwrap(),
fallback,
}
}
pub(super) fn get(&self, document_path: &Path) -> Arc<RuffSettings> {