vim: Add gdefault setting to set /g as a default substitution flag (#47664)
Add support for Vim's `gdefault` option which makes the `:substitute` command replace all matches in a line by default, instead of just the first match. When enabled, the `/g` flag inverts this behavior. - Add `vim.gdefault` setting - Add `:set gdefault`, `:set nogdefault` (and short forms `:set gd`, `:set nogd`) - Fix handling of multiple `/g` flags so that each one inverts the one before Closes #36209 Release Notes: - vim: Add `vim.gdefault` setting to make `/g` (replace all matches in a line) the default for substitutions, along with `:set gdefault` and `:set nogdefault` commands (short forms: `gd`, `nogd`) --------- Co-authored-by: dino <dinojoaocosta@gmail.com>
This commit is contained in:
1 parent
559a8500a0
commit
1169dff02b
8 files changed
+166
-7
No files matched your search
@@ -2275,6 +2275,7 @@
|
||||
"toggle_relative_line_numbers": false,
|
||||
"use_system_clipboard": "always",
|
||||
"use_smartcase_find": false,
|
||||
"gdefault": false,
|
||||
"highlight_on_yank_duration": 200,
|
||||
"custom_digraphs": {},
|
||||
// Cursor shape for each mode.
|
||||
|
||||
@@ -701,6 +701,9 @@ pub struct VimSettingsContent {
|
||||
pub toggle_relative_line_numbers: Option<bool>,
|
||||
pub use_system_clipboard: Option<UseSystemClipboard>,
|
||||
pub use_smartcase_find: Option<bool>,
|
||||
/// When enabled, the `:substitute` command replaces all matches in a line
|
||||
/// by default. The 'g' flag then toggles this behavior.,
|
||||
pub gdefault: Option<bool>,
|
||||
pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
|
||||
pub highlight_on_yank_duration: Option<u64>,
|
||||
pub cursor_shape: Option<CursorShapeSettings>,
|
||||
|
||||
@@ -2410,7 +2410,7 @@ fn editor_page() -> SettingsPage {
|
||||
]
|
||||
}
|
||||
|
||||
fn vim_settings_section() -> [SettingsPageItem; 11] {
|
||||
fn vim_settings_section() -> [SettingsPageItem; 12] {
|
||||
[
|
||||
SettingsPageItem::SectionHeader("Vim"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
@@ -2484,6 +2484,19 @@ fn editor_page() -> SettingsPage {
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Global Substitution Default",
|
||||
description: "When enabled, the :substitute command replaces all matches in a line by default. The 'g' flag then toggles this behavior.",
|
||||
field: Box::new(SettingField {
|
||||
json_path: Some("vim.gdefault"),
|
||||
pick: |settings_content| settings_content.vim.as_ref()?.gdefault.as_ref(),
|
||||
write: |settings_content, value| {
|
||||
settings_content.vim.get_or_insert_default().gdefault = value;
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Highlight on Yank Duration",
|
||||
description: "Duration in milliseconds to highlight yanked text in Vim mode.",
|
||||
|
||||
@@ -40,7 +40,7 @@ use workspace::{SplitDirection, notifications::DetachAndPromptErr};
|
||||
use zed_actions::{OpenDocs, RevealTarget};
|
||||
|
||||
use crate::{
|
||||
ToggleMarksView, ToggleRegistersView, Vim,
|
||||
ToggleMarksView, ToggleRegistersView, Vim, VimSettings,
|
||||
motion::{EndOfDocument, Motion, MotionKind, StartOfDocument},
|
||||
normal::{
|
||||
JoinLines,
|
||||
@@ -88,6 +88,7 @@ pub enum VimOption {
|
||||
Number(bool),
|
||||
RelativeNumber(bool),
|
||||
IgnoreCase(bool),
|
||||
GDefault(bool),
|
||||
}
|
||||
|
||||
impl VimOption {
|
||||
@@ -134,6 +135,10 @@ impl VimOption {
|
||||
(None, VimOption::IgnoreCase(false)),
|
||||
(Some("ic"), VimOption::IgnoreCase(true)),
|
||||
(Some("noic"), VimOption::IgnoreCase(false)),
|
||||
(None, VimOption::GDefault(true)),
|
||||
(Some("gd"), VimOption::GDefault(true)),
|
||||
(None, VimOption::GDefault(false)),
|
||||
(Some("nogd"), VimOption::GDefault(false)),
|
||||
]
|
||||
.into_iter()
|
||||
.filter(move |(prefix, option)| prefix.unwrap_or(option.to_string()).starts_with(query))
|
||||
@@ -160,6 +165,11 @@ impl VimOption {
|
||||
"noignorecase" => Some(Self::IgnoreCase(false)),
|
||||
"noic" => Some(Self::IgnoreCase(false)),
|
||||
|
||||
"gdefault" => Some(Self::GDefault(true)),
|
||||
"gd" => Some(Self::GDefault(true)),
|
||||
"nogdefault" => Some(Self::GDefault(false)),
|
||||
"nogd" => Some(Self::GDefault(false)),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -174,6 +184,8 @@ impl VimOption {
|
||||
VimOption::RelativeNumber(false) => "norelativenumber",
|
||||
VimOption::IgnoreCase(true) => "ignorecase",
|
||||
VimOption::IgnoreCase(false) => "noignorecase",
|
||||
VimOption::GDefault(true) => "gdefault",
|
||||
VimOption::GDefault(false) => "nogdefault",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -271,7 +283,6 @@ impl Deref for WrappedAction {
|
||||
}
|
||||
|
||||
pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
||||
// Vim::action(editor, cx, |vim, action: &StartOfLine, window, cx| {
|
||||
Vim::action(editor, cx, |vim, action: &VimSet, _, cx| {
|
||||
for option in action.options.iter() {
|
||||
vim.update_editor(cx, |_, editor, cx| match option {
|
||||
@@ -295,6 +306,14 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
||||
store.override_global(settings);
|
||||
});
|
||||
}
|
||||
VimOption::GDefault(enabled) => {
|
||||
let mut settings = VimSettings::get_global(cx).clone();
|
||||
settings.gdefault = *enabled;
|
||||
|
||||
SettingsStore::update(cx, |store, _| {
|
||||
store.override_global(settings);
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ use util::serde::default_true;
|
||||
use workspace::{notifications::NotifyResultExt, searchable::Direction};
|
||||
|
||||
use crate::{
|
||||
Vim,
|
||||
Vim, VimSettings,
|
||||
command::CommandRange,
|
||||
motion::Motion,
|
||||
state::{Mode, SearchState},
|
||||
@@ -581,7 +581,9 @@ impl Vim {
|
||||
)
|
||||
}
|
||||
|
||||
if !replacement.flag_g {
|
||||
// gdefault inverts the behavior of the 'g' flag.
|
||||
let replace_all = VimSettings::get_global(cx).gdefault != replacement.flag_g;
|
||||
if !replace_all {
|
||||
options.set(SearchOptions::ONE_MATCH_PER_LINE, true);
|
||||
}
|
||||
|
||||
@@ -704,7 +706,7 @@ impl Replacement {
|
||||
|
||||
for c in flags.chars() {
|
||||
match c {
|
||||
'g' => replacement.flag_g = true,
|
||||
'g' => replacement.flag_g = !replacement.flag_g,
|
||||
'n' => replacement.flag_n = true,
|
||||
'c' => replacement.flag_c = true,
|
||||
'i' => replacement.case_sensitive = Some(false),
|
||||
@@ -1143,6 +1145,59 @@ mod test {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_replace_gdefault(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
// Set the `gdefault` option in both Zed and Neovim.
|
||||
cx.simulate_shared_keystrokes(": s e t space g d e f a u l t")
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("enter").await;
|
||||
|
||||
cx.set_shared_state(indoc! {
|
||||
"ˇaa aa aa aa
|
||||
aa
|
||||
aa"
|
||||
})
|
||||
.await;
|
||||
|
||||
// With gdefault on, :s/// replaces all matches (like :s///g normally).
|
||||
cx.simulate_shared_keystrokes(": s / a a / b b").await;
|
||||
cx.simulate_shared_keystrokes("enter").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {
|
||||
"ˇbb bb bb bb
|
||||
aa
|
||||
aa"
|
||||
});
|
||||
|
||||
// With gdefault on, :s///g replaces only the first match.
|
||||
cx.simulate_shared_keystrokes(": s / b b / c c / g").await;
|
||||
cx.simulate_shared_keystrokes("enter").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {
|
||||
"ˇcc bb bb bb
|
||||
aa
|
||||
aa"
|
||||
});
|
||||
|
||||
// Each successive `/g` flag should invert the one before it.
|
||||
cx.simulate_shared_keystrokes(": s / b b / d d / g g").await;
|
||||
cx.simulate_shared_keystrokes("enter").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {
|
||||
"ˇcc dd dd dd
|
||||
aa
|
||||
aa"
|
||||
});
|
||||
|
||||
cx.simulate_shared_keystrokes(": s / c c / e e / g g g")
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("enter").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {
|
||||
"ˇee dd dd dd
|
||||
aa
|
||||
aa"
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_replace_c(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
|
||||
@@ -2075,12 +2075,13 @@ struct VimEditorSettingsState {
|
||||
hide_edit_predictions: bool,
|
||||
}
|
||||
|
||||
#[derive(RegisterSetting)]
|
||||
#[derive(Clone, RegisterSetting)]
|
||||
struct VimSettings {
|
||||
pub default_mode: Mode,
|
||||
pub toggle_relative_line_numbers: bool,
|
||||
pub use_system_clipboard: settings::UseSystemClipboard,
|
||||
pub use_smartcase_find: bool,
|
||||
pub gdefault: bool,
|
||||
pub custom_digraphs: HashMap<String, Arc<str>>,
|
||||
pub highlight_on_yank_duration: u64,
|
||||
pub cursor_shape: CursorShapeSettings,
|
||||
@@ -2166,6 +2167,7 @@ impl Settings for VimSettings {
|
||||
toggle_relative_line_numbers: vim.toggle_relative_line_numbers.unwrap(),
|
||||
use_system_clipboard: vim.use_system_clipboard.unwrap(),
|
||||
use_smartcase_find: vim.use_smartcase_find.unwrap(),
|
||||
gdefault: vim.gdefault.unwrap(),
|
||||
custom_digraphs: vim.custom_digraphs.unwrap(),
|
||||
highlight_on_yank_duration: vim.highlight_on_yank_duration.unwrap(),
|
||||
cursor_shape: vim.cursor_shape.unwrap().into(),
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
{"Key":":"}
|
||||
{"Key":"s"}
|
||||
{"Key":"e"}
|
||||
{"Key":"t"}
|
||||
{"Key":"space"}
|
||||
{"Key":"g"}
|
||||
{"Key":"d"}
|
||||
{"Key":"e"}
|
||||
{"Key":"f"}
|
||||
{"Key":"a"}
|
||||
{"Key":"u"}
|
||||
{"Key":"l"}
|
||||
{"Key":"t"}
|
||||
{"Key":"enter"}
|
||||
{"Put":{"state":"ˇaa aa aa aa\naa\naa"}}
|
||||
{"Key":":"}
|
||||
{"Key":"s"}
|
||||
{"Key":"/"}
|
||||
{"Key":"a"}
|
||||
{"Key":"a"}
|
||||
{"Key":"/"}
|
||||
{"Key":"b"}
|
||||
{"Key":"b"}
|
||||
{"Key":"enter"}
|
||||
{"Get":{"state":"ˇbb bb bb bb\naa\naa","mode":"Normal"}}
|
||||
{"Key":":"}
|
||||
{"Key":"s"}
|
||||
{"Key":"/"}
|
||||
{"Key":"b"}
|
||||
{"Key":"b"}
|
||||
{"Key":"/"}
|
||||
{"Key":"c"}
|
||||
{"Key":"c"}
|
||||
{"Key":"/"}
|
||||
{"Key":"g"}
|
||||
{"Key":"enter"}
|
||||
{"Get":{"state":"ˇcc bb bb bb\naa\naa","mode":"Normal"}}
|
||||
{"Key":":"}
|
||||
{"Key":"s"}
|
||||
{"Key":"/"}
|
||||
{"Key":"b"}
|
||||
{"Key":"b"}
|
||||
{"Key":"/"}
|
||||
{"Key":"d"}
|
||||
{"Key":"d"}
|
||||
{"Key":"/"}
|
||||
{"Key":"g"}
|
||||
{"Key":"g"}
|
||||
{"Key":"enter"}
|
||||
{"Get":{"state":"ˇcc dd dd dd\naa\naa","mode":"Normal"}}
|
||||
{"Key":":"}
|
||||
{"Key":"s"}
|
||||
{"Key":"/"}
|
||||
{"Key":"c"}
|
||||
{"Key":"c"}
|
||||
{"Key":"/"}
|
||||
{"Key":"e"}
|
||||
{"Key":"e"}
|
||||
{"Key":"/"}
|
||||
{"Key":"g"}
|
||||
{"Key":"g"}
|
||||
{"Key":"g"}
|
||||
{"Key":"enter"}
|
||||
{"Get":{"state":"ˇee dd dd dd\naa\naa","mode":"Normal"}}
|
||||
@@ -569,6 +569,7 @@ You can change the following settings to modify vim mode's behavior:
|
||||
| use_system_clipboard | Determines how system clipboard is used:<br><ul><li>"always": use for all operations</li><li>"never": only use when explicitly specified</li><li>"on_yank": use for yank operations</li></ul> | "always" |
|
||||
| use_multiline_find | deprecated |
|
||||
| use_smartcase_find | If `true`, `f` and `t` motions are case-insensitive when the target letter is lowercase. | false |
|
||||
| gdefault | If `true`, the `:substitute` command replaces all matches in a line by default (as if `g` flag was given). The `g` flag then toggles this, replacing only the first match. | false |
|
||||
| toggle_relative_line_numbers | If `true`, line numbers are relative in normal mode and absolute in insert mode, giving you the best of both options. | false |
|
||||
| custom_digraphs | An object that allows you to add custom digraphs. Read below for an example. | {} |
|
||||
| highlight_on_yank_duration | The duration of the highlight animation(in ms). Set to `0` to disable | 200 |
|
||||
@@ -593,6 +594,7 @@ Here's an example of these settings changed:
|
||||
"default_mode": "insert",
|
||||
"use_system_clipboard": "never",
|
||||
"use_smartcase_find": true,
|
||||
"gdefault": true,
|
||||
"relative_line_numbers": "enabled",
|
||||
"highlight_on_yank_duration": 50,
|
||||
"custom_digraphs": {
|
||||
|
||||
Reference in new issue
Block a user