extend eidtor mode

Co-authored-by: Oleksiy Syvokon <oleksiy.syvokon@gmail.com>
This commit is contained in:
Smit Barmase 2025-08-22 15:23:27 +05:30
parent c6d0d75711
commit fd0316f7d1
No known key found for this signature in database
12 changed files with 261 additions and 227 deletions

View file

@ -50,14 +50,17 @@ pub enum NotifyWhenAgentWaiting {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
pub enum AgentEditorMode {
Vim,
VimInsert,
Helix,
Default,
EditorModeOverride(EditorMode),
#[default]
Inherit,
}
impl From<EditorMode> for AgentEditorMode {
fn from(b: EditorMode) -> Self {
AgentEditorMode::EditorModeOverride(b)
}
}
#[derive(Default, Clone, Debug)]
pub struct AgentSettings {
pub enabled: bool,

View file

@ -112,6 +112,14 @@ impl MessageEditor {
range: Cell::new(None),
});
let mention_set = MentionSet::default();
let editor_mode = match settings.editor_mode {
agent_settings::AgentEditorMode::EditorModeOverride(mode) => mode,
agent_settings::AgentEditorMode::Inherit => {
vim_mode_setting::EditorModeSetting::get_global(cx).0
}
};
let editor = cx.new(|cx| {
let buffer = cx.new(|cx| Buffer::local("", cx).with_language(Arc::new(language), cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
@ -120,7 +128,7 @@ impl MessageEditor {
editor.set_placeholder_text(placeholder, cx);
editor.set_show_indent_guides(false, cx);
editor.set_soft_wrap();
editor.set_use_modal_editing(true);
editor.set_use_modal_editing(editor_mode);
editor.set_completion_provider(Some(Rc::new(completion_provider)));
editor.set_context_menu_options(ContextMenuOptions {
min_entries_visible: 12,

View file

@ -117,10 +117,7 @@ pub(crate) fn create_editor(
let settings = agent_settings::AgentSettings::get_global(cx);
let editor_mode = match settings.editor_mode {
agent_settings::AgentEditorMode::Vim => vim_mode_setting::EditorMode::Vim,
agent_settings::AgentEditorMode::VimInsert => vim_mode_setting::EditorMode::VimInsert,
agent_settings::AgentEditorMode::Helix => vim_mode_setting::EditorMode::Helix,
agent_settings::AgentEditorMode::Default => vim_mode_setting::EditorMode::Default,
agent_settings::AgentEditorMode::EditorModeOverride(mode) => mode,
agent_settings::AgentEditorMode::Inherit => {
vim_mode_setting::EditorModeSetting::get_global(cx).0
}
@ -139,7 +136,6 @@ pub(crate) fn create_editor(
editor.set_placeholder_text("Message the agent @ to include context", cx);
editor.set_show_indent_guides(false, cx);
editor.set_soft_wrap();
editor.set_use_modal_editing(true);
editor.set_default_editor_mode(editor_mode);
editor.set_context_menu_options(ContextMenuOptions {
min_entries_visible: 12,

View file

@ -76,6 +76,7 @@ util.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
zed_actions.workspace = true
vim_mode_settings.workspace = true
[dev-dependencies]
dap = { workspace = true, features = ["test-support"] }

View file

@ -26,6 +26,7 @@ use std::{cell::RefCell, ops::Range, rc::Rc, usize};
use theme::{Theme, ThemeSettings};
use ui::{ContextMenu, Divider, PopoverMenu, SplitButton, Tooltip, prelude::*};
use util::ResultExt;
use vim_mode_settings::EditorMode;
actions!(
console,
@ -74,7 +75,7 @@ impl Console {
editor.set_show_wrap_guides(false, cx);
editor.set_show_indent_guides(false, cx);
editor.set_show_edit_predictions(Some(false), window, cx);
editor.set_use_modal_editing(false);
editor.set_default_editor_mode(EditorMode::Default);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
editor
});

View file

@ -165,7 +165,6 @@ use project::{
};
use rand::{seq::SliceRandom, thread_rng};
use rpc::{ErrorCode, ErrorExt, proto::PeerId};
use schemars::JsonSchema;
use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
use selections_collection::{
MutableSelectionsCollection, SelectionsCollection, resolve_selections,
@ -202,7 +201,6 @@ use ui::{
IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
};
use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
use vim_mode_setting::EditorModeSetting;
use workspace::{
CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
@ -2185,7 +2183,6 @@ impl Editor {
collapse_matches: false,
workspace: None,
input_enabled: !is_minimap,
use_modal_editing: full_mode,
read_only: is_minimap,
use_autoclose: true,
use_auto_surround: true,
@ -22940,6 +22937,7 @@ pub enum EditorEvent {
anchor: Anchor,
is_deactivate: bool,
},
EditorModeChanged,
}
impl EventEmitter<EditorEvent> for Editor {}

View file

@ -406,7 +406,7 @@ pub(crate) fn commit_message_editor(
commit_editor.set_collaboration_hub(Box::new(project));
commit_editor.set_use_autoclose(false);
commit_editor.set_show_gutter(false, cx);
commit_editor.set_use_modal_editing(true);
// commit_editor.set_use_modal_editing(true); TODO
commit_editor.set_show_wrap_guides(false, cx);
commit_editor.set_show_indent_guides(false, cx);
let placeholder = placeholder.unwrap_or("Enter commit message".into());

View file

@ -75,7 +75,8 @@ pub fn init(cx: &mut App) {
return;
};
if !editor.use_modal_editing() || !editor.buffer().read(cx).is_singleton() {
if !editor.default_editor_mode().is_modal() || !editor.buffer().read(cx).is_singleton()
{
return;
}

View file

@ -637,7 +637,7 @@ impl RulesLibrary {
editor.set_show_gutter(false, cx);
editor.set_show_wrap_guides(false, cx);
editor.set_show_indent_guides(false, cx);
editor.set_use_modal_editing(false);
editor.set_default_editor_mode(EditorMode::Default);
editor.set_current_line_highlight(Some(CurrentLineHighlight::None));
editor.set_completion_provider(Some(make_completion_provider()));
if focus {

View file

@ -32,49 +32,10 @@ use ui::{
StyledTypography, Window, h_flex, rems,
};
use util::ResultExt;
use vim_mode_setting::ModalMode;
use workspace::searchable::Direction;
use workspace::{Workspace, WorkspaceDb, WorkspaceId};
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum Mode {
Normal,
Insert,
Replace,
Visual,
VisualLine,
VisualBlock,
HelixNormal,
}
impl Display for Mode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Mode::Normal => write!(f, "NORMAL"),
Mode::Insert => write!(f, "INSERT"),
Mode::Replace => write!(f, "REPLACE"),
Mode::Visual => write!(f, "VISUAL"),
Mode::VisualLine => write!(f, "VISUAL LINE"),
Mode::VisualBlock => write!(f, "VISUAL BLOCK"),
Mode::HelixNormal => write!(f, "HELIX NORMAL"),
}
}
}
impl Mode {
pub fn is_visual(&self) -> bool {
match self {
Self::Visual | Self::VisualLine | Self::VisualBlock => true,
Self::Normal | Self::Insert | Self::Replace | Self::HelixNormal => false,
}
}
}
impl Default for Mode {
fn default() -> Self {
Self::Normal
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Operator {
Change,
@ -976,7 +937,7 @@ pub struct SearchState {
pub prior_selections: Vec<Range<Anchor>>,
pub prior_operator: Option<Operator>,
pub prior_mode: Mode,
pub prior_mode: ModalMode,
}
impl Operator {
@ -1043,7 +1004,7 @@ impl Operator {
}
}
pub fn is_waiting(&self, mode: Mode) -> bool {
pub fn is_waiting(&self, mode: ModalMode) -> bool {
match self {
Operator::AddSurrounds { target } => target.is_some() || mode.is_visual(),
Operator::FindForward { .. }

View file

@ -40,7 +40,7 @@ use schemars::JsonSchema;
use serde::Deserialize;
use serde_derive::Serialize;
use settings::{Settings, SettingsSources, SettingsStore, update_settings_file};
use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals};
use state::{Operator, RecordedSelection, SearchState, VimGlobals};
use std::{mem, ops::Range, sync::Arc};
use surrounds::SurroundsType;
use theme::ThemeSettings;
@ -360,8 +360,8 @@ impl editor::Addon for VimAddon {
/// The state pertaining to Vim mode.
pub(crate) struct Vim {
pub(crate) mode: Mode,
pub last_mode: Mode,
pub(crate) mode: ModalMode,
pub last_mode: ModalMode,
pub temp_mode: bool,
pub status_label: Option<SharedString>,
pub exit_temporary_mode: bool,
@ -369,11 +369,11 @@ pub(crate) struct Vim {
operator_stack: Vec<Operator>,
pub(crate) replacements: Vec<(Range<editor::Anchor>, String)>,
pub(crate) stored_visual_mode: Option<(Mode, Vec<bool>)>,
pub(crate) stored_visual_mode: Option<(ModalMode, Vec<bool>)>,
pub(crate) current_tx: Option<TransactionId>,
pub(crate) current_anchor: Option<Selection<Anchor>>,
pub(crate) undo_modes: HashMap<TransactionId, Mode>,
pub(crate) undo_modes: HashMap<TransactionId, ModalMode>,
pub(crate) undo_last_line_tx: Option<TransactionId>,
selected_register: Option<char>,
@ -407,16 +407,9 @@ impl Vim {
pub fn new(window: &mut Window, cx: &mut Context<Editor>) -> Entity<Self> {
let editor = cx.entity();
let mut initial_mode = VimSettings::get_global(cx).default_mode;
if initial_mode == Mode::Normal
&& matches!(EditorModeSetting::get_global(cx).0, EditorMode::Helix)
{
initial_mode = Mode::HelixNormal;
}
cx.new(|cx| Vim {
mode: initial_mode,
last_mode: Mode::Normal,
mode: ModalMode::Normal,
last_mode: ModalMode::Normal,
temp_mode: false,
exit_temporary_mode: false,
operator_stack: Vec::new(),
@ -450,56 +443,45 @@ impl Vim {
return;
};
if !editor.use_modal_editing() {
if !editor.default_editor_mode().is_modal() {
return;
}
if editor.default_editor_mode() == EditorMode::Default {
return;
}
let mut was_enabled = Vim::enabled(cx);
let mut was_toggle = VimSettings::get_global(cx).toggle_relative_line_numbers;
cx.observe_global_in::<SettingsStore>(window, move |editor, window, cx| {
let enabled = Vim::enabled(cx);
let toggle = VimSettings::get_global(cx).toggle_relative_line_numbers;
if enabled && was_enabled && (toggle != was_toggle) {
if toggle != was_toggle {
if toggle {
let is_relative = editor
.addon::<VimAddon>()
.map(|vim| vim.entity.read(cx).mode != Mode::Insert);
.map(|vim| vim.entity.read(cx).mode != ModalMode::Insert);
editor.set_relative_line_number(is_relative, cx)
} else {
editor.set_relative_line_number(None, cx)
}
}
was_toggle = VimSettings::get_global(cx).toggle_relative_line_numbers;
if was_enabled == enabled {
return;
}
was_enabled = enabled;
if enabled {
Self::activate(editor, window, cx)
} else {
Self::deactivate(editor, cx)
}
})
.detach();
if was_enabled {
Self::activate(editor, window, cx)
}
let mut was_enabled = true;
cx.observe::<Editor>(window, move |editor, window, cx| {})
.detach();
Self::activate(editor, window, cx)
}
fn activate(editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>) {
let vim = Vim::new(window, cx);
let default_editor_mode = editor.default_editor_mode();
if default_editor_mode == EditorMode::VimInsert {
vim.update(cx, |vim, _| {
vim.mode = Mode::Insert;
});
}
vim.update(cx, |vim, _| {
let initial_mode = match editor.default_editor_mode() {
EditorMode::Default => return,
EditorMode::Vim(modal_mode) => modal_mode,
EditorMode::Helix(modal_mode) => modal_mode,
};
vim.mode = initial_mode;
});
editor.register_addon(VimAddon {
entity: vim.clone(),
@ -511,34 +493,34 @@ impl Vim {
cx,
move |vim, _: &SwitchToNormalMode, window, cx| {
if matches!(default_editor_mode, EditorMode::Helix) {
vim.switch_mode(Mode::HelixNormal, false, window, cx)
vim.switch_mode(ModalMode::HelixNormal, false, window, cx)
} else {
vim.switch_mode(Mode::Normal, false, window, cx)
vim.switch_mode(ModalMode::Normal, false, window, cx)
}
},
);
Vim::action(editor, cx, |vim, _: &SwitchToInsertMode, window, cx| {
vim.switch_mode(Mode::Insert, false, window, cx)
vim.switch_mode(ModalMode::Insert, false, window, cx)
});
Vim::action(editor, cx, |vim, _: &SwitchToReplaceMode, window, cx| {
vim.switch_mode(Mode::Replace, false, window, cx)
vim.switch_mode(ModalMode::Replace, false, window, cx)
});
Vim::action(editor, cx, |vim, _: &SwitchToVisualMode, window, cx| {
vim.switch_mode(Mode::Visual, false, window, cx)
vim.switch_mode(ModalMode::Visual, false, window, cx)
});
Vim::action(editor, cx, |vim, _: &SwitchToVisualLineMode, window, cx| {
vim.switch_mode(Mode::VisualLine, false, window, cx)
vim.switch_mode(ModalMode::VisualLine, false, window, cx)
});
Vim::action(
editor,
cx,
|vim, _: &SwitchToVisualBlockMode, window, cx| {
vim.switch_mode(Mode::VisualBlock, false, window, cx)
vim.switch_mode(ModalMode::VisualBlock, false, window, cx)
},
);
@ -546,7 +528,7 @@ impl Vim {
editor,
cx,
|vim, _: &SwitchToHelixNormalMode, window, cx| {
vim.switch_mode(Mode::HelixNormal, false, window, cx)
vim.switch_mode(ModalMode::HelixNormal, false, window, cx)
},
);
Vim::action(editor, cx, |_, _: &PushForcedMotion, _, cx| {
@ -766,8 +748,8 @@ impl Vim {
// displayed (and performed by `accept_edit_prediction`). This switches to
// insert mode so that the prediction is displayed after the jump.
match vim.mode {
Mode::Replace => {}
_ => vim.switch_mode(Mode::Insert, true, window, cx),
ModalMode::Replace => {}
_ => vim.switch_mode(ModalMode::Insert, true, window, cx),
};
},
);
@ -859,7 +841,7 @@ impl Vim {
{
return;
}
self.switch_mode(Mode::Insert, false, window, cx)
self.switch_mode(ModalMode::Insert, false, window, cx)
}
if let Some(action) = keystroke_event.action.as_ref() {
// Keystroke is handled by the vim system, so continue forward
@ -935,6 +917,24 @@ impl Vim {
vim.set_mark(mark, vec![*anchor], editor.buffer(), window, cx);
});
}
EditorEvent::EditorModeChanged => {
self.update_editor(cx, |vim, editor, cx| {
let enabled = editor.default_editor_mode().is_modal();
if was_enabled == enabled {
return;
}
if !enabled {
editor.set_relative_line_number(None, cx);
}
was_enabled = enabled;
if enabled {
Self::activate(editor, window, cx)
} else {
Self::deactivate(editor, cx)
}
//
});
}
_ => {}
}
}
@ -961,18 +961,21 @@ impl Vim {
pub fn switch_mode(
&mut self,
mode: Mode,
mode: ModalMode,
leave_selections: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
if self.temp_mode && mode == Mode::Normal {
if self.temp_mode && mode == ModalMode::Normal {
self.temp_mode = false;
self.switch_mode(Mode::Normal, leave_selections, window, cx);
self.switch_mode(Mode::Insert, false, window, cx);
self.switch_mode(ModalMode::Normal, leave_selections, window, cx);
self.switch_mode(ModalMode::Insert, false, window, cx);
return;
} else if self.temp_mode
&& !matches!(mode, Mode::Visual | Mode::VisualLine | Mode::VisualBlock)
&& !matches!(
mode,
ModalMode::Visual | ModalMode::VisualLine | ModalMode::VisualBlock
)
{
self.temp_mode = false;
}
@ -986,7 +989,7 @@ impl Vim {
self.operator_stack.clear();
self.selected_register.take();
self.cancel_running_command(window, cx);
if mode == Mode::Normal || mode != last_mode {
if mode == ModalMode::Normal || mode != last_mode {
self.current_tx.take();
self.current_anchor.take();
self.update_editor(cx, |_, editor, _| {
@ -994,7 +997,7 @@ impl Vim {
});
}
Vim::take_forced_motion(cx);
if mode != Mode::Insert && mode != Mode::Replace {
if mode != ModalMode::Insert && mode != ModalMode::Replace {
Vim::take_count(cx);
}
@ -1003,10 +1006,10 @@ impl Vim {
if VimSettings::get_global(cx).toggle_relative_line_numbers
&& self.mode != self.last_mode
&& (self.mode == Mode::Insert || self.last_mode == Mode::Insert)
&& (self.mode == ModalMode::Insert || self.last_mode == ModalMode::Insert)
{
self.update_editor(cx, |vim, editor, cx| {
let is_relative = vim.mode != Mode::Insert;
let is_relative = vim.mode != ModalMode::Insert;
editor.set_relative_line_number(Some(is_relative), cx)
});
}
@ -1021,13 +1024,15 @@ impl Vim {
// Adjust selections
self.update_editor(cx, |vim, editor, cx| {
if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock
if last_mode != ModalMode::VisualBlock
&& last_mode.is_visual()
&& mode == ModalMode::VisualBlock
{
vim.visual_block_motion(true, editor, window, cx, |_, point, goal| {
Some((point, goal))
})
}
if (last_mode == Mode::Insert || last_mode == Mode::Replace)
if (last_mode == ModalMode::Insert || last_mode == ModalMode::Replace)
&& let Some(prior_tx) = prior_tx
{
editor.group_until_transaction(prior_tx, cx)
@ -1037,15 +1042,15 @@ impl Vim {
// we cheat with visual block mode and use multiple cursors.
// the cost of this cheat is we need to convert back to a single
// cursor whenever vim would.
if last_mode == Mode::VisualBlock
&& (mode != Mode::VisualBlock && mode != Mode::Insert)
if last_mode == ModalMode::VisualBlock
&& (mode != ModalMode::VisualBlock && mode != ModalMode::Insert)
{
let tail = s.oldest_anchor().tail();
let head = s.newest_anchor().head();
s.select_anchor_ranges(vec![tail..head]);
} else if last_mode == Mode::Insert
&& prior_mode == Mode::VisualBlock
&& mode != Mode::VisualBlock
} else if last_mode == ModalMode::Insert
&& prior_mode == ModalMode::VisualBlock
&& mode != ModalMode::VisualBlock
{
let pos = s.first_anchor().head();
s.select_anchor_ranges(vec![pos..pos])
@ -1110,7 +1115,7 @@ impl Vim {
pub fn cursor_shape(&self, cx: &mut App) -> CursorShape {
let cursor_shape = VimSettings::get_global(cx).cursor_shape;
match self.mode {
Mode::Normal => {
ModalMode::Normal => {
if let Some(operator) = self.operator_stack.last() {
match operator {
// Navigation operators -> Block cursor
@ -1129,12 +1134,12 @@ impl Vim {
cursor_shape.normal.unwrap_or(CursorShape::Block)
}
}
Mode::HelixNormal => cursor_shape.normal.unwrap_or(CursorShape::Block),
Mode::Replace => cursor_shape.replace.unwrap_or(CursorShape::Underline),
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
ModalMode::HelixNormal => cursor_shape.normal.unwrap_or(CursorShape::Block),
ModalMode::Replace => cursor_shape.replace.unwrap_or(CursorShape::Underline),
ModalMode::Visual | ModalMode::VisualLine | ModalMode::VisualBlock => {
cursor_shape.visual.unwrap_or(CursorShape::Block)
}
Mode::Insert => cursor_shape.insert.unwrap_or({
ModalMode::Insert => cursor_shape.insert.unwrap_or({
let editor_settings = EditorSettings::get_global(cx);
editor_settings.cursor_shape.unwrap_or_default()
}),
@ -1143,45 +1148,45 @@ impl Vim {
pub fn editor_input_enabled(&self) -> bool {
match self.mode {
Mode::Insert => {
ModalMode::Insert => {
if let Some(operator) = self.operator_stack.last() {
!operator.is_waiting(self.mode)
} else {
true
}
}
Mode::Normal
| Mode::HelixNormal
| Mode::Replace
| Mode::Visual
| Mode::VisualLine
| Mode::VisualBlock => false,
ModalMode::Normal
| ModalMode::HelixNormal
| ModalMode::Replace
| ModalMode::Visual
| ModalMode::VisualLine
| ModalMode::VisualBlock => false,
}
}
pub fn should_autoindent(&self) -> bool {
!(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
!(self.mode == ModalMode::Insert && self.last_mode == ModalMode::VisualBlock)
}
pub fn clip_at_line_ends(&self) -> bool {
match self.mode {
Mode::Insert
| Mode::Visual
| Mode::VisualLine
| Mode::VisualBlock
| Mode::Replace
| Mode::HelixNormal => false,
Mode::Normal => true,
ModalMode::Insert
| ModalMode::Visual
| ModalMode::VisualLine
| ModalMode::VisualBlock
| ModalMode::Replace
| ModalMode::HelixNormal => false,
ModalMode::Normal => true,
}
}
pub fn extend_key_context(&self, context: &mut KeyContext, cx: &App) {
let mut mode = match self.mode {
Mode::Normal => "normal",
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
Mode::Insert => "insert",
Mode::Replace => "replace",
Mode::HelixNormal => "helix_normal",
ModalMode::Normal => "normal",
ModalMode::Visual | ModalMode::VisualLine | ModalMode::VisualBlock => "visual",
ModalMode::Insert => "insert",
ModalMode::Replace => "replace",
ModalMode::HelixNormal => "helix_normal",
}
.to_string();
@ -1226,12 +1231,12 @@ impl Vim {
if editor_mode.is_full()
&& !newest_selection_empty
&& self.mode == Mode::Normal
&& self.mode == ModalMode::Normal
// When following someone, don't switch vim mode.
&& editor.leader_id().is_none()
{
if preserve_selection {
self.switch_mode(Mode::Visual, true, window, cx);
self.switch_mode(ModalMode::Visual, true, window, cx);
} else {
self.update_editor(cx, |_, editor, cx| {
editor.set_clip_at_line_ends(false, cx);
@ -1257,13 +1262,13 @@ impl Vim {
});
self.update_editor(cx, |vim, editor, cx| {
let is_relative = vim.mode != Mode::Insert;
let is_relative = vim.mode != ModalMode::Insert;
editor.set_relative_line_number(Some(is_relative), cx)
});
}
} else {
self.update_editor(cx, |vim, editor, cx| {
let is_relative = vim.mode != Mode::Insert;
let is_relative = vim.mode != ModalMode::Insert;
editor.set_relative_line_number(Some(is_relative), cx)
});
}
@ -1351,19 +1356,19 @@ impl Vim {
if let Some((oldest, newest)) = selections {
globals.recorded_selection = match self.mode {
Mode::Visual if newest.end.row == newest.start.row => {
ModalMode::Visual if newest.end.row == newest.start.row => {
RecordedSelection::SingleLine {
cols: newest.end.column - newest.start.column,
}
}
Mode::Visual => RecordedSelection::Visual {
ModalMode::Visual => RecordedSelection::Visual {
rows: newest.end.row - newest.start.row,
cols: newest.end.column,
},
Mode::VisualLine => RecordedSelection::VisualLine {
ModalMode::VisualLine => RecordedSelection::VisualLine {
rows: newest.end.row - newest.start.row,
},
Mode::VisualBlock => RecordedSelection::VisualBlock {
ModalMode::VisualBlock => RecordedSelection::VisualBlock {
rows: newest.end.row.abs_diff(oldest.start.row),
cols: newest.end.column.abs_diff(oldest.start.column),
},
@ -1480,9 +1485,9 @@ impl Vim {
_window: &mut Window,
_: &mut Context<Self>,
) {
let mode = if (self.mode == Mode::Insert
|| self.mode == Mode::Replace
|| self.mode == Mode::Normal)
let mode = if (self.mode == ModalMode::Insert
|| self.mode == ModalMode::Replace
|| self.mode == ModalMode::Normal)
&& self.current_tx.is_none()
{
self.current_tx = Some(transaction_id);
@ -1490,7 +1495,7 @@ impl Vim {
} else {
self.mode
};
if mode == Mode::VisualLine || mode == Mode::VisualBlock {
if mode == ModalMode::VisualLine || mode == ModalMode::VisualBlock {
self.undo_modes.insert(transaction_id, mode);
}
}
@ -1502,12 +1507,12 @@ impl Vim {
cx: &mut Context<Self>,
) {
match self.mode {
Mode::VisualLine | Mode::VisualBlock | Mode::Visual => {
ModalMode::VisualLine | ModalMode::VisualBlock | ModalMode::Visual => {
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| {
match original_mode {
Some(Mode::VisualLine) => {
Some(ModalMode::VisualLine) => {
s.move_with(|map, selection| {
selection.collapse_to(
map.prev_line_boundary(selection.start.to_point(map)).1,
@ -1515,7 +1520,7 @@ impl Vim {
)
});
}
Some(Mode::VisualBlock) => {
Some(ModalMode::VisualBlock) => {
let mut first = s.first_anchor();
first.collapse_to(first.start, first.goal);
s.select_anchors(vec![first]);
@ -1531,9 +1536,9 @@ impl Vim {
}
});
});
self.switch_mode(Mode::Normal, true, window, cx)
self.switch_mode(ModalMode::Normal, true, window, cx)
}
Mode::Normal => {
ModalMode::Normal => {
self.update_editor(cx, |_, editor, cx| {
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
s.move_with(|map, selection| {
@ -1543,7 +1548,7 @@ impl Vim {
})
});
}
Mode::Insert | Mode::Replace | Mode::HelixNormal => {}
ModalMode::Insert | ModalMode::Replace | ModalMode::HelixNormal => {}
}
}
@ -1556,7 +1561,7 @@ impl Vim {
let newest = editor.read(cx).selections.newest_anchor().clone();
let is_multicursor = editor.read(cx).selections.count() > 1;
if self.mode == Mode::Insert && self.current_tx.is_some() {
if self.mode == ModalMode::Insert && self.current_tx.is_some() {
if self.current_anchor.is_none() {
self.current_anchor = Some(newest);
} else if self.current_anchor.as_ref().unwrap() != &newest
@ -1566,17 +1571,22 @@ impl Vim {
editor.group_until_transaction(tx_id, cx)
});
}
} else if self.mode == Mode::Normal && newest.start != newest.end {
} else if self.mode == ModalMode::Normal && newest.start != newest.end {
if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
self.switch_mode(Mode::VisualBlock, false, window, cx);
self.switch_mode(ModalMode::VisualBlock, false, window, cx);
} else {
self.switch_mode(Mode::Visual, false, window, cx)
self.switch_mode(ModalMode::Visual, false, window, cx)
}
} else if newest.start == newest.end
&& !is_multicursor
&& [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&self.mode)
&& [
ModalMode::Visual,
ModalMode::VisualLine,
ModalMode::VisualBlock,
]
.contains(&self.mode)
{
self.switch_mode(Mode::Normal, true, window, cx);
self.switch_mode(ModalMode::Normal, true, window, cx);
}
}
@ -1649,11 +1659,11 @@ impl Vim {
}
}
Some(Operator::Replace) => match self.mode {
Mode::Normal => self.normal_replace(text, window, cx),
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
ModalMode::Normal => self.normal_replace(text, window, cx),
ModalMode::Visual | ModalMode::VisualLine | ModalMode::VisualBlock => {
self.visual_replace(text, window, cx)
}
Mode::HelixNormal => self.helix_replace(&text, window, cx),
ModalMode::HelixNormal => self.helix_replace(&text, window, cx),
_ => self.clear_operator(window, cx),
},
Some(Operator::Digraph { first_char }) => {
@ -1671,20 +1681,20 @@ impl Vim {
self.handle_literal_input(prefix.unwrap_or_default(), &text, window, cx)
}
Some(Operator::AddSurrounds { target }) => match self.mode {
Mode::Normal => {
ModalMode::Normal => {
if let Some(target) = target {
self.add_surrounds(text, target, window, cx);
self.clear_operator(window, cx);
}
}
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
ModalMode::Visual | ModalMode::VisualLine | ModalMode::VisualBlock => {
self.add_surrounds(text, SurroundsType::Selection, window, cx);
self.clear_operator(window, cx);
}
_ => self.clear_operator(window, cx),
},
Some(Operator::ChangeSurrounds { target }) => match self.mode {
Mode::Normal => {
ModalMode::Normal => {
if let Some(target) = target {
self.change_surrounds(text, target, window, cx);
self.clear_operator(window, cx);
@ -1693,7 +1703,7 @@ impl Vim {
_ => self.clear_operator(window, cx),
},
Some(Operator::DeleteSurrounds) => match self.mode {
Mode::Normal => {
ModalMode::Normal => {
self.delete_surrounds(text, window, cx);
self.clear_operator(window, cx);
}
@ -1707,7 +1717,7 @@ impl Vim {
self.replay_register(text.chars().next().unwrap(), window, cx)
}
Some(Operator::Register) => match self.mode {
Mode::Insert => {
ModalMode::Insert => {
self.update_editor(cx, |_, editor, cx| {
if let Some(register) = Vim::update_globals(cx, |globals, cx| {
globals.read_register(text.chars().next(), Some(editor), cx)
@ -1729,11 +1739,11 @@ impl Vim {
},
Some(Operator::Jump { line }) => self.jump(text, line, true, window, cx),
_ => {
if self.mode == Mode::Replace {
if self.mode == ModalMode::Replace {
self.multi_replace(text, window, cx)
}
if self.mode == Mode::Normal {
if self.mode == ModalMode::Normal {
self.update_editor(cx, |_, editor, cx| {
editor.accept_edit_prediction(
&editor::actions::AcceptEditPrediction {},
@ -1753,9 +1763,9 @@ impl Vim {
editor.set_collapse_matches(true);
editor.set_input_enabled(vim.editor_input_enabled());
editor.set_autoindent(vim.should_autoindent());
editor.selections.line_mode = matches!(vim.mode, Mode::VisualLine);
editor.selections.line_mode = matches!(vim.mode, ModalMode::VisualLine);
let hide_edit_predictions = !matches!(vim.mode, Mode::Insert | Mode::Replace);
let hide_edit_predictions = !matches!(vim.mode, ModalMode::Insert | ModalMode::Replace);
editor.set_edit_predictions_hidden_for_vim_mode(hide_edit_predictions, window, cx);
});
cx.notify()
@ -1797,7 +1807,6 @@ struct CursorShapeSettings {
#[derive(Deserialize)]
struct VimSettings {
pub default_mode: Mode,
pub toggle_relative_line_numbers: bool,
pub use_system_clipboard: UseSystemClipboard,
pub use_smartcase_find: bool,
@ -1808,7 +1817,6 @@ struct VimSettings {
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
struct VimSettingsContent {
pub default_mode: Option<ModeContent>,
pub toggle_relative_line_numbers: Option<bool>,
pub use_system_clipboard: Option<UseSystemClipboard>,
pub use_smartcase_find: Option<bool>,
@ -1817,33 +1825,6 @@ struct VimSettingsContent {
pub cursor_shape: Option<CursorShapeSettings>,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ModeContent {
#[default]
Normal,
Insert,
Replace,
Visual,
VisualLine,
VisualBlock,
HelixNormal,
}
impl From<ModeContent> for Mode {
fn from(mode: ModeContent) -> Self {
match mode {
ModeContent::Normal => Self::Normal,
ModeContent::Insert => Self::Insert,
ModeContent::Replace => Self::Replace,
ModeContent::Visual => Self::Visual,
ModeContent::VisualLine => Self::VisualLine,
ModeContent::VisualBlock => Self::VisualBlock,
ModeContent::HelixNormal => Self::HelixNormal,
}
}
}
impl Settings for VimSettings {
const KEY: Option<&'static str> = Some("vim");
@ -1853,10 +1834,6 @@ impl Settings for VimSettings {
let settings: VimSettingsContent = sources.json_merge()?;
Ok(Self {
default_mode: settings
.default_mode
.ok_or_else(Self::missing_default)?
.into(),
toggle_relative_line_numbers: settings
.toggle_relative_line_numbers
.ok_or_else(Self::missing_default)?,

View file

@ -7,8 +7,10 @@
use anyhow::Result;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use settings::{Settings, SettingsSources};
use std::fmt::Display;
/// Initializes the `vim_mode_setting` crate.
pub fn init(cx: &mut App) {
@ -20,13 +22,93 @@ pub fn init(cx: &mut App) {
/// Default: `EditMode::Default`
pub struct EditorModeSetting(pub EditorMode);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, JsonSchema, Default)]
pub enum EditorMode {
Vim,
VimInsert,
Helix,
#[default]
Default,
Vim(ModalMode),
Helix(ModalMode),
}
impl<'de> Deserialize<'de> for EditorMode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.as_str() {
"default" => Ok(EditorMode::Default),
"vim" => Ok(EditorMode::Vim(ModalMode::Normal)),
"vim_normal" => Ok(EditorMode::Vim(ModalMode::Normal)),
"vim_insert" => Ok(EditorMode::Vim(ModalMode::Insert)),
"vim_replace" => Ok(EditorMode::Vim(ModalMode::Replace)),
"vim_visual" => Ok(EditorMode::Vim(ModalMode::Visual)),
"vim_visual_line" => Ok(EditorMode::Vim(ModalMode::VisualLine)),
"vim_visual_block" => Ok(EditorMode::Vim(ModalMode::VisualBlock)),
"helix_experimental" => Ok(EditorMode::Helix(ModalMode::HelixNormal)),
_ => Err(D::Error::custom(format!("Unknown editor mode: {}", s))),
}
}
}
impl Serialize for EditorMode {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = match self {
EditorMode::Default => "default",
EditorMode::Vim(ModalMode::Normal) => "vim",
EditorMode::Vim(ModalMode::Insert) => "vim_insert",
EditorMode::Vim(ModalMode::Replace) => "vim_replace",
EditorMode::Vim(ModalMode::Visual) => "vim_visual",
EditorMode::Vim(ModalMode::VisualLine) => "vim_visual_line",
EditorMode::Vim(ModalMode::VisualBlock) => "vim_visual_block",
EditorMode::Helix(ModalMode::Normal) => "helix_experimental",
_ => return Err(serde::ser::Error::custom("unsupported editor mode variant")),
};
serializer.serialize_str(s)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum ModalMode {
Normal,
Insert,
Replace,
Visual,
VisualLine,
VisualBlock,
HelixNormal,
}
impl Display for ModalMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ModalMode::Normal => write!(f, "NORMAL"),
ModalMode::Insert => write!(f, "INSERT"),
ModalMode::Replace => write!(f, "REPLACE"),
ModalMode::Visual => write!(f, "VISUAL"),
ModalMode::VisualLine => write!(f, "VISUAL LINE"),
ModalMode::VisualBlock => write!(f, "VISUAL BLOCK"),
ModalMode::HelixNormal => write!(f, "HELIX NORMAL"),
}
}
}
impl ModalMode {
pub fn is_visual(&self) -> bool {
match self {
Self::Visual | Self::VisualLine | Self::VisualBlock => true,
Self::Normal | Self::Insert | Self::Replace | Self::HelixNormal => false,
}
}
}
impl Default for ModalMode {
fn default() -> Self {
Self::Normal
}
}
impl Settings for EditorModeSetting {
@ -49,3 +131,9 @@ impl Settings for EditorModeSetting {
// TODO: could possibly check if any of the `vim.<foo>` keys are set?
}
}
impl EditorMode {
pub fn is_modal(&self) -> bool {
matches!(self, EditorMode::Vim(_) | EditorMode::Helix(_))
}
}