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:
Dom PoradaandKirill Bulatov authored and GitHub committed 2026-09-21 13:08:53 +00:00
1 parent 0ddd9c9863
commit 3a7b16da5d
12 files changed
+598 -68

No files matched your search

Generated
-1
View File
@@ -5556,7 +5556,6 @@ dependencies = [
"multi_buffer", "multi_buffer",
"paths", "paths",
"project", "project",
"regex",
"settings", "settings",
"telemetry", "telemetry",
"text", "text",
+15 -6
View File
@@ -586,7 +586,7 @@
// Whether to show code action buttons in the editor toolbar. // Whether to show code action buttons in the editor toolbar.
"code_actions": false, "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, "use_system_window_tabs": false,
// Which fullscreen mode the `zed::ToggleFullScreen` action enters (macOS only). // Which fullscreen mode the `zed::ToggleFullScreen` action enters (macOS only).
// //
@@ -1873,11 +1873,20 @@
"edit_predictions": { "edit_predictions": {
// Which edit prediction provider to use. // Which edit prediction provider to use.
"provider": "zed", "provider": "zed",
// A list of globs representing files that edit predictions should be disabled for. // Disable edit predictions for files matching these glob patterns.
// There's a sensible default list of globs already included. //
// Any addition to this list will be merged with the default list. // Use `"..."` to add patterns without repeating Zed's defaults. In project
// Globs are matched relative to the worktree root, // settings, it extends the user or parent configuration value. Omit
// except when starting with a slash (/) or equivalent in Windows. // `"..."` 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": [ "disabled_globs": [
"**/.env*", "**/.env*",
"**/*.pem", "**/*.pem",
@@ -246,7 +246,7 @@ mod tests {
use lsp::Uri; use lsp::Uri;
use project::Project; use project::Project;
use serde_json::json; use serde_json::json;
use settings::{AllLanguageSettingsContent, DelayMs, SettingsStore}; use settings::{AllLanguageSettingsContent, DelayMs, SettingsStore, SplicingVec};
use std::future::Future; use std::future::Future;
use util::{ use util::{
path, path,
@@ -979,7 +979,7 @@ mod tests {
settings settings
.edit_predictions .edit_predictions
.get_or_insert(Default::default()) .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); let (copilot, copilot_lsp) = Copilot::fake(cx);
-1
View File
@@ -36,7 +36,6 @@ menu.workspace = true
multi_buffer.workspace = true multi_buffer.workspace = true
paths.workspace = true paths.workspace = true
project.workspace = true project.workspace = true
regex.workspace = true
settings.workspace = true settings.workspace = true
telemetry.workspace = true telemetry.workspace = true
text.workspace = true text.workspace = true
@@ -23,13 +23,11 @@ use language::{
}, },
}; };
use project::{DisableAiSettings, Project}; use project::{DisableAiSettings, Project};
use regex::Regex; use settings::{
use settings::{Settings, SettingsStore, update_settings_file}; Settings, SettingsContent, SettingsStore, SplicingVec, find_value_range_in_json_text,
use std::{ update_settings_file,
rc::Rc,
sync::{Arc, LazyLock},
time::Duration,
}; };
use std::{ops::Range, rc::Rc, sync::Arc, time::Duration};
use ui::{ use ui::{
Clickable, ContextMenu, ContextMenuEntry, DocumentationSide, IconButton, IconButtonShape, Clickable, ContextMenu, ContextMenuEntry, DocumentationSide, IconButton, IconButtonShape,
Indicator, PopoverMenu, PopoverMenuHandle, ProgressBar, Tooltip, prelude::*, 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( async fn open_disabled_globs_setting_in_editor(
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
cx: &mut AsyncWindowContext, cx: &mut AsyncWindowContext,
@@ -1423,16 +1440,8 @@ async fn open_disabled_globs_setting_in_editor(
let settings = cx.global::<SettingsStore>(); let settings = cx.global::<SettingsStore>();
// Ensure that we always have "edit_predictions { "disabled_globs": [] }"
let Some(edits) = settings let Some(edits) = settings
.edits_for_update(&text, |file| { .edits_for_update(&text, initialize_disabled_globs_setting)
file.project
.all_languages
.edit_predictions
.get_or_insert_with(Default::default)
.disabled_globs
.get_or_insert_with(Vec::new);
})
.log_err() .log_err()
else { else {
return; return;
@@ -1449,16 +1458,7 @@ async fn open_disabled_globs_setting_in_editor(
let text = item.buffer().read(cx).snapshot(cx).text(); let text = item.buffer().read(cx).snapshot(cx).text();
static DISABLED_GLOBS_REGEX: LazyLock<Regex> = LazyLock::new(|| { if let Some(range) = disabled_globs_content_range(&text) {
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 {
let range = MultiBufferOffset(range.start)..MultiBufferOffset(range.end); let range = MultiBufferOffset(range.start)..MultiBufferOffset(range.end);
item.change_selections( item.change_selections(
SelectionEffects::scroll(Autoscroll::newest()), SelectionEffects::scroll(Autoscroll::newest()),
@@ -1686,7 +1686,118 @@ fn copilot_settings_url(enterprise_uri: Option<&str>) -> Arc<str> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use anyhow::Context as _;
use gpui::TestAppContext; 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] #[gpui::test]
async fn test_copilot_settings_url_with_enterprise_uri(cx: &mut TestAppContext) { async fn test_copilot_settings_url_with_enterprise_uri(cx: &mut TestAppContext) {
+227 -3
View File
@@ -487,9 +487,28 @@ impl InlayHintSettings {
pub struct EditPredictionSettings { pub struct EditPredictionSettings {
/// The provider that supplies edit predictions. /// The provider that supplies edit predictions.
pub provider: settings::EditPredictionProvider, pub provider: settings::EditPredictionProvider,
/// A list of globs representing files that edit predictions should be disabled for. /// Disable edit predictions for files matching these glob patterns.
/// This list adds to a pre-existing, sensible default set of globs. ///
/// Any additional ones you add are combined with them. /// 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>, pub disabled_globs: Vec<DisabledGlob>,
/// Configures how edit predictions are displayed in the buffer. /// Configures how edit predictions are displayed in the buffer.
pub mode: settings::EditPredictionsMode, pub mode: settings::EditPredictionsMode,
@@ -918,6 +937,7 @@ impl settings::Settings for AllLanguageSettings {
.disabled_globs .disabled_globs
.as_ref() .as_ref()
.unwrap() .unwrap()
.0
.iter() .iter()
.collect(); .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] #[gpui::test]
fn test_edit_predictions_enabled_for_file(cx: &mut TestAppContext) { fn test_edit_predictions_enabled_for_file(cx: &mut TestAppContext) {
use crate::TestFile; use crate::TestFile;
+96 -11
View File
@@ -677,19 +677,22 @@ impl VsCodeSettings {
} }
fn edit_predictions_settings_content(&self) -> Option<EditPredictionSettingsContent> { fn edit_predictions_settings_content(&self) -> Option<EditPredictionSettingsContent> {
let disabled_globs = self let mut disabled_globs = self
.read_value("cursor.general.globalCursorIgnoreList")? .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 { Some(EditPredictionSettingsContent {
disabled_globs: skip_default( disabled_globs: Some(SplicingVec::from(disabled_globs)),
disabled_globs ..EditPredictionSettingsContent::default()
.iter()
.filter_map(|glob| glob.as_str())
.map(|s| s.to_string())
.collect(),
),
..Default::default()
}) })
} }
@@ -1235,6 +1238,88 @@ mod tests {
.reduce_motion .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] #[test]
fn test_import_file_exclusions() -> Result<()> { fn test_import_file_exclusions() -> Result<()> {
let imported = VsCodeSettings::from_str( let imported = VsCodeSettings::from_str(
+25 -5
View File
@@ -7,7 +7,8 @@ use settings_macros::{MergeFrom, with_fallible_options};
use std::sync::Arc; use std::sync::Arc;
use crate::{ 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 /// The state of the modifier keys at some point in time
@@ -132,10 +133,29 @@ impl EditPredictionProvider {
pub struct EditPredictionSettingsContent { pub struct EditPredictionSettingsContent {
/// Determines which edit prediction provider to use. /// Determines which edit prediction provider to use.
pub provider: Option<EditPredictionProvider>, pub provider: Option<EditPredictionProvider>,
/// A list of globs representing files that edit predictions should be disabled for. /// Disable edit predictions for files matching these glob patterns.
/// This list adds to a pre-existing, sensible default set of globs. ///
/// Any additional ones you add are combined with them. /// Use `"..."` to add patterns without repeating Zed's defaults. In project
pub disabled_globs: Option<Vec<String>>, /// 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. /// The mode used to display edit predictions in the buffer.
/// Provider support required. /// Provider support required.
pub mode: Option<EditPredictionsMode>, pub mode: Option<EditPredictionsMode>,
+4 -4
View File
@@ -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 /// settings, it extends the user or parent configuration value. Omit
/// `"..."` to replace the inherited list. /// `"..."` 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 /// settings, it extends the user or parent configuration value. Omit
/// `"..."` to replace the inherited list. /// `"..."` 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 /// settings, it extends the user or parent configuration value. Omit
/// `"..."` to replace the inherited list. /// `"..."` 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 /// settings, it extends the user or parent configuration value. Omit
/// `"..."` to replace the inherited list. /// `"..."` to replace the inherited list.
/// ///
+1 -1
View File
@@ -125,7 +125,7 @@ pub struct WorkspaceSettingsContent {
/// ///
/// Default: false /// Default: false
pub close_on_file_delete: Option<bool>, 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 /// Default: false
pub use_system_window_tabs: Option<bool>, pub use_system_window_tabs: Option<bool>,
+65
View File
@@ -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`. /// * `replace_key` - When an exact key match according to `key_path` is found, replace the key with `replace_key` if `Some`.
#[cfg(feature = "editing")] #[cfg(feature = "editing")]
pub fn replace_value_in_json_text<T: AsRef<str>>( 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(); 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
);
}
} }
+27 -9
View File
@@ -100,7 +100,7 @@ To configure panel sizing, open the Settings Editor and search for “Agent Pane
### Flexible Sizing {#agent-panel-flexible-sizing} ### 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` - Setting: `agent.flexible`
- Default: `true` - 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. 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: - Default:
```json [settings] ```json [settings]
{
"edit_predictions": { "edit_predictions": {
"disabled_globs": [ "disabled_globs": [
"**/.env*", "**/.env*",
@@ -803,6 +804,7 @@ For the case of "open", regular selection behavior can be achieved by holding `a
"/**/zed/keymap.json" "/**/zed/keymap.json"
] ]
} }
}
``` ```
**Options** **Options**
@@ -847,7 +849,7 @@ For the case of "open", regular selection behavior can be achieved by holding `a
### Disabled Globs ### 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` - Setting: `disabled_globs`
- Default: `["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/.dev.vars", "**/secrets.yml", "**/.zed/settings.json", "/**/zed/settings.json", "/**/zed/keymap.json"]` - 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. 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 ### Prediction Debounce
- Description: How long Zed waits after you stop typing before automatically requesting an edit prediction. - 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 ## 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` - Setting: `use_system_window_tabs`
- Default: `false` - Default: `false`
**Options** **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 ## 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] ```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] ```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] ```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] ```json [settings]
{ {