Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

helix: WIP #11130

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 19 additions & 1 deletion assets/keymaps/vim.json
Original file line number Diff line number Diff line change
Expand Up @@ -575,9 +575,27 @@
}
},
{
"context": "Editor && vim_mode == insert",
"context": "Editor && vim_mode == helixnormal",
"bindings": {
"escape": ["vim::SwitchMode", "HelixNormal"]

}
},
{
"context": "Editor && vim_mode == insert && HelixControl",
"bindings": {
"escape": ["vim::SwitchMode", "HelixNormal"]
}
},
{
"context": "Editor && vim_mode == insert && !HelixControl",
"bindings": {
"escape": "vim::NormalBefore",
}
},
{
"context": "Editor && vim_mode == insert",
"bindings": {
"ctrl-c": "vim::NormalBefore",
"ctrl-[": "vim::NormalBefore",
"ctrl-x ctrl-o": "editor::ShowCompletions",
Expand Down
2 changes: 2 additions & 0 deletions assets/settings/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"multi_cursor_modifier": "alt",
// Whether to enable vim modes and key bindings.
"vim_mode": false,
"helix_mode": false,
// Whether to show the informational hover box when moving the mouse
// over symbols in the editor.
"hover_popover_enabled": true,
Expand Down Expand Up @@ -792,6 +793,7 @@
"use_multiline_find": false,
"use_smartcase_find": false
},
"modal_editor_type": "off",
// The server to connect to. If the environment variable
// ZED_SERVER_URL is set, it will override this setting.
"server_url": "https://zed.dev",
Expand Down
269 changes: 269 additions & 0 deletions crates/vim/src/helix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
use editor::{movement, scroll::Autoscroll};
use gpui::actions;
use language::{char_kind, CharKind};
use ui::{ViewContext, WindowContext};
use workspace::Workspace;

use crate::{motion::Motion, normal::normal_motion, visual::visual_motion, Vim};

actions!(helix, [SelectNextLine,]);

pub fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
workspace.register_action(|_: &mut Workspace, _: &SelectNextLine, cx: _| select_next_line(cx));
}

fn select_next_line(cx: &mut WindowContext) {}

pub fn helix_normal_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
// Helix only selects the last of the motions
if let Some(times) = times {
normal_motion(motion.to_owned(), None, Some(times - 1), cx);
};
match motion {
Motion::Up { .. } | Motion::Down { .. } | Motion::Right | Motion::Left => {
normal_motion(motion.to_owned(), None, None, cx);
select_current(cx);
}
Motion::NextWordStart { .. } => {
next_word(motion, cx);
}
Motion::NextWordEnd { .. } => {
next_word(motion, cx);
}
Motion::PreviousWordStart { .. } => {
// TODO same problem as NextWordEnd (single punctuation in word)
clear_selection(cx);
normal_motion(Motion::Left, None, None, cx);
visual_motion(motion.to_owned(), None, cx);
}
_ => {
clear_selection(cx);
visual_motion(motion.to_owned(), None, cx);
}
};
}

// calling this with motion other than NextWordStart or NextWordEnd panicks
fn next_word(motion: Motion, cx: &mut WindowContext) {
// TODO: single punctuation in word are skipped in NextWordEnd

// exceptions from simple "select from next char to delimiter"
let mut select_cur = false;
let mut rev = false;
let (left_kind, right_kind) = get_left_right_kind(cx);
if !is_selection_reverse(cx) {
match motion {
Motion::NextWordStart { .. } => {
if !(left_kind == CharKind::Whitespace && right_kind != CharKind::Whitespace) {
select_cur = true;
}
}
Motion::NextWordEnd { .. } => {
if !(left_kind != CharKind::Whitespace && right_kind == CharKind::Whitespace) {
select_cur = true;
}
}
_ => todo!("error"),
}
if (left_kind == CharKind::Punctuation || right_kind == CharKind::Punctuation)
&& left_kind != right_kind
{
select_cur = false;
}
} else {
if left_kind == CharKind::Whitespace && right_kind != CharKind::Whitespace {
rev = true;
}
}
println!(
"{:?}, {:?}; rev: {rev}, sel_cur: {select_cur}",
left_kind, right_kind
);
if select_cur {
select_current(cx);
} else if rev {
normal_motion(Motion::Right, None, None, cx);
clear_selection(cx);
} else {
clear_selection(cx);
}
visual_motion(motion.to_owned(), None, cx);
match motion {
Motion::NextWordStart { .. } => visual_motion(Motion::Left, None, cx),
_ => {}
}
}

fn is_selection_reverse(cx: &mut WindowContext) -> bool {
let mut rev = false;
Vim::update(cx, |vim, cx| {
vim.update_active_editor(cx, |_, editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_with(|_, selection| {
rev = selection.reversed;
});
});
});
});
rev
}

fn get_left_right_kind(cx: &mut WindowContext) -> (CharKind, CharKind) {
let mut right_kind = CharKind::Whitespace;
let mut left_kind = CharKind::Whitespace;
Vim::update(cx, |vim, cx| {
vim.update_active_editor(cx, |_, editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_with(|map, selection| {
let scope = map
.buffer_snapshot
.language_scope_at(selection.end.to_point(map));

// find chars around cursor
let mut chars = map.display_chars_at(selection.start);
if !selection.reversed {
for ch in chars {
if ch.1 == selection.head() {
right_kind = char_kind(&scope, ch.0);
break;
} else {
left_kind = char_kind(&scope, ch.0);
}
}
} else {
if let (Some(left), Some(right)) = (chars.next(), chars.next()) {
(left_kind, right_kind) =
(char_kind(&scope, left.0), char_kind(&scope, right.0));
}
}
});
});
});
});
(left_kind, right_kind)
}

fn clear_selection(cx: &mut WindowContext) {
Vim::update(cx, |vim, cx| {
vim.update_active_editor(cx, |_, editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_with(|_, selection| {
let point = selection.head();
selection.collapse_to(point, selection.goal);
});
});
});
});
}

fn select_current(cx: &mut WindowContext) {
// go left so selecting right selects current
normal_motion(Motion::Left, None, None, cx);
Vim::update(cx, |vim, cx| {
vim.update_active_editor(cx, |_, editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_with(|map, selection| {
// Clear previous selection
let point = selection.head();
selection.collapse_to(point, selection.goal);
//select right
selection.end = movement::right(map, selection.start)
});
});
});
});
}

// fn next_word_start(motion: Motion, cx: &mut WindowContext) {
// let mut select_cur = false;
// let mut rev = false;
// Vim::update(cx, |vim, cx| {
// vim.update_active_editor(cx, |_, editor, cx| {
// editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
// s.move_with(|map, selection| {
// let mut current_char = ' ';
// let mut next_char = ' ';

// let mut chars = map.display_chars_at(selection.start);
// if !selection.reversed {
// for ch in chars {
// if ch.1 == selection.head() {
// next_char = ch.0;
// break;
// } else {
// current_char = ch.0;
// }
// }
// // if on space right before word, dont select that space
// if !(current_char == ' ' && next_char != ' ') {
// select_cur = true;
// }
// } else {
// if let (Some(cur), Some(next)) = (chars.next(), chars.next()) {
// if cur.0 == ' ' && next.0 != ' ' {
// rev = true;
// }
// }
// }
// });
// });
// });
// });

// if select_cur {
// select_current(cx);
// } else if rev {
// normal_motion(Motion::Right, None, None, cx);
// clear_selection(cx);
// } else {
// clear_selection(cx);
// }
// visual_motion(motion.to_owned(), None, cx);
// visual_motion(Motion::Left, None, cx);
// }

// fn next_word_end(motion: Motion, cx: &mut WindowContext) {
// let mut select_cur = false;
// let mut rev = false;
// Vim::update(cx, |vim, cx| {
// vim.update_active_editor(cx, |_, editor, cx| {
// editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
// s.move_with(|map, selection| {
// let mut current_char = ' ';
// let mut next_char = ' ';

// let mut chars = map.display_chars_at(selection.start);
// if !selection.reversed {
// for ch in chars {
// if ch.1 == selection.head() {
// next_char = ch.0;
// break;
// } else {
// current_char = ch.0;
// }
// }
// if !(current_char != ' ' && next_char == ' ') {
// select_cur = true;
// }
// } else {
// if let (Some(cur), Some(next)) = (chars.next(), chars.next()) {
// if cur.0 == ' ' && next.0 != ' ' {
// rev = true;
// }
// }
// }
// // if on space right before word, dont select that space
// });
// });
// });
// });
// if select_cur {
// select_current(cx);
// } else if rev {
// normal_motion(Motion::Right, None, None, cx);
// clear_selection(cx);
// } else {
// clear_selection(cx);
// }
// visual_motion(motion.to_owned(), None, cx);
// }
27 changes: 13 additions & 14 deletions crates/vim/src/motion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::ops::Range;
use workspace::Workspace;

use crate::{
helix::helix_normal_motion,
normal::{mark, normal_motion},
state::{Mode, Operator},
surrounds::SurroundsType,
Expand Down Expand Up @@ -398,22 +399,19 @@ pub(crate) fn search_motion(m: Motion, cx: &mut WindowContext) {
prior_selections, ..
} = &m
{
match Vim::read(cx).state().mode {
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
if !prior_selections.is_empty() {
Vim::update(cx, |vim, cx| {
vim.update_active_editor(cx, |_, editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges(prior_selections.iter().cloned())
})
});
if Vim::read(cx).state().mode.is_visual() {
if !prior_selections.is_empty() {
Vim::update(cx, |vim, cx| {
vim.update_active_editor(cx, |_, editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges(prior_selections.iter().cloned())
})
});
}
});
}
Mode::Normal | Mode::Replace | Mode::Insert => {
if Vim::read(cx).active_operator().is_none() {
return;
}
} else {
if Vim::read(cx).active_operator().is_none() {
return;
}
}
}
Expand Down Expand Up @@ -444,6 +442,7 @@ pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
visual_motion(motion.clone(), count, cx)
}
Mode::HelixNormal => helix_normal_motion(motion.clone(), count, cx),
Mode::Insert => {
// Shouldn't execute a motion in insert mode. Ignoring
}
Expand Down
2 changes: 1 addition & 1 deletion crates/vim/src/normal/case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ where
cursor_positions.push(selection.start..selection.start);
}
}
Mode::Insert | Mode::Normal | Mode::Replace => {
Mode::Insert | Mode::Normal | Mode::Replace | Mode::HelixNormal => {
let start = selection.start;
let mut end = start;
for _ in 0..count {
Expand Down
4 changes: 3 additions & 1 deletion crates/vim/src/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
fn object(object: Object, cx: &mut WindowContext) {
match Vim::read(cx).state().mode {
Mode::Normal => normal_object(object, cx),
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_object(object, cx),
Mode::Visual | Mode::VisualLine | Mode::VisualBlock | Mode::HelixNormal => {
visual_object(object, cx)
}
Mode::Insert | Mode::Replace => {
// Shouldn't execute a text object in insert mode. Ignoring
}
Expand Down