vim: (BREAKING) clean up keymap contexts (#14233)

Release Notes:

- vim: (BREAKING) Improved vim keymap contexts.

Previously `vim_mode == normal` was true even when operators were
pending, which led to bugs like #13789 and a requirement for custom
keymaps to exclude various conditions like (`!VimObject` and
`!VimWaiting`) to avoid bugs.

Now `vim_mode` will be set to `operator` or `waiting` in these cases as
described in [the docs](https://zed.dev/docs/vim#keybindings). For most
custom keymaps this change will be a no-op or an improvement, but if you
were deliberately relying on the old behaviour (if you were relying on
`VimObject` or `VimWaiting` becoming true) you will need to update your
keymap.

---------

Co-authored-by: Thorsten <thorsten@zed.dev>
This commit is contained in:
Conrad Irwin 2024-07-11 13:16:26 -06:00 committed by GitHub
parent 8e853e2b56
commit b0dbc80575
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 186 additions and 246 deletions

View file

@ -228,21 +228,19 @@ impl EditorState {
}
}
pub fn vim_controlled(&self) -> bool {
let is_insert_mode = matches!(self.mode, Mode::Insert);
if !is_insert_mode {
return true;
pub fn editor_input_enabled(&self) -> bool {
match self.mode {
Mode::Insert => {
if let Some(operator) = self.operator_stack.last() {
!operator.is_waiting(self.mode)
} else {
true
}
}
Mode::Normal | Mode::Replace | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
false
}
}
matches!(
self.operator_stack.last(),
Some(Operator::FindForward { .. })
| Some(Operator::FindBackward { .. })
| Some(Operator::Mark)
| Some(Operator::Register)
| Some(Operator::RecordRegister)
| Some(Operator::ReplayRegister)
| Some(Operator::Jump { .. })
)
}
pub fn should_autoindent(&self) -> bool {
@ -264,48 +262,39 @@ impl EditorState {
pub fn keymap_context_layer(&self) -> KeyContext {
let mut context = KeyContext::new_with_defaults();
context.set(
"vim_mode",
match self.mode {
Mode::Normal => "normal",
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
Mode::Insert => "insert",
Mode::Replace => "replace",
},
);
if self.vim_controlled() {
context.add("VimControl");
let mut mode = match self.mode {
Mode::Normal => "normal",
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
Mode::Insert => "insert",
Mode::Replace => "replace",
}
.to_string();
if self.active_operator().is_none() && self.pre_count.is_some()
|| self.active_operator().is_some() && self.post_count.is_some()
let mut operator_id = "none";
let active_operator = self.active_operator();
if active_operator.is_none() && self.pre_count.is_some()
|| active_operator.is_some() && self.post_count.is_some()
{
context.add("VimCount");
}
let active_operator = self.active_operator();
if let Some(active_operator) = active_operator.clone() {
for context_flag in active_operator.context_flags().into_iter() {
context.add(*context_flag);
if let Some(active_operator) = active_operator {
if active_operator.is_waiting(self.mode) {
mode = "waiting".to_string();
} else {
mode = "operator".to_string();
operator_id = active_operator.id();
}
}
context.set(
"vim_operator",
active_operator
.clone()
.map(|op| op.id())
.unwrap_or_else(|| "none"),
);
if self.mode == Mode::Replace
|| (matches!(active_operator, Some(Operator::AddSurrounds { .. }))
&& self.mode.is_visual())
{
context.add("VimWaiting");
if mode != "waiting" && mode != "insert" && mode != "replace" {
context.add("VimControl");
}
context.set("vim_mode", mode);
context.set("vim_operator", operator_id);
context
}
}
@ -340,9 +329,9 @@ impl Operator {
}
}
pub fn context_flags(&self) -> &'static [&'static str] {
pub fn is_waiting(&self, mode: Mode) -> bool {
match self {
Operator::Object { .. } | Operator::ChangeSurrounds { target: None } => &["VimObject"],
Operator::AddSurrounds { target } => target.is_some() || mode.is_visual(),
Operator::FindForward { .. }
| Operator::Mark
| Operator::Jump { .. }
@ -351,10 +340,18 @@ impl Operator {
| Operator::RecordRegister
| Operator::ReplayRegister
| Operator::Replace
| Operator::AddSurrounds { target: Some(_) }
| Operator::ChangeSurrounds { .. }
| Operator::DeleteSurrounds => &["VimWaiting"],
_ => &[],
| Operator::ChangeSurrounds { target: Some(_) }
| Operator::DeleteSurrounds => true,
Operator::Change
| Operator::Delete
| Operator::Yank
| Operator::Indent
| Operator::Outdent
| Operator::Lowercase
| Operator::Uppercase
| Operator::Object { .. }
| Operator::ChangeSurrounds { target: None }
| Operator::OppositeCase => false,
}
}
}

View file

@ -76,6 +76,7 @@ struct SelectRegister(String);
actions!(
vim,
[
ClearOperators,
Tab,
Enter,
Object,
@ -129,6 +130,9 @@ fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
Vim::update(cx, |vim, cx| vim.push_operator(operator.clone(), cx))
},
);
workspace.register_action(|_: &mut Workspace, _: &ClearOperators, cx| {
Vim::update(cx, |vim, cx| vim.clear_operator(cx))
});
workspace.register_action(|_: &mut Workspace, n: &Number, cx: _| {
Vim::update(cx, |vim, cx| vim.push_count_digit(n.0, cx));
});
@ -973,7 +977,7 @@ impl Vim {
editor.set_cursor_shape(state.cursor_shape(), cx);
editor.set_clip_at_line_ends(state.clip_at_line_ends(), cx);
editor.set_collapse_matches(true);
editor.set_input_enabled(!state.vim_controlled());
editor.set_input_enabled(state.editor_input_enabled());
editor.set_autoindent(state.should_autoindent());
editor.selections.line_mode = matches!(state.mode, Mode::VisualLine);
if editor.is_focused(cx) || editor.mouse_menu_is_focused(cx) {