Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions assets/settings/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -2274,6 +2274,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.
Expand Down
3 changes: 3 additions & 0 deletions crates/settings_content/src/settings_content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
15 changes: 14 additions & 1 deletion crates/settings_ui/src/page_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.",
Expand Down
23 changes: 21 additions & 2 deletions crates/vim/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -88,6 +88,7 @@ pub enum VimOption {
Number(bool),
RelativeNumber(bool),
IgnoreCase(bool),
GDefault(bool),
}

impl VimOption {
Expand Down Expand Up @@ -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))
Expand All @@ -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,
}
}
Expand All @@ -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",
}
}
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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);
})
}
});
}
});
Expand Down
61 changes: 58 additions & 3 deletions crates/vim/src/normal/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion crates/vim/src/vim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand Down
64 changes: 64 additions & 0 deletions crates/vim/test_data/test_replace_gdefault.json
Original file line number Diff line number Diff line change
@@ -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"}}
2 changes: 2 additions & 0 deletions docs/src/vim.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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": {
Expand Down
Loading