Helix Select Mode (#37748)
Please credit @eliaperantoni, for the original PR (#34136). Merge after (#34060) to avoid conflicts. Closes https://github.com/zed-industries/zed/issues/33838 Closes https://github.com/zed-industries/zed/issues/33906 Release Notes: - Helix will no longer sometimes fall out into "normal" mode, will remain in "helix normal" (example: vv) - Added dedicated "helix select" mode that can be targeted by keybindings Known issues: - [ ] Helix motion, especially surround-add will not properly work in visual mode, as it won't call `helix_move_cursor`. It is possible however to respect self.mode in change_selection now. - [ ] Some operations, such as `Ctrl+A` (increment) or `>` (indent) will collapse selection also. I haven't found a way to avoid it. --------- Co-authored-by: fantacell <ghub@giggo.de> Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
co-authored by
fantacell
Conrad Irwin
parent
a577128163
commit
cba9ff55c7
+27
-1
@@ -325,6 +325,27 @@
|
||||
"\"": "vim::PushRegister"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == helix_select",
|
||||
"bindings": {
|
||||
"escape": "vim::NormalBefore",
|
||||
";": "vim::HelixCollapseSelection",
|
||||
"~": "vim::ChangeCase",
|
||||
"ctrl-a": "vim::Increment",
|
||||
"ctrl-x": "vim::Decrement",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"i": "vim::InsertBefore",
|
||||
"a": "vim::InsertAfter",
|
||||
"p": "vim::Paste",
|
||||
"u": "vim::Undo",
|
||||
"r": "vim::PushReplace",
|
||||
"s": "vim::Substitute",
|
||||
"ctrl-pageup": "pane::ActivatePreviousItem",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
".": "vim::Repeat",
|
||||
"alt-.": "vim::RepeatFind"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == insert",
|
||||
"bindings": {
|
||||
@@ -396,7 +417,12 @@
|
||||
"bindings": {
|
||||
"i": "vim::HelixInsert",
|
||||
"a": "vim::HelixAppend",
|
||||
"ctrl-[": "editor::Cancel",
|
||||
"ctrl-[": "editor::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "(vim_mode == helix_normal || vim_mode == helix_select) && !menu",
|
||||
"bindings": {
|
||||
";": "vim::HelixCollapseSelection",
|
||||
":": "command_palette::Toggle",
|
||||
"m": "vim::PushHelixMatch",
|
||||
|
||||
+14
-19
@@ -6,7 +6,7 @@ use editor::display_map::DisplaySnapshot;
|
||||
use editor::{
|
||||
DisplayPoint, Editor, HideMouseCursorOrigin, SelectionEffects, ToOffset, ToPoint, movement,
|
||||
};
|
||||
use gpui::{Action, actions};
|
||||
use gpui::actions;
|
||||
use gpui::{Context, Window};
|
||||
use language::{CharClassifier, CharKind, Point};
|
||||
use text::{Bias, SelectionGoal};
|
||||
@@ -21,8 +21,6 @@ use crate::{
|
||||
actions!(
|
||||
vim,
|
||||
[
|
||||
/// Switches to normal mode after the cursor (Helix-style).
|
||||
HelixNormalAfter,
|
||||
/// Yanks the current selection or character if no selection.
|
||||
HelixYank,
|
||||
/// Inserts at the beginning of the selection.
|
||||
@@ -37,7 +35,6 @@ actions!(
|
||||
);
|
||||
|
||||
pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
||||
Vim::action(editor, cx, Vim::helix_normal_after);
|
||||
Vim::action(editor, cx, Vim::helix_select_lines);
|
||||
Vim::action(editor, cx, Vim::helix_insert);
|
||||
Vim::action(editor, cx, Vim::helix_append);
|
||||
@@ -46,21 +43,6 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
||||
}
|
||||
|
||||
impl Vim {
|
||||
pub fn helix_normal_after(
|
||||
&mut self,
|
||||
action: &HelixNormalAfter,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.active_operator().is_some() {
|
||||
self.operator_stack.clear();
|
||||
self.sync_vim_settings(window, cx);
|
||||
return;
|
||||
}
|
||||
self.stop_recording_immediately(action.boxed_clone(), cx);
|
||||
self.switch_mode(Mode::HelixNormal, false, window, cx);
|
||||
}
|
||||
|
||||
pub fn helix_normal_motion(
|
||||
&mut self,
|
||||
motion: Motion,
|
||||
@@ -854,6 +836,19 @@ mod test {
|
||||
cx.assert_state("foo hello worldˇ baz", Mode::HelixNormal);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_helix_select_mode(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
|
||||
assert_eq!(cx.mode(), Mode::Normal);
|
||||
cx.enable_helix();
|
||||
|
||||
cx.simulate_keystrokes("v");
|
||||
assert_eq!(cx.mode(), Mode::HelixSelect);
|
||||
cx.simulate_keystrokes("escape");
|
||||
assert_eq!(cx.mode(), Mode::HelixNormal);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_insert_mode_stickiness(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
|
||||
@@ -63,11 +63,7 @@ impl Vim {
|
||||
}
|
||||
});
|
||||
|
||||
if HelixModeSetting::get_global(cx).0 {
|
||||
self.switch_mode(Mode::HelixNormal, false, window, cx);
|
||||
} else {
|
||||
self.switch_mode(Mode::Normal, false, window, cx);
|
||||
}
|
||||
self.switch_mode(Mode::Normal, false, window, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -692,7 +692,7 @@ impl Vim {
|
||||
}
|
||||
}
|
||||
|
||||
Mode::HelixNormal => {}
|
||||
Mode::HelixNormal | Mode::HelixSelect => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,7 +726,9 @@ impl Vim {
|
||||
self.visual_motion(motion, count, window, cx)
|
||||
}
|
||||
|
||||
Mode::HelixNormal => self.helix_normal_motion(motion, count, window, cx),
|
||||
Mode::HelixNormal | Mode::HelixSelect => {
|
||||
self.helix_normal_motion(motion, count, window, cx)
|
||||
}
|
||||
}
|
||||
self.clear_operator(window, cx);
|
||||
if let Some(operator) = waiting_operator {
|
||||
|
||||
@@ -212,7 +212,7 @@ impl Vim {
|
||||
}
|
||||
}
|
||||
|
||||
Mode::HelixNormal => {
|
||||
Mode::HelixNormal | Mode::HelixSelect => {
|
||||
if selection.is_empty() {
|
||||
// Handle empty selection by operating on the whole word
|
||||
let (word_range, _) = snapshot.surrounding_word(selection.start, false);
|
||||
|
||||
@@ -398,7 +398,7 @@ impl Vim {
|
||||
|
||||
match self.mode {
|
||||
Mode::Normal | Mode::HelixNormal => self.normal_object(object, count, window, cx),
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock | Mode::HelixSelect => {
|
||||
self.visual_object(object, count, window, cx)
|
||||
}
|
||||
Mode::Insert | Mode::Replace => {
|
||||
|
||||
@@ -46,6 +46,7 @@ pub enum Mode {
|
||||
VisualLine,
|
||||
VisualBlock,
|
||||
HelixNormal,
|
||||
HelixSelect,
|
||||
}
|
||||
|
||||
impl Display for Mode {
|
||||
@@ -58,6 +59,7 @@ impl Display for Mode {
|
||||
Mode::VisualLine => write!(f, "VISUAL LINE"),
|
||||
Mode::VisualBlock => write!(f, "VISUAL BLOCK"),
|
||||
Mode::HelixNormal => write!(f, "HELIX NORMAL"),
|
||||
Mode::HelixSelect => write!(f, "HELIX SELECT"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +67,7 @@ impl Display for Mode {
|
||||
impl Mode {
|
||||
pub fn is_visual(&self) -> bool {
|
||||
match self {
|
||||
Self::Visual | Self::VisualLine | Self::VisualBlock => true,
|
||||
Self::Visual | Self::VisualLine | Self::VisualBlock | Self::HelixSelect => true,
|
||||
Self::Normal | Self::Insert | Self::Replace | Self::HelixNormal => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,7 +443,7 @@ impl NeovimConnection {
|
||||
}
|
||||
Mode::Insert | Mode::Normal | Mode::Replace => selections
|
||||
.push(Point::new(selection_row, selection_col)..Point::new(cursor_row, cursor_col)),
|
||||
Mode::HelixNormal => unreachable!(),
|
||||
Mode::HelixNormal | Mode::HelixSelect => unreachable!(),
|
||||
}
|
||||
|
||||
let ranges = encode_ranges(&text, &selections);
|
||||
|
||||
+21
-10
@@ -516,11 +516,7 @@ impl Vim {
|
||||
|
||||
vim.update(cx, |_, cx| {
|
||||
Vim::action(editor, cx, |vim, _: &SwitchToNormalMode, window, cx| {
|
||||
if HelixModeSetting::get_global(cx).0 {
|
||||
vim.switch_mode(Mode::HelixNormal, false, window, cx)
|
||||
} else {
|
||||
vim.switch_mode(Mode::Normal, false, window, cx)
|
||||
}
|
||||
vim.switch_mode(Mode::Normal, false, window, cx)
|
||||
});
|
||||
|
||||
Vim::action(editor, cx, |vim, _: &SwitchToInsertMode, window, cx| {
|
||||
@@ -1030,6 +1026,13 @@ impl Vim {
|
||||
editor.set_relative_line_number(Some(is_relative), cx)
|
||||
});
|
||||
}
|
||||
if HelixModeSetting::get_global(cx).0 {
|
||||
if self.mode == Mode::Normal {
|
||||
self.mode = Mode::HelixNormal
|
||||
} else if self.mode == Mode::Visual {
|
||||
self.mode = Mode::HelixSelect
|
||||
}
|
||||
}
|
||||
|
||||
if leave_selections {
|
||||
return;
|
||||
@@ -1151,7 +1154,7 @@ impl Vim {
|
||||
}
|
||||
Mode::HelixNormal => cursor_shape.normal.unwrap_or(CursorShape::Block),
|
||||
Mode::Replace => cursor_shape.replace.unwrap_or(CursorShape::Underline),
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock | Mode::HelixSelect => {
|
||||
cursor_shape.visual.unwrap_or(CursorShape::Block)
|
||||
}
|
||||
Mode::Insert => cursor_shape.insert.unwrap_or({
|
||||
@@ -1175,7 +1178,8 @@ impl Vim {
|
||||
| Mode::Replace
|
||||
| Mode::Visual
|
||||
| Mode::VisualLine
|
||||
| Mode::VisualBlock => false,
|
||||
| Mode::VisualBlock
|
||||
| Mode::HelixSelect => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1190,7 +1194,8 @@ impl Vim {
|
||||
| Mode::VisualLine
|
||||
| Mode::VisualBlock
|
||||
| Mode::Replace
|
||||
| Mode::HelixNormal => false,
|
||||
| Mode::HelixNormal
|
||||
| Mode::HelixSelect => false,
|
||||
Mode::Normal => true,
|
||||
}
|
||||
}
|
||||
@@ -1202,6 +1207,7 @@ impl Vim {
|
||||
Mode::Insert => "insert",
|
||||
Mode::Replace => "replace",
|
||||
Mode::HelixNormal => "helix_normal",
|
||||
Mode::HelixSelect => "helix_select",
|
||||
}
|
||||
.to_string();
|
||||
|
||||
@@ -1227,7 +1233,12 @@ impl Vim {
|
||||
}
|
||||
}
|
||||
|
||||
if mode == "normal" || mode == "visual" || mode == "operator" || mode == "helix_normal" {
|
||||
if mode == "normal"
|
||||
|| mode == "visual"
|
||||
|| mode == "operator"
|
||||
|| mode == "helix_normal"
|
||||
|| mode == "helix_select"
|
||||
{
|
||||
context.add("VimControl");
|
||||
}
|
||||
context.set("vim_mode", mode);
|
||||
@@ -1522,7 +1533,7 @@ impl Vim {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match self.mode {
|
||||
Mode::VisualLine | Mode::VisualBlock | Mode::Visual => {
|
||||
Mode::VisualLine | Mode::VisualBlock | Mode::Visual | Mode::HelixSelect => {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let original_mode = vim.undo_modes.get(transaction_id);
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
|
||||
Reference in New Issue
Block a user