Simulate helix line wrapping (#32763)

In helix the `f`, `F`, `t`, `T`, left and right motions wrap lines. I
added that by default.

Release Notes:

- vim: The `use_multiline_find` setting is replaced by binding to the
correct action in the keymap:
    ```
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
"t": ["vim::PushFindForward", { "before": true, "multiline": true }],
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true
}],
"shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true
}],
    ```
- helix: `f`/`t`/`shift-f`/`shift-t`/`h`/`l`/`left`/`right` are now
multiline by default (like helix)
This commit is contained in:
fantacell 2025-06-24 18:51:41 +02:00 committed by GitHub
parent 7be57baef0
commit 95cf153ad7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 60 additions and 104 deletions

View file

@ -85,10 +85,10 @@
"[ {": ["vim::UnmatchedBackward", { "char": "{" }], "[ {": ["vim::UnmatchedBackward", { "char": "{" }],
"] )": ["vim::UnmatchedForward", { "char": ")" }], "] )": ["vim::UnmatchedForward", { "char": ")" }],
"[ (": ["vim::UnmatchedBackward", { "char": "(" }], "[ (": ["vim::UnmatchedBackward", { "char": "(" }],
"f": ["vim::PushFindForward", { "before": false }], "f": ["vim::PushFindForward", { "before": false, "multiline": false }],
"t": ["vim::PushFindForward", { "before": true }], "t": ["vim::PushFindForward", { "before": true, "multiline": false }],
"shift-f": ["vim::PushFindBackward", { "after": false }], "shift-f": ["vim::PushFindBackward", { "after": false, "multiline": false }],
"shift-t": ["vim::PushFindBackward", { "after": true }], "shift-t": ["vim::PushFindBackward", { "after": true, "multiline": false }],
"m": "vim::PushMark", "m": "vim::PushMark",
"'": ["vim::PushJump", { "line": true }], "'": ["vim::PushJump", { "line": true }],
"`": ["vim::PushJump", { "line": false }], "`": ["vim::PushJump", { "line": false }],
@ -368,6 +368,10 @@
"escape": "editor::Cancel", "escape": "editor::Cancel",
"ctrl-[": "editor::Cancel", "ctrl-[": "editor::Cancel",
":": "command_palette::Toggle", ":": "command_palette::Toggle",
"left": "vim::WrappingLeft",
"right": "vim::WrappingRight",
"h": "vim::WrappingLeft",
"l": "vim::WrappingRight",
"shift-d": "vim::DeleteToEndOfLine", "shift-d": "vim::DeleteToEndOfLine",
"shift-j": "vim::JoinLines", "shift-j": "vim::JoinLines",
"y": "editor::Copy", "y": "editor::Copy",
@ -385,6 +389,10 @@
"shift-p": ["vim::Paste", { "before": true }], "shift-p": ["vim::Paste", { "before": true }],
"u": "vim::Undo", "u": "vim::Undo",
"ctrl-r": "vim::Redo", "ctrl-r": "vim::Redo",
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
"t": ["vim::PushFindForward", { "before": true, "multiline": true }],
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }],
"shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }],
"r": "vim::PushReplace", "r": "vim::PushReplace",
"s": "vim::Substitute", "s": "vim::Substitute",
"shift-s": "vim::SubstituteLine", "shift-s": "vim::SubstituteLine",

View file

@ -1734,7 +1734,6 @@
"default_mode": "normal", "default_mode": "normal",
"toggle_relative_line_numbers": false, "toggle_relative_line_numbers": false,
"use_system_clipboard": "always", "use_system_clipboard": "always",
"use_multiline_find": false,
"use_smartcase_find": false, "use_smartcase_find": false,
"highlight_on_yank_duration": 200, "highlight_on_yank_duration": 200,
"custom_digraphs": {}, "custom_digraphs": {},

View file

@ -435,4 +435,37 @@ mod test {
// Mode::HelixNormal, // Mode::HelixNormal,
// ); // );
// } // }
#[gpui::test]
async fn test_f_and_t(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state(
indoc! {"
The quˇick brown
fox jumps over
the lazy dog."},
Mode::HelixNormal,
);
cx.simulate_keystrokes("f z");
cx.assert_state(
indoc! {"
The qu«ick brown
fox jumps over
the lazˇ»y dog."},
Mode::HelixNormal,
);
cx.simulate_keystrokes("2 T r");
cx.assert_state(
indoc! {"
The quick br«ˇown
fox jumps over
the laz»y dog."},
Mode::HelixNormal,
);
}
} }

View file

@ -1532,90 +1532,6 @@ mod test {
} }
} }
#[gpui::test]
async fn test_f_and_t_multiline(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<VimSettings>(cx, |s| {
s.use_multiline_find = Some(true);
});
});
cx.assert_binding(
"f l",
indoc! {"
ˇfunction print() {
console.log('ok')
}
"},
Mode::Normal,
indoc! {"
function print() {
consoˇle.log('ok')
}
"},
Mode::Normal,
);
cx.assert_binding(
"t l",
indoc! {"
ˇfunction print() {
console.log('ok')
}
"},
Mode::Normal,
indoc! {"
function print() {
consˇole.log('ok')
}
"},
Mode::Normal,
);
}
#[gpui::test]
async fn test_capital_f_and_capital_t_multiline(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<VimSettings>(cx, |s| {
s.use_multiline_find = Some(true);
});
});
cx.assert_binding(
"shift-f p",
indoc! {"
function print() {
console.ˇlog('ok')
}
"},
Mode::Normal,
indoc! {"
function ˇprint() {
console.log('ok')
}
"},
Mode::Normal,
);
cx.assert_binding(
"shift-t p",
indoc! {"
function print() {
console.ˇlog('ok')
}
"},
Mode::Normal,
indoc! {"
function pˇrint() {
console.log('ok')
}
"},
Mode::Normal,
);
}
#[gpui::test] #[gpui::test]
async fn test_f_and_t_smartcase(cx: &mut gpui::TestAppContext) { async fn test_f_and_t_smartcase(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await; let mut cx = VimTestContext::new(cx, true).await;

View file

@ -86,9 +86,11 @@ pub enum Operator {
}, },
FindForward { FindForward {
before: bool, before: bool,
multiline: bool,
}, },
FindBackward { FindBackward {
after: bool, after: bool,
multiline: bool,
}, },
Sneak { Sneak {
first_char: Option<char>, first_char: Option<char>,
@ -994,12 +996,12 @@ impl Operator {
Operator::Replace => "r", Operator::Replace => "r",
Operator::Digraph { .. } => "^K", Operator::Digraph { .. } => "^K",
Operator::Literal { .. } => "^V", Operator::Literal { .. } => "^V",
Operator::FindForward { before: false } => "f", Operator::FindForward { before: false, .. } => "f",
Operator::FindForward { before: true } => "t", Operator::FindForward { before: true, .. } => "t",
Operator::Sneak { .. } => "s", Operator::Sneak { .. } => "s",
Operator::SneakBackward { .. } => "S", Operator::SneakBackward { .. } => "S",
Operator::FindBackward { after: false } => "F", Operator::FindBackward { after: false, .. } => "F",
Operator::FindBackward { after: true } => "T", Operator::FindBackward { after: true, .. } => "T",
Operator::AddSurrounds { .. } => "ys", Operator::AddSurrounds { .. } => "ys",
Operator::ChangeSurrounds { .. } => "cs", Operator::ChangeSurrounds { .. } => "cs",
Operator::DeleteSurrounds => "ds", Operator::DeleteSurrounds => "ds",

View file

@ -72,6 +72,7 @@ struct PushObject {
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
struct PushFindForward { struct PushFindForward {
before: bool, before: bool,
multiline: bool,
} }
#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]
@ -79,6 +80,7 @@ struct PushFindForward {
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
struct PushFindBackward { struct PushFindBackward {
after: bool, after: bool,
multiline: bool,
} }
#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]
@ -500,6 +502,7 @@ impl Vim {
vim.push_operator( vim.push_operator(
Operator::FindForward { Operator::FindForward {
before: action.before, before: action.before,
multiline: action.multiline,
}, },
window, window,
cx, cx,
@ -510,6 +513,7 @@ impl Vim {
vim.push_operator( vim.push_operator(
Operator::FindBackward { Operator::FindBackward {
after: action.after, after: action.after,
multiline: action.multiline,
}, },
window, window,
cx, cx,
@ -1513,11 +1517,11 @@ impl Vim {
} }
match self.active_operator() { match self.active_operator() {
Some(Operator::FindForward { before }) => { Some(Operator::FindForward { before, multiline }) => {
let find = Motion::FindForward { let find = Motion::FindForward {
before, before,
char: text.chars().next().unwrap(), char: text.chars().next().unwrap(),
mode: if VimSettings::get_global(cx).use_multiline_find { mode: if multiline {
FindRange::MultiLine FindRange::MultiLine
} else { } else {
FindRange::SingleLine FindRange::SingleLine
@ -1527,11 +1531,11 @@ impl Vim {
Vim::globals(cx).last_find = Some(find.clone()); Vim::globals(cx).last_find = Some(find.clone());
self.motion(find, window, cx) self.motion(find, window, cx)
} }
Some(Operator::FindBackward { after }) => { Some(Operator::FindBackward { after, multiline }) => {
let find = Motion::FindBackward { let find = Motion::FindBackward {
after, after,
char: text.chars().next().unwrap(), char: text.chars().next().unwrap(),
mode: if VimSettings::get_global(cx).use_multiline_find { mode: if multiline {
FindRange::MultiLine FindRange::MultiLine
} else { } else {
FindRange::SingleLine FindRange::SingleLine
@ -1729,7 +1733,6 @@ struct VimSettings {
pub default_mode: Mode, pub default_mode: Mode,
pub toggle_relative_line_numbers: bool, pub toggle_relative_line_numbers: bool,
pub use_system_clipboard: UseSystemClipboard, pub use_system_clipboard: UseSystemClipboard,
pub use_multiline_find: bool,
pub use_smartcase_find: bool, pub use_smartcase_find: bool,
pub custom_digraphs: HashMap<String, Arc<str>>, pub custom_digraphs: HashMap<String, Arc<str>>,
pub highlight_on_yank_duration: u64, pub highlight_on_yank_duration: u64,
@ -1741,7 +1744,6 @@ struct VimSettingsContent {
pub default_mode: Option<ModeContent>, pub default_mode: Option<ModeContent>,
pub toggle_relative_line_numbers: Option<bool>, pub toggle_relative_line_numbers: Option<bool>,
pub use_system_clipboard: Option<UseSystemClipboard>, pub use_system_clipboard: Option<UseSystemClipboard>,
pub use_multiline_find: Option<bool>,
pub use_smartcase_find: Option<bool>, pub use_smartcase_find: Option<bool>,
pub custom_digraphs: Option<HashMap<String, Arc<str>>>, pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
pub highlight_on_yank_duration: Option<u64>, pub highlight_on_yank_duration: Option<u64>,
@ -1794,9 +1796,6 @@ impl Settings for VimSettings {
use_system_clipboard: settings use_system_clipboard: settings
.use_system_clipboard .use_system_clipboard
.ok_or_else(Self::missing_default)?, .ok_or_else(Self::missing_default)?,
use_multiline_find: settings
.use_multiline_find
.ok_or_else(Self::missing_default)?,
use_smartcase_find: settings use_smartcase_find: settings
.use_smartcase_find .use_smartcase_find
.ok_or_else(Self::missing_default)?, .ok_or_else(Self::missing_default)?,

View file

@ -561,7 +561,7 @@ You can change the following settings to modify vim mode's behavior:
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| default_mode | The default mode to start in. One of "normal", "insert", "replace", "visual", "visual_line", "visual_block", "helix_normal". | "normal" | | default_mode | The default mode to start in. One of "normal", "insert", "replace", "visual", "visual_line", "visual_block", "helix_normal". | "normal" |
| 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_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 | If `true`, `f` and `t` motions extend across multiple lines. | false | | use_multiline_find | deprecated |
| use_smartcase_find | If `true`, `f` and `t` motions are case-insensitive when the target letter is lowercase. | false | | use_smartcase_find | If `true`, `f` and `t` motions are case-insensitive when the target letter is lowercase. | 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 | | 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. | {} | | custom_digraphs | An object that allows you to add custom digraphs. Read below for an example. | {} |
@ -586,7 +586,6 @@ Here's an example of these settings changed:
"vim": { "vim": {
"default_mode": "insert", "default_mode": "insert",
"use_system_clipboard": "never", "use_system_clipboard": "never",
"use_multiline_find": true,
"use_smartcase_find": true, "use_smartcase_find": true,
"toggle_relative_line_numbers": true, "toggle_relative_line_numbers": true,
"highlight_on_yank_duration": 50, "highlight_on_yank_duration": 50,