Detailed changes
@@ -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",
@@ -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;
@@ -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)
}
@@ -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);
}
@@ -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())
@@ -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,7 +4491,11 @@ impl File for TestFile {
}
fn as_local(&self) -> Option<&dyn LocalFile> {
- None
+ if self.local_root.is_some() {
+ Some(self)
+ } else {
+ None
+ }
}
fn disk_state(&self) -> DiskState {
@@ -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,
@@ -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,
})
}
@@ -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> {