Add support for "..." in edit_predictions.disabled_globs (#64540)
## Objective Let users and projects add edit prediction exclusion patterns without repeating defaults or inherited patterns. Part of #64399. ## Solution Extend the `"..."` convention from #62769, #64222, #64440, and #64484 to `edit_predictions.disabled_globs` using `SplicingVec`. Inherited patterns are inserted at `"..."`, and duplicates retain their first occurrence. Omitting the marker replaces the list, `[]` clears it, and omitting the setting preserves inheritance. Opening an absent setting now initializes it with `["..."]` instead of clearing inherited patterns, and the selection is located by parsing the JSON instead of a regex, so a commented-out or nested `"disabled_globs"` can no longer capture it. Cursor imports append `"..."` like the `files.exclude` import does, so Zed's sensitive-file defaults survive an import; empty lists and empty or literal `"..."` entries are dropped, matching `enabled_patterns`. ## Testing Tested on macOS with settings merge and loading tests, Cursor import tests, the UI settings update and selection path (including commented-out and nested decoy keys), the new `settings_json` key-path lookup, and Copilot integration coverage. To check manually, configure custom disabled patterns alongside `"..."` and confirm that both custom patterns and inherited sensitive-file patterns remain disabled. Repeat without `"..."` and with `[]` to verify replacement and clearing. Opening the setting when it is absent should preserve inherited patterns. ## Self-Review Checklist: - [x] I’ve reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed’s UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Added support for `"..."` in `edit_predictions.disabled_globs` so custom patterns can extend inherited exclusions, and fixed "Configure Excluded Files" selecting the wrong list when a commented-out `disabled_globs` precedes it. --------- Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
1 parent
0ddd9c9863
commit
3a7b16da5d
12 files changed
+598
-68
No files matched your search
Generated
-1
@@ -5556,7 +5556,6 @@ dependencies = [
|
||||
"multi_buffer",
|
||||
"paths",
|
||||
"project",
|
||||
"regex",
|
||||
"settings",
|
||||
"telemetry",
|
||||
"text",
|
||||
|
||||
@@ -586,7 +586,7 @@
|
||||
// Whether to show code action buttons in the editor toolbar.
|
||||
"code_actions": false,
|
||||
},
|
||||
// Whether to allow windows to tab together based on the user’s tabbing preference (macOS only).
|
||||
// Whether to allow windows to tab together based on the user's tabbing preference (macOS only).
|
||||
"use_system_window_tabs": false,
|
||||
// Which fullscreen mode the `zed::ToggleFullScreen` action enters (macOS only).
|
||||
//
|
||||
@@ -1873,11 +1873,20 @@
|
||||
"edit_predictions": {
|
||||
// Which edit prediction provider to use.
|
||||
"provider": "zed",
|
||||
// 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.
|
||||
// Disable edit predictions for files matching these glob patterns.
|
||||
//
|
||||
// Use `"..."` to add patterns without repeating Zed's defaults. In project
|
||||
// settings, it extends the user or parent configuration value. Omit
|
||||
// `"..."` to replace the inherited list.
|
||||
//
|
||||
// Inherited patterns are inserted at `"..."`, and duplicates keep their first
|
||||
// occurrence.
|
||||
//
|
||||
// Set `[]` to clear the inherited list. Omit this setting to inherit it unchanged.
|
||||
//
|
||||
// Relative patterns are matched against paths relative to the worktree root.
|
||||
// Absolute patterns are matched against absolute paths.
|
||||
// A leading `~` is expanded to your home folder.
|
||||
"disabled_globs": [
|
||||
"**/.env*",
|
||||
"**/*.pem",
|
||||
|
||||
@@ -246,7 +246,7 @@ mod tests {
|
||||
use lsp::Uri;
|
||||
use project::Project;
|
||||
use serde_json::json;
|
||||
use settings::{AllLanguageSettingsContent, DelayMs, SettingsStore};
|
||||
use settings::{AllLanguageSettingsContent, DelayMs, SettingsStore, SplicingVec};
|
||||
use std::future::Future;
|
||||
use util::{
|
||||
path,
|
||||
@@ -979,7 +979,7 @@ mod tests {
|
||||
settings
|
||||
.edit_predictions
|
||||
.get_or_insert(Default::default())
|
||||
.disabled_globs = Some(vec![".env*".to_string()]);
|
||||
.disabled_globs = Some(SplicingVec::from(vec![".env*".to_string()]));
|
||||
});
|
||||
|
||||
let (copilot, copilot_lsp) = Copilot::fake(cx);
|
||||
|
||||
@@ -36,7 +36,6 @@ menu.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
paths.workspace = true
|
||||
project.workspace = true
|
||||
regex.workspace = true
|
||||
settings.workspace = true
|
||||
telemetry.workspace = true
|
||||
text.workspace = true
|
||||
|
||||
@@ -23,13 +23,11 @@ use language::{
|
||||
},
|
||||
};
|
||||
use project::{DisableAiSettings, Project};
|
||||
use regex::Regex;
|
||||
use settings::{Settings, SettingsStore, update_settings_file};
|
||||
use std::{
|
||||
rc::Rc,
|
||||
sync::{Arc, LazyLock},
|
||||
time::Duration,
|
||||
use settings::{
|
||||
Settings, SettingsContent, SettingsStore, SplicingVec, find_value_range_in_json_text,
|
||||
update_settings_file,
|
||||
};
|
||||
use std::{ops::Range, rc::Rc, sync::Arc, time::Duration};
|
||||
use ui::{
|
||||
Clickable, ContextMenu, ContextMenuEntry, DocumentationSide, IconButton, IconButtonShape,
|
||||
Indicator, PopoverMenu, PopoverMenuHandle, ProgressBar, Tooltip, prelude::*,
|
||||
@@ -1402,6 +1400,25 @@ impl StatusItemView for EditPredictionButton {
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize_disabled_globs_setting(file: &mut SettingsContent) {
|
||||
file.project
|
||||
.all_languages
|
||||
.edit_predictions
|
||||
.get_or_insert_with(Default::default)
|
||||
.disabled_globs
|
||||
.get_or_insert_with(|| SplicingVec::from(vec![SplicingVec::REST.to_string()]));
|
||||
}
|
||||
|
||||
fn disabled_globs_content_range(text: &str) -> Option<Range<usize>> {
|
||||
let array_range = find_value_range_in_json_text(text, &["edit_predictions", "disabled_globs"])?;
|
||||
let content = text
|
||||
.get(array_range.clone())?
|
||||
.strip_prefix('[')?
|
||||
.strip_suffix(']')?;
|
||||
let start = array_range.start + 1 + (content.len() - content.trim_start().len());
|
||||
Some(start..start + content.trim().len())
|
||||
}
|
||||
|
||||
async fn open_disabled_globs_setting_in_editor(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
@@ -1423,16 +1440,8 @@ async fn open_disabled_globs_setting_in_editor(
|
||||
|
||||
let settings = cx.global::<SettingsStore>();
|
||||
|
||||
// Ensure that we always have "edit_predictions { "disabled_globs": [] }"
|
||||
let Some(edits) = settings
|
||||
.edits_for_update(&text, |file| {
|
||||
file.project
|
||||
.all_languages
|
||||
.edit_predictions
|
||||
.get_or_insert_with(Default::default)
|
||||
.disabled_globs
|
||||
.get_or_insert_with(Vec::new);
|
||||
})
|
||||
.edits_for_update(&text, initialize_disabled_globs_setting)
|
||||
.log_err()
|
||||
else {
|
||||
return;
|
||||
@@ -1449,16 +1458,7 @@ async fn open_disabled_globs_setting_in_editor(
|
||||
|
||||
let text = item.buffer().read(cx).snapshot(cx).text();
|
||||
|
||||
static DISABLED_GLOBS_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r#""disabled_globs":\s*\[\s*(?P<content>(?:.|\n)*?)\s*\]"#).unwrap()
|
||||
});
|
||||
// Only capture [...]
|
||||
let range = DISABLED_GLOBS_REGEX.captures(&text).and_then(|captures| {
|
||||
captures
|
||||
.name("content")
|
||||
.map(|inner_match| inner_match.start()..inner_match.end())
|
||||
});
|
||||
if let Some(range) = range {
|
||||
if let Some(range) = disabled_globs_content_range(&text) {
|
||||
let range = MultiBufferOffset(range.start)..MultiBufferOffset(range.end);
|
||||
item.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::newest()),
|
||||
@@ -1686,7 +1686,118 @@ fn copilot_settings_url(enterprise_uri: Option<&str>) -> Arc<str> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use anyhow::Context as _;
|
||||
use gpui::TestAppContext;
|
||||
use settings::RootUserSettings;
|
||||
|
||||
#[test]
|
||||
fn test_disabled_globs_selection_and_replacement() -> Result<()> {
|
||||
for contents in [
|
||||
r#""[.][.][.]""#,
|
||||
r#""**/[ab]/**", "...""#,
|
||||
r#""escaped\"]", "backslash\\", "...""#,
|
||||
r#""escaped\\]", "line\nbreak]", "unicode\u005d""#,
|
||||
"\n \"é[ab]\",\n \"...\"\n ",
|
||||
"// Ignore filenames containing a \" character\n \"**/private/**\"\n",
|
||||
"/* Ignore \" and ] */\n \"**/private/**\"\n",
|
||||
"\"**/private/**\", // Keep \" and ]\n \"...\"\n",
|
||||
r#""**/build/**", "...""#,
|
||||
"",
|
||||
"\n ",
|
||||
] {
|
||||
let mut text = format!(
|
||||
r#"// "disabled_globs": ["commented/**"]
|
||||
{{"languages":{{"Rust":{{"disabled_globs":["nested/**"]}}}},
|
||||
/* "disabled_globs": ["commented/**"] */
|
||||
"edit_predictions":{{"disabled_globs":[{contents}],"mode":"subtle"}}}}"#
|
||||
);
|
||||
let mut expected = SettingsContent::parse_json_with_comments(&text)?;
|
||||
let range = disabled_globs_content_range(&text).context("disabled globs selection")?;
|
||||
assert_eq!(text.get(range.clone()), Some(contents.trim()));
|
||||
|
||||
text.replace_range(range, r#""**/replacement/**", "...""#);
|
||||
expected
|
||||
.project
|
||||
.all_languages
|
||||
.edit_predictions
|
||||
.as_mut()
|
||||
.context("edit prediction settings")?
|
||||
.disabled_globs = Some(SplicingVec::from(vec![
|
||||
"**/replacement/**".to_string(),
|
||||
SplicingVec::REST.to_string(),
|
||||
]));
|
||||
assert_eq!(SettingsContent::parse_json_with_comments(&text)?, expected);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_initialize_disabled_globs_setting(cx: &mut App) {
|
||||
let store = SettingsStore::new(cx, &settings::default_settings());
|
||||
|
||||
let cases: &[(&str, &[&str])] = &[
|
||||
("", &[SplicingVec::REST]),
|
||||
(r#"{}"#, &[SplicingVec::REST]),
|
||||
(r#"{"edit_predictions":{}}"#, &[SplicingVec::REST]),
|
||||
(
|
||||
r#"{"edit_predictions":{"mode":"subtle"}}"#,
|
||||
&[SplicingVec::REST],
|
||||
),
|
||||
(r#"{"edit_predictions":{"disabled_globs":[]}}"#, &[]),
|
||||
(
|
||||
r#"{"edit_predictions":{"disabled_globs":["**/build/**"]}}"#,
|
||||
&["**/build/**"],
|
||||
),
|
||||
(
|
||||
r#"{"edit_predictions":{"disabled_globs":["...","**/build/**"]}}"#,
|
||||
&[SplicingVec::REST, "**/build/**"],
|
||||
),
|
||||
(
|
||||
r#"{"edit_predictions":{"disabled_globs":["[.][.][.]"]}}"#,
|
||||
&["[.][.][.]"],
|
||||
),
|
||||
];
|
||||
for &(content, expected_globs) in cases {
|
||||
let original = if content.is_empty() { "{}" } else { content };
|
||||
let mut expected_content = SettingsContent::parse_json_with_comments(original)
|
||||
.expect("settings content parses");
|
||||
let original_globs = &mut expected_content
|
||||
.project
|
||||
.all_languages
|
||||
.edit_predictions
|
||||
.get_or_insert_with(Default::default)
|
||||
.disabled_globs;
|
||||
let already_configured = original_globs.is_some();
|
||||
*original_globs = Some(SplicingVec::from(
|
||||
expected_globs
|
||||
.iter()
|
||||
.map(|glob| glob.to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
));
|
||||
|
||||
let edits = store
|
||||
.edits_for_update(content, initialize_disabled_globs_setting)
|
||||
.expect("settings edits are generated");
|
||||
assert_eq!(edits.is_empty(), already_configured, "{content}");
|
||||
let mut updated = content.to_string();
|
||||
for (range, replacement) in edits {
|
||||
updated.replace_range(range, &replacement);
|
||||
}
|
||||
assert_eq!(
|
||||
SettingsContent::parse_json_with_comments(&updated)
|
||||
.expect("updated settings parse"),
|
||||
expected_content,
|
||||
"{content}",
|
||||
);
|
||||
assert!(
|
||||
store
|
||||
.edits_for_update(&updated, initialize_disabled_globs_setting)
|
||||
.expect("repeated settings edits are generated")
|
||||
.is_empty(),
|
||||
"{content}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_copilot_settings_url_with_enterprise_uri(cx: &mut TestAppContext) {
|
||||
|
||||
@@ -487,9 +487,28 @@ impl InlayHintSettings {
|
||||
pub struct EditPredictionSettings {
|
||||
/// The provider that supplies edit predictions.
|
||||
pub provider: settings::EditPredictionProvider,
|
||||
/// 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.
|
||||
/// Disable edit predictions for files matching these glob patterns.
|
||||
///
|
||||
/// Use `"..."` to add patterns without repeating Zed's defaults. In project
|
||||
/// settings, it extends the user or parent configuration value. Omit
|
||||
/// `"..."` to replace the inherited list.
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "edit_predictions": {
|
||||
/// "disabled_globs": ["**/build/**", "..."]
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Inherited patterns are inserted at `"..."`, and duplicates keep their first
|
||||
/// occurrence.
|
||||
///
|
||||
/// Set `[]` to clear the inherited list. Omit this setting to inherit it unchanged.
|
||||
///
|
||||
/// Relative patterns are matched against paths relative to the worktree root.
|
||||
/// Absolute patterns are matched against absolute paths. A leading `~` is
|
||||
/// expanded to your home folder.
|
||||
pub disabled_globs: Vec<DisabledGlob>,
|
||||
/// Configures how edit predictions are displayed in the buffer.
|
||||
pub mode: settings::EditPredictionsMode,
|
||||
@@ -918,6 +937,7 @@ impl settings::Settings for AllLanguageSettings {
|
||||
.disabled_globs
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.0
|
||||
.iter()
|
||||
.collect();
|
||||
|
||||
@@ -1096,6 +1116,210 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_edit_predictions_disabled_globs_replace_and_clear(cx: &mut App) {
|
||||
let mut store = SettingsStore::new(cx, &settings::default_settings());
|
||||
store.register_setting::<AllLanguageSettings>();
|
||||
|
||||
for (content, sensitive_enabled, custom_enabled) in [
|
||||
(r#"{}"#, false, true),
|
||||
(
|
||||
r#"{"edit_predictions":{"disabled_globs":["**/build/**"]}}"#,
|
||||
true,
|
||||
false,
|
||||
),
|
||||
(r#"{"edit_predictions":{"disabled_globs":[]}}"#, true, true),
|
||||
(r#"{"edit_predictions":{}}"#, false, true),
|
||||
] {
|
||||
store
|
||||
.set_user_settings(content, cx)
|
||||
.expect("user settings should load");
|
||||
let settings = &store.get::<AllLanguageSettings>(None).edit_predictions;
|
||||
for (path, expected_enabled) in [
|
||||
(".env", sensitive_enabled),
|
||||
("build/output.rs", custom_enabled),
|
||||
("certificates/private.pem", sensitive_enabled),
|
||||
("config/secrets.yml", sensitive_enabled),
|
||||
("src/main.rs", true),
|
||||
] {
|
||||
let file: Arc<dyn File> = Arc::new(crate::TestFile {
|
||||
path: rel_path(path).into(),
|
||||
root_name: "project".to_string(),
|
||||
local_root: None,
|
||||
});
|
||||
assert_eq!(
|
||||
settings.enabled_for_file(&file, cx),
|
||||
expected_enabled,
|
||||
"path: {path}, settings: {content}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_edit_predictions_disabled_globs_inherit_across_settings_files(cx: &mut App) {
|
||||
let mut store = SettingsStore::new(cx, &settings::default_settings());
|
||||
store.register_setting::<AllLanguageSettings>();
|
||||
let worktree_id = WorktreeId::from_usize(1);
|
||||
let root = LocalSettingsPath::InWorktree(rel_path("root").into());
|
||||
let child = LocalSettingsPath::InWorktree(rel_path("root/child").into());
|
||||
|
||||
store
|
||||
.set_user_settings(
|
||||
r#"{"edit_predictions":{"disabled_globs":["**/user/**","..."]}}"#,
|
||||
cx,
|
||||
)
|
||||
.expect("user settings should load");
|
||||
store
|
||||
.set_local_settings(
|
||||
worktree_id,
|
||||
root,
|
||||
LocalSettingsKind::Settings,
|
||||
Some(r#"{"edit_predictions":{"disabled_globs":["**/project/**","..."]}}"#),
|
||||
cx,
|
||||
)
|
||||
.expect("project settings should load");
|
||||
|
||||
for (content, inherits, child_enabled) in [
|
||||
(
|
||||
r#"{"edit_predictions":{"disabled_globs":["**/child/**","...","**/.env*","..."]}}"#,
|
||||
true,
|
||||
false,
|
||||
),
|
||||
(r#"{"edit_predictions":{}}"#, true, true),
|
||||
(
|
||||
r#"{"edit_predictions":{"disabled_globs":["**/child/**"]}}"#,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
(r#"{"edit_predictions":{"disabled_globs":[]}}"#, false, true),
|
||||
] {
|
||||
store
|
||||
.set_local_settings(
|
||||
worktree_id,
|
||||
child.clone(),
|
||||
LocalSettingsKind::Settings,
|
||||
Some(content),
|
||||
cx,
|
||||
)
|
||||
.expect("child settings should load");
|
||||
for (location, expected) in [
|
||||
(None, [false, true, true, false]),
|
||||
(
|
||||
Some(SettingsLocation {
|
||||
worktree_id,
|
||||
path: rel_path("root/file.rs"),
|
||||
}),
|
||||
[false, true, false, false],
|
||||
),
|
||||
(
|
||||
Some(SettingsLocation {
|
||||
worktree_id,
|
||||
path: rel_path("root/child/file.rs"),
|
||||
}),
|
||||
[!inherits, child_enabled, !inherits, !inherits],
|
||||
),
|
||||
] {
|
||||
let settings = &store.get::<AllLanguageSettings>(location).edit_predictions;
|
||||
for (path, enabled) in [
|
||||
".env",
|
||||
"child/output.rs",
|
||||
"project/output.rs",
|
||||
"user/output.rs",
|
||||
]
|
||||
.into_iter()
|
||||
.zip(expected)
|
||||
.chain([("...", true), ("src/main.rs", true)])
|
||||
{
|
||||
let file: Arc<dyn File> = Arc::new(crate::TestFile {
|
||||
path: rel_path(path).into(),
|
||||
root_name: "project".to_string(),
|
||||
local_root: None,
|
||||
});
|
||||
assert_eq!(
|
||||
settings.enabled_for_file(&file, cx),
|
||||
enabled,
|
||||
"path: {path}, settings: {content}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_edit_predictions_disabled_globs_preserve_matching(cx: &mut App) {
|
||||
let mut store = SettingsStore::new(cx, &settings::default_settings());
|
||||
store.register_setting::<AllLanguageSettings>();
|
||||
let absolute_root = if cfg!(windows) {
|
||||
"C:/absolute"
|
||||
} else {
|
||||
"/absolute"
|
||||
};
|
||||
let absolute_pattern = format!("{absolute_root}/project/**/*.rs");
|
||||
let home = shellexpand::tilde("~").into_owned();
|
||||
for (patterns, path, local_root, enabled) in [
|
||||
(
|
||||
vec!["**/src/**", "..."],
|
||||
"src/main.rs",
|
||||
Some(absolute_root),
|
||||
false,
|
||||
),
|
||||
(
|
||||
vec!["**/src/**", "..."],
|
||||
"other/main.rs",
|
||||
Some(absolute_root),
|
||||
true,
|
||||
),
|
||||
(
|
||||
vec![absolute_pattern.as_str(), "..."],
|
||||
"src/main.rs",
|
||||
Some(absolute_root),
|
||||
false,
|
||||
),
|
||||
(
|
||||
vec![absolute_pattern.as_str(), "..."],
|
||||
"src/main.rs",
|
||||
None,
|
||||
true,
|
||||
),
|
||||
(
|
||||
vec!["~/project/**/*.rs", "..."],
|
||||
"src/main.rs",
|
||||
Some(home.as_str()),
|
||||
false,
|
||||
),
|
||||
(
|
||||
vec!["[", "**/src/**", "..."],
|
||||
"src/main.rs",
|
||||
Some(absolute_root),
|
||||
false,
|
||||
),
|
||||
(vec!["[", "..."], ".env", Some(absolute_root), false),
|
||||
(vec!["[", "{"], ".env", Some(absolute_root), true),
|
||||
(vec!["[", "{"], "src/main.rs", Some(absolute_root), true),
|
||||
(vec!["[.][.][.]"], "...", Some(absolute_root), false),
|
||||
] {
|
||||
store
|
||||
.set_user_settings(
|
||||
&serde_json::json!({"edit_predictions": {"disabled_globs": patterns}})
|
||||
.to_string(),
|
||||
cx,
|
||||
)
|
||||
.expect("user settings should load");
|
||||
let file: Arc<dyn File> = Arc::new(crate::TestFile {
|
||||
path: rel_path(path).into(),
|
||||
root_name: "project".to_string(),
|
||||
local_root: local_root.map(std::path::PathBuf::from),
|
||||
});
|
||||
let settings = &store.get::<AllLanguageSettings>(None).edit_predictions;
|
||||
assert_eq!(
|
||||
settings.enabled_for_file(&file, cx),
|
||||
enabled,
|
||||
"path: {path}, patterns: {patterns:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_edit_predictions_enabled_for_file(cx: &mut TestAppContext) {
|
||||
use crate::TestFile;
|
||||
|
||||
@@ -677,19 +677,22 @@ impl VsCodeSettings {
|
||||
}
|
||||
|
||||
fn edit_predictions_settings_content(&self) -> Option<EditPredictionSettingsContent> {
|
||||
let disabled_globs = self
|
||||
let mut disabled_globs = self
|
||||
.read_value("cursor.general.globalCursorIgnoreList")?
|
||||
.as_array()?;
|
||||
.as_array()?
|
||||
.iter()
|
||||
.filter_map(Value::as_str)
|
||||
.filter(|glob| !glob.is_empty() && *glob != SplicingVec::REST)
|
||||
.map(str::to_owned)
|
||||
.collect::<Vec<_>>();
|
||||
if disabled_globs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
disabled_globs.push(SplicingVec::REST.to_owned());
|
||||
|
||||
skip_default(EditPredictionSettingsContent {
|
||||
disabled_globs: skip_default(
|
||||
disabled_globs
|
||||
.iter()
|
||||
.filter_map(|glob| glob.as_str())
|
||||
.map(|s| s.to_string())
|
||||
.collect(),
|
||||
),
|
||||
..Default::default()
|
||||
Some(EditPredictionSettingsContent {
|
||||
disabled_globs: Some(SplicingVec::from(disabled_globs)),
|
||||
..EditPredictionSettingsContent::default()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1235,6 +1238,88 @@ mod tests {
|
||||
.reduce_motion
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_import_disabled_globs_extends_inherited_patterns() -> Result<()> {
|
||||
for (ignore_list, expected_imported, expected_merged) in [
|
||||
(
|
||||
serde_json::json!(["**/build/**", "**/cache/**"]),
|
||||
serde_json::json!(["**/build/**", "**/cache/**", "..."]),
|
||||
serde_json::json!(["**/build/**", "**/cache/**", "**/inherited/**"]),
|
||||
),
|
||||
(
|
||||
serde_json::json!(["**/build/**", "", false, null, 1, {}, []]),
|
||||
serde_json::json!(["**/build/**", "..."]),
|
||||
serde_json::json!(["**/build/**", "**/inherited/**"]),
|
||||
),
|
||||
(
|
||||
serde_json::json!(["...", "**/build/**", false]),
|
||||
serde_json::json!(["**/build/**", "..."]),
|
||||
serde_json::json!(["**/build/**", "**/inherited/**"]),
|
||||
),
|
||||
(
|
||||
serde_json::json!(["**/inherited/**", "**/build/**"]),
|
||||
serde_json::json!(["**/inherited/**", "**/build/**", "..."]),
|
||||
serde_json::json!(["**/inherited/**", "**/build/**"]),
|
||||
),
|
||||
] {
|
||||
let content = serde_json::json!({
|
||||
"cursor.general.globalCursorIgnoreList": ignore_list,
|
||||
});
|
||||
let imported =
|
||||
VsCodeSettings::from_str(&content.to_string(), VsCodeSettingsSource::Cursor)?
|
||||
.settings_content();
|
||||
let imported = imported
|
||||
.project
|
||||
.all_languages
|
||||
.edit_predictions
|
||||
.context("imported edit prediction settings")?;
|
||||
assert_eq!(
|
||||
serde_json::to_value(&imported.disabled_globs)?,
|
||||
expected_imported
|
||||
);
|
||||
|
||||
let mut inherited = EditPredictionSettingsContent {
|
||||
disabled_globs: Some(SplicingVec::from(vec!["**/inherited/**".to_string()])),
|
||||
..Default::default()
|
||||
};
|
||||
inherited.merge_from(&imported);
|
||||
assert_eq!(
|
||||
serde_json::to_value(&inherited.disabled_globs)?,
|
||||
expected_merged
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_import_disabled_globs_omits_empty_results() -> Result<()> {
|
||||
let inherited = AllLanguageSettingsContent {
|
||||
edit_predictions: Some(EditPredictionSettingsContent {
|
||||
disabled_globs: Some(SplicingVec::from(vec!["**/inherited/**".to_string()])),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
for content in [
|
||||
r#"{"cursor.general.globalCursorIgnoreList": "**/build/**"}"#,
|
||||
r#"{"cursor.general.globalCursorIgnoreList": ["..."]}"#,
|
||||
r#"{"cursor.general.globalCursorIgnoreList": [""]}"#,
|
||||
r#"{"cursor.general.globalCursorIgnoreList": ["...", "", false, null]}"#,
|
||||
r#"{"cursor.general.globalCursorIgnoreList": []}"#,
|
||||
r#"{"cursor.general.globalCursorIgnoreList": [false, null, 1, {}, []]}"#,
|
||||
r#"{"cursor.general.globalCursorIgnoreList": null}"#,
|
||||
r#"{}"#,
|
||||
] {
|
||||
let imported =
|
||||
VsCodeSettings::from_str(content, VsCodeSettingsSource::Cursor)?.settings_content();
|
||||
assert_eq!(imported.project.all_languages.edit_predictions, None);
|
||||
let mut unchanged = inherited.clone();
|
||||
unchanged.merge_from(&imported.project.all_languages);
|
||||
assert_eq!(unchanged.edit_predictions, inherited.edit_predictions);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_import_file_exclusions() -> Result<()> {
|
||||
let imported = VsCodeSettings::from_str(
|
||||
|
||||
@@ -7,7 +7,8 @@ use settings_macros::{MergeFrom, with_fallible_options};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
DelayMs, DocumentFoldingRanges, DocumentSymbols, ExtendingSet, SemanticTokens, merge_from,
|
||||
DelayMs, DocumentFoldingRanges, DocumentSymbols, ExtendingSet, SemanticTokens, SplicingVec,
|
||||
merge_from,
|
||||
};
|
||||
|
||||
/// The state of the modifier keys at some point in time
|
||||
@@ -132,10 +133,29 @@ impl EditPredictionProvider {
|
||||
pub struct EditPredictionSettingsContent {
|
||||
/// Determines which edit prediction provider to use.
|
||||
pub provider: Option<EditPredictionProvider>,
|
||||
/// 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: Option<Vec<String>>,
|
||||
/// Disable edit predictions for files matching these glob patterns.
|
||||
///
|
||||
/// Use `"..."` to add patterns without repeating Zed's defaults. In project
|
||||
/// settings, it extends the user or parent configuration value. Omit
|
||||
/// `"..."` to replace the inherited list.
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "edit_predictions": {
|
||||
/// "disabled_globs": ["**/build/**", "..."]
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Inherited patterns are inserted at `"..."`, and duplicates keep their first
|
||||
/// occurrence.
|
||||
///
|
||||
/// Set `[]` to clear the inherited list. Omit this setting to inherit it unchanged.
|
||||
///
|
||||
/// Relative patterns are matched against paths relative to the worktree root.
|
||||
/// Absolute patterns are matched against absolute paths. A leading `~` is
|
||||
/// expanded to your home folder.
|
||||
pub disabled_globs: Option<SplicingVec>,
|
||||
/// The mode used to display edit predictions in the buffer.
|
||||
/// Provider support required.
|
||||
pub mode: Option<EditPredictionsMode>,
|
||||
|
||||
@@ -151,7 +151,7 @@ pub struct WorktreeSettingsContent {
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use `"..."` to add patterns without repeating Zed’s defaults. In project
|
||||
/// Use `"..."` to add patterns without repeating Zed's defaults. In project
|
||||
/// settings, it extends the user or parent configuration value. Omit
|
||||
/// `"..."` to replace the inherited list.
|
||||
///
|
||||
@@ -177,7 +177,7 @@ pub struct WorktreeSettingsContent {
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use `"..."` to add patterns without repeating Zed’s defaults. In project
|
||||
/// Use `"..."` to add patterns without repeating Zed's defaults. In project
|
||||
/// settings, it extends the user or parent configuration value. Omit
|
||||
/// `"..."` to replace the inherited list.
|
||||
///
|
||||
@@ -224,7 +224,7 @@ pub struct WorktreeSettingsContent {
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use `"..."` to add patterns without repeating Zed’s defaults. In project
|
||||
/// Use `"..."` to add patterns without repeating Zed's defaults. In project
|
||||
/// settings, it extends the user or parent configuration value. Omit
|
||||
/// `"..."` to replace the inherited list.
|
||||
///
|
||||
@@ -253,7 +253,7 @@ pub struct WorktreeSettingsContent {
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use `"..."` to add patterns without repeating Zed’s defaults. In project
|
||||
/// Use `"..."` to add patterns without repeating Zed's defaults. In project
|
||||
/// settings, it extends the user or parent configuration value. Omit
|
||||
/// `"..."` to replace the inherited list.
|
||||
///
|
||||
|
||||
@@ -125,7 +125,7 @@ pub struct WorkspaceSettingsContent {
|
||||
///
|
||||
/// Default: false
|
||||
pub close_on_file_delete: Option<bool>,
|
||||
/// Whether to allow windows to tab together based on the user’s tabbing preference (macOS only).
|
||||
/// Whether to allow windows to tab together based on the user's tabbing preference (macOS only).
|
||||
///
|
||||
/// Default: false
|
||||
pub use_system_window_tabs: Option<bool>,
|
||||
|
||||
@@ -71,6 +71,37 @@ pub fn update_value_in_json_text<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "editing")]
|
||||
pub fn find_value_range_in_json_text<T: AsRef<str>>(
|
||||
text: &str,
|
||||
key_path: &[T],
|
||||
) -> Option<Range<usize>> {
|
||||
let mut parser = tree_sitter::Parser::new();
|
||||
parser
|
||||
.set_language(&tree_sitter_json::LANGUAGE.into())
|
||||
.ok()?;
|
||||
let syntax_tree = parser.parse(text, None)?;
|
||||
let mut node = syntax_tree.root_node();
|
||||
if node.kind() == TS_DOCUMENT_KIND {
|
||||
let mut cursor = node.walk();
|
||||
node = node
|
||||
.named_children(&mut cursor)
|
||||
.find(|child| child.kind() != TS_COMMENT_KIND)?;
|
||||
}
|
||||
for key in key_path {
|
||||
let key_json = serde_json::to_string(key.as_ref()).ok()?;
|
||||
let mut cursor = node.walk();
|
||||
let value = node.named_children(&mut cursor).find_map(|pair| {
|
||||
let key_node = pair.child_by_field_name("key")?;
|
||||
(text.get(key_node.byte_range()) == Some(key_json.as_str()))
|
||||
.then(|| pair.child_by_field_name("value"))
|
||||
.flatten()
|
||||
})?;
|
||||
node = value;
|
||||
}
|
||||
Some(node.byte_range())
|
||||
}
|
||||
|
||||
/// * `replace_key` - When an exact key match according to `key_path` is found, replace the key with `replace_key` if `Some`.
|
||||
#[cfg(feature = "editing")]
|
||||
pub fn replace_value_in_json_text<T: AsRef<str>>(
|
||||
@@ -2700,4 +2731,38 @@ mod tests {
|
||||
"#;
|
||||
parse_json_with_comments::<Vec<Value>>(trailing).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_value_range_in_json_text() {
|
||||
let text = r#"// "edit_predictions": { "disabled_globs": ["commented/**"] },
|
||||
{
|
||||
// "disabled_globs": ["commented/**"],
|
||||
"languages": { "edit_predictions": { "disabled_globs": ["nested/**"] } },
|
||||
"edit_predictions": {
|
||||
/* "disabled_globs": ["commented/**"], */
|
||||
"mode": "subtle",
|
||||
"disabled_globs": ["live/**", /* ] */ "..."], // ]
|
||||
}
|
||||
}"#;
|
||||
let range = find_value_range_in_json_text(text, &["edit_predictions", "disabled_globs"])
|
||||
.expect("value range");
|
||||
assert_eq!(&text[range], r#"["live/**", /* ] */ "..."]"#);
|
||||
assert_eq!(
|
||||
find_value_range_in_json_text(text, &["edit_predictions", "missing"]),
|
||||
None
|
||||
);
|
||||
assert_eq!(find_value_range_in_json_text(text, &["missing"]), None);
|
||||
assert_eq!(
|
||||
find_value_range_in_json_text("// only a comment", &["edit_predictions"]),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
find_value_range_in_json_text("", &["edit_predictions"]),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
find_value_range_in_json_text(r#"{"a": 1}"#, &["a", "b"]),
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ To configure panel sizing, open the Settings Editor and search for “Agent Pane
|
||||
|
||||
### Flexible Sizing {#agent-panel-flexible-sizing}
|
||||
|
||||
- Description: Whether the agent panel uses flexible (proportional) sizing when docked to the left or right. When enabled, `agent.default_width` does not control the panel width, and double-clicking the panel’s outer resize handle restores the default proportion.
|
||||
- Description: Whether the agent panel uses flexible (proportional) sizing when docked to the left or right. When enabled, `agent.default_width` does not control the panel width, and double-clicking the panel's outer resize handle restores the default proportion.
|
||||
- Setting: `agent.flexible`
|
||||
- Default: `true`
|
||||
|
||||
@@ -141,7 +141,7 @@ Open the Settings Editor and search for “Threads Sidebar Default Width”. Or
|
||||
}
|
||||
```
|
||||
|
||||
If you haven’t manually resized the sidebar, its width follows changes to this setting immediately. A manually resized width takes precedence until you double-click the divider to reset it. After resetting, the sidebar follows this setting again.
|
||||
If you haven't manually resized the sidebar, its width follows changes to this setting immediately. A manually resized width takes precedence until you double-click the divider to reset it. After resetting, the sidebar follows this setting again.
|
||||
|
||||
Widths saved by older versions of Zed are preserved if they differ from the previous default of 300 pixels. A saved width of 300 pixels uses this setting instead.
|
||||
|
||||
@@ -789,6 +789,7 @@ For the case of "open", regular selection behavior can be achieved by holding `a
|
||||
- Default:
|
||||
|
||||
```json [settings]
|
||||
{
|
||||
"edit_predictions": {
|
||||
"disabled_globs": [
|
||||
"**/.env*",
|
||||
@@ -803,6 +804,7 @@ For the case of "open", regular selection behavior can be achieved by holding `a
|
||||
"/**/zed/keymap.json"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Options**
|
||||
@@ -847,7 +849,7 @@ For the case of "open", regular selection behavior can be achieved by holding `a
|
||||
|
||||
### Disabled Globs
|
||||
|
||||
- Description: A list of globs for which 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.
|
||||
- Description: Disable edit predictions for files matching these glob patterns.
|
||||
- Setting: `disabled_globs`
|
||||
- Default: `["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/.dev.vars", "**/secrets.yml", "**/.zed/settings.json", "/**/zed/settings.json", "/**/zed/keymap.json"]`
|
||||
|
||||
@@ -855,6 +857,22 @@ For the case of "open", regular selection behavior can be achieved by holding `a
|
||||
|
||||
List of `string` values.
|
||||
|
||||
Use `"..."` to add patterns without repeating Zed's defaults. In project settings, it extends the user or parent configuration value. Omit `"..."` to replace the inherited list.
|
||||
|
||||
```json [settings]
|
||||
{
|
||||
"edit_predictions": {
|
||||
"disabled_globs": ["**/build/**", "..."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Inherited patterns are inserted at `"..."`, and duplicates keep their first occurrence.
|
||||
|
||||
Set `[]` to clear the inherited list. Omit this setting to inherit it unchanged.
|
||||
|
||||
Relative patterns are matched against paths relative to the worktree root. Absolute patterns are matched against absolute paths. A leading `~` is expanded to your home folder.
|
||||
|
||||
### Prediction Debounce
|
||||
|
||||
- Description: How long Zed waits after you stop typing before automatically requesting an edit prediction.
|
||||
@@ -1878,13 +1896,13 @@ Each option controls displaying of a particular toolbar element. If all elements
|
||||
|
||||
## Use System Tabs
|
||||
|
||||
- Description: Whether to allow windows to tab together based on the user’s tabbing preference (macOS only).
|
||||
- Description: Whether to allow windows to tab together based on the user's tabbing preference (macOS only).
|
||||
- Setting: `use_system_window_tabs`
|
||||
- Default: `false`
|
||||
|
||||
**Options**
|
||||
|
||||
This setting enables integration with macOS’s native window tabbing feature. When set to `true`, Zed windows can be grouped together as tabs in a single macOS window, following the system-wide tabbing preferences set by the user (such as "Always", "In Full Screen", or "Never"). This setting is only available on macOS.
|
||||
This setting enables integration with macOS's native window tabbing feature. When set to `true`, Zed windows can be grouped together as tabs in a single macOS window, following the system-wide tabbing preferences set by the user (such as "Always", "In Full Screen", or "Never"). This setting is only available on macOS.
|
||||
|
||||
## Fullscreen Mode
|
||||
|
||||
@@ -2353,7 +2371,7 @@ The result is still `)))` and not `))))))`, which is what it would be by default
|
||||
}
|
||||
```
|
||||
|
||||
Use `"..."` to add patterns without repeating Zed’s defaults. In project settings, it extends the user or parent configuration value. Omit `"..."` to replace the inherited list.
|
||||
Use `"..."` to add patterns without repeating Zed's defaults. In project settings, it extends the user or parent configuration value. Omit `"..."` to replace the inherited list.
|
||||
|
||||
```json [settings]
|
||||
{
|
||||
@@ -2375,7 +2393,7 @@ Inherited patterns are inserted at `"..."`, and duplicates keep their first occu
|
||||
}
|
||||
```
|
||||
|
||||
Use `"..."` to add patterns without repeating Zed’s defaults. In project settings, it extends the user or parent configuration value. Omit `"..."` to replace the inherited list.
|
||||
Use `"..."` to add patterns without repeating Zed's defaults. In project settings, it extends the user or parent configuration value. Omit `"..."` to replace the inherited list.
|
||||
|
||||
```json [settings]
|
||||
{
|
||||
@@ -2944,7 +2962,7 @@ Example:
|
||||
}
|
||||
```
|
||||
|
||||
Use `"..."` to add patterns without repeating Zed’s defaults. In project settings, it extends the user or parent configuration value. Omit `"..."` to replace the inherited list.
|
||||
Use `"..."` to add patterns without repeating Zed's defaults. In project settings, it extends the user or parent configuration value. Omit `"..."` to replace the inherited list.
|
||||
|
||||
```json [settings]
|
||||
{
|
||||
@@ -3908,7 +3926,7 @@ List of `string` glob patterns
|
||||
}
|
||||
```
|
||||
|
||||
Use `"..."` to add patterns without repeating Zed’s defaults. In project settings, it extends the user or parent configuration value. Omit `"..."` to replace the inherited list.
|
||||
Use `"..."` to add patterns without repeating Zed's defaults. In project settings, it extends the user or parent configuration value. Omit `"..."` to replace the inherited list.
|
||||
|
||||
```json [settings]
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user