Support absolute disabled_globs (#25755)

Closes: #25556

We were always comparing `disabled_globs` against the relative file
path, we'll now use the absolute path if the glob is also absolute.

Release Notes:

- Support absolute globs in `edit_predictions.disabled_globs`
This commit is contained in:
Agus Zubiaga 2025-02-27 15:29:32 -03:00 committed by GitHub
parent c5632f8c31
commit 6eb2ffe77a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 191 additions and 16 deletions

View file

@ -829,6 +829,8 @@
// A list of globs representing files that edit predictions should be disabled for.
// There's a sensible default list of globs already included.
// Any addition to this list will be merged with the default list.
// Globs are matched relative to the worktree root,
// except when starting with a slash (/) or equivalent in Windows.
"disabled_globs": [
"**/.env*",
"**/*.pem",

View file

@ -192,7 +192,7 @@ impl EditPredictionProvider for CopilotCompletionProvider {
fn discard(&mut self, cx: &mut Context<Self>) {
let settings = AllLanguageSettings::get_global(cx);
let copilot_enabled = settings.show_inline_completions(None, cx);
let copilot_enabled = settings.show_edit_predictions(None, cx);
if !copilot_enabled {
return;

View file

@ -5005,7 +5005,7 @@ impl Editor {
return Some(true);
};
let settings = all_language_settings(Some(file), cx);
Some(settings.inline_completions_enabled_for_path(file.path()))
Some(settings.edit_predictions_enabled_for_file(file, cx))
})
.unwrap_or(false)
}

View file

@ -1739,6 +1739,7 @@ mod tests {
let file = TestFile {
path: Path::new("").into(),
root_name: String::new(),
local_root: None,
};
assert_eq!(path_for_file(&file, 0, false, cx), None);
}

View file

@ -456,7 +456,7 @@ impl InlineCompletionButton {
}
let settings = AllLanguageSettings::get_global(cx);
let globally_enabled = settings.show_inline_completions(None, cx);
let globally_enabled = settings.show_edit_predictions(None, cx);
menu = menu.toggleable_entry("All Files", globally_enabled, IconPosition::Start, None, {
let fs = fs.clone();
move |_, cx| toggle_inline_completions_globally(fs.clone(), cx)
@ -702,7 +702,7 @@ impl InlineCompletionButton {
Some(
file.map(|file| {
all_language_settings(Some(file), cx)
.inline_completions_enabled_for_path(file.path())
.edit_predictions_enabled_for_file(file, cx)
})
.unwrap_or(true),
)
@ -825,7 +825,7 @@ async fn open_disabled_globs_setting_in_editor(
}
fn toggle_inline_completions_globally(fs: Arc<dyn Fs>, cx: &mut App) {
let show_edit_predictions = all_language_settings(None, cx).show_inline_completions(None, cx);
let show_edit_predictions = all_language_settings(None, cx).show_edit_predictions(None, cx);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.defaults.show_edit_predictions = Some(!show_edit_predictions)
});
@ -845,7 +845,7 @@ fn toggle_show_inline_completions_for_language(
cx: &mut App,
) {
let show_edit_predictions =
all_language_settings(None, cx).show_inline_completions(Some(&language), cx);
all_language_settings(None, cx).show_edit_predictions(Some(&language), cx);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.languages
.entry(language.name())

View file

@ -4477,6 +4477,7 @@ impl IndentSize {
pub struct TestFile {
pub path: Arc<Path>,
pub root_name: String,
pub local_root: Option<PathBuf>,
}
#[cfg(any(test, feature = "test-support"))]
@ -4490,8 +4491,12 @@ impl File for TestFile {
}
fn as_local(&self) -> Option<&dyn LocalFile> {
if self.local_root.is_some() {
Some(self)
} else {
None
}
}
fn disk_state(&self) -> DiskState {
unimplemented!()
@ -4518,6 +4523,23 @@ impl File for TestFile {
}
}
#[cfg(any(test, feature = "test-support"))]
impl LocalFile for TestFile {
fn abs_path(&self, _cx: &App) -> PathBuf {
PathBuf::from(self.local_root.as_ref().unwrap())
.join(&self.root_name)
.join(self.path.as_ref())
}
fn load(&self, _cx: &App) -> Task<Result<String>> {
unimplemented!()
}
fn load_bytes(&self, _cx: &App) -> Task<Result<Vec<u8>>> {
unimplemented!()
}
}
pub(crate) fn contiguous_ranges(
values: impl Iterator<Item = u32>,
max_len: usize,

View file

@ -254,6 +254,7 @@ fn file(path: &str) -> Arc<dyn File> {
Arc::new(TestFile {
path: Path::new(path).into(),
root_name: "zed".into(),
local_root: None,
})
}

View file

@ -231,13 +231,33 @@ pub struct EditPredictionSettings {
/// A list of globs representing files that edit predictions should be disabled for.
/// This list adds to a pre-existing, sensible default set of globs.
/// Any additional ones you add are combined with them.
pub disabled_globs: Vec<GlobMatcher>,
pub disabled_globs: Vec<DisabledGlob>,
/// Configures how edit predictions are displayed in the buffer.
pub mode: EditPredictionsMode,
/// Settings specific to GitHub Copilot.
pub copilot: CopilotSettings,
}
impl EditPredictionSettings {
/// Returns whether edit predictions are enabled for the given path.
pub fn enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
!self.disabled_globs.iter().any(|glob| {
if glob.is_absolute {
file.as_local()
.map_or(false, |local| glob.matcher.is_match(local.abs_path(cx)))
} else {
glob.matcher.is_match(file.path())
}
})
}
}
#[derive(Clone, Debug)]
pub struct DisabledGlob {
matcher: GlobMatcher,
is_absolute: bool,
}
/// The mode in which edit predictions should be displayed.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
@ -965,16 +985,12 @@ impl AllLanguageSettings {
}
/// Returns whether edit predictions are enabled for the given path.
pub fn inline_completions_enabled_for_path(&self, path: &Path) -> bool {
!self
.edit_predictions
.disabled_globs
.iter()
.any(|glob| glob.is_match(path))
pub fn edit_predictions_enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
self.edit_predictions.enabled_for_file(file, cx)
}
/// Returns whether edit predictions are enabled for the given language and path.
pub fn show_inline_completions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
pub fn show_edit_predictions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
self.language(None, language.map(|l| l.name()).as_ref(), cx)
.show_edit_predictions
}
@ -1199,7 +1215,12 @@ impl settings::Settings for AllLanguageSettings {
},
disabled_globs: completion_globs
.iter()
.filter_map(|g| Some(globset::Glob::new(g).ok()?.compile_matcher()))
.filter_map(|g| {
Some(DisabledGlob {
matcher: globset::Glob::new(g).ok()?.compile_matcher(),
is_absolute: Path::new(g).is_absolute(),
})
})
.collect(),
mode: edit_predictions_mode,
copilot: copilot_settings,
@ -1357,6 +1378,8 @@ pub struct PrettierSettings {
#[cfg(test)]
mod tests {
use gpui::TestAppContext;
use super::*;
#[test]
@ -1401,6 +1424,132 @@ mod tests {
assert!(result.is_err());
}
#[gpui::test]
fn test_edit_predictions_enabled_for_file(cx: &mut TestAppContext) {
use crate::TestFile;
use std::path::PathBuf;
let cx = cx.app.borrow_mut();
let build_settings = |globs: &[&str]| -> EditPredictionSettings {
EditPredictionSettings {
disabled_globs: globs
.iter()
.map(|glob_str| {
#[cfg(windows)]
let glob_str = {
let mut g = String::new();
if glob_str.starts_with('/') {
g.push_str("C:");
}
g.push_str(&glob_str.replace('/', "\\"));
g
};
#[cfg(windows)]
let glob_str = glob_str.as_str();
DisabledGlob {
matcher: globset::Glob::new(glob_str).unwrap().compile_matcher(),
is_absolute: Path::new(glob_str).is_absolute(),
}
})
.collect(),
..Default::default()
}
};
const WORKTREE_NAME: &str = "project";
let make_test_file = |segments: &[&str]| -> Arc<dyn File> {
let mut path_buf = PathBuf::new();
path_buf.extend(segments);
Arc::new(TestFile {
path: path_buf.as_path().into(),
root_name: WORKTREE_NAME.to_string(),
local_root: Some(PathBuf::from(if cfg!(windows) {
"C:\\absolute\\"
} else {
"/absolute/"
})),
})
};
let test_file = make_test_file(&["src", "test", "file.rs"]);
// Test relative globs
let settings = build_settings(&["*.rs"]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let settings = build_settings(&["*.txt"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test absolute globs
let settings = build_settings(&["/absolute/**/*.rs"]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let settings = build_settings(&["/other/**/*.rs"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test exact path match relative
let settings = build_settings(&["src/test/file.rs"]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let settings = build_settings(&["src/test/otherfile.rs"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test exact path match absolute
let settings = build_settings(&[&format!("/absolute/{}/src/test/file.rs", WORKTREE_NAME)]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let settings = build_settings(&["/other/test/otherfile.rs"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test * glob
let settings = build_settings(&["*"]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let settings = build_settings(&["*.txt"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test **/* glob
let settings = build_settings(&["**/*"]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let settings = build_settings(&["other/**/*"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test directory/** glob
let settings = build_settings(&["src/**"]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let test_file_root: Arc<dyn File> = Arc::new(TestFile {
path: PathBuf::from("file.rs").as_path().into(),
root_name: WORKTREE_NAME.to_string(),
local_root: Some(PathBuf::from("/absolute/")),
});
assert!(settings.enabled_for_file(&test_file_root, &cx));
let settings = build_settings(&["other/**"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test **/directory/* glob
let settings = build_settings(&["**/test/*"]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let settings = build_settings(&["**/other/*"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test multiple globs
let settings = build_settings(&["*.rs", "*.txt", "src/**"]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let settings = build_settings(&["*.txt", "*.md", "other/**"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test dot files
let dot_file = make_test_file(&[".config", "settings.json"]);
let settings = build_settings(&[".*/**"]);
assert!(!settings.enabled_for_file(&dot_file, &cx));
let dot_env_file = make_test_file(&[".env"]);
let settings = build_settings(&[".env"]);
assert!(!settings.enabled_for_file(&dot_env_file, &cx));
}
#[test]
pub fn test_resolve_language_servers() {
fn language_server_names(names: &[&str]) -> Vec<LanguageServerName> {