global editor mode setting

Co-authored-by: Oleksiy Syvokon <oleksiy.syvokon@gmail.com>
This commit is contained in:
Smit Barmase 2025-08-21 21:28:04 +05:30
parent 27a26d53b1
commit 0d5becfadf
No known key found for this signature in database
11 changed files with 93 additions and 78 deletions

2
Cargo.lock generated
View file

@ -17993,6 +17993,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"gpui", "gpui",
"schemars",
"serde",
"settings", "settings",
"workspace-hack", "workspace-hack",
] ]

View file

@ -27,7 +27,7 @@ use ui::{
CheckboxWithLabel, Chip, ContextMenu, PopoverMenu, ScrollableHandle, Scrollbar, ScrollbarState, CheckboxWithLabel, Chip, ContextMenu, PopoverMenu, ScrollableHandle, Scrollbar, ScrollbarState,
ToggleButton, Tooltip, prelude::*, ToggleButton, Tooltip, prelude::*,
}; };
use vim_mode_setting::VimModeSetting; use vim_mode_setting::{EditorMode, EditorModeSetting};
use workspace::{ use workspace::{
Workspace, WorkspaceId, Workspace, WorkspaceId,
item::{Item, ItemEvent}, item::{Item, ItemEvent},
@ -1335,17 +1335,26 @@ impl ExtensionsPage {
.child(CheckboxWithLabel::new( .child(CheckboxWithLabel::new(
"enable-vim", "enable-vim",
Label::new("Enable vim mode"), Label::new("Enable vim mode"),
if VimModeSetting::get_global(cx).0 { {
ui::ToggleState::Selected let editor_mode = EditorModeSetting::get_global(cx).0;
} else { if matches!(editor_mode, EditorMode::Vim | EditorMode::Helix) {
ui::ToggleState::Unselected ui::ToggleState::Selected
} else {
ui::ToggleState::Unselected
}
}, },
cx.listener(move |this, selection, _, cx| { cx.listener(move |this, selection, _, cx| {
telemetry::event!("Vim Mode Toggled", source = "Feature Upsell"); telemetry::event!("Vim Mode Toggled", source = "Feature Upsell");
this.update_settings::<VimModeSetting>( this.update_settings::<EditorModeSetting>(
selection, selection,
cx, cx,
|setting, value| *setting = Some(value), |setting, value| {
*setting = Some(if value {
EditorMode::Vim
} else {
EditorMode::Default
});
},
); );
}), }),
)), )),

View file

@ -12,7 +12,7 @@ use ui::{
ParentElement as _, StatefulInteractiveElement, SwitchField, ToggleButtonGroup, ParentElement as _, StatefulInteractiveElement, SwitchField, ToggleButtonGroup,
ToggleButtonSimple, ToggleButtonWithIcon, prelude::*, rems_from_px, ToggleButtonSimple, ToggleButtonWithIcon, prelude::*, rems_from_px,
}; };
use vim_mode_setting::VimModeSetting; use vim_mode_setting::{EditorMode, EditorModeSetting};
use crate::theme_preview::{ThemePreviewStyle, ThemePreviewTile}; use crate::theme_preview::{ThemePreviewStyle, ThemePreviewTile};
@ -331,11 +331,13 @@ fn render_base_keymap_section(tab_index: &mut isize, cx: &mut App) -> impl IntoE
} }
fn render_vim_mode_switch(tab_index: &mut isize, cx: &mut App) -> impl IntoElement { fn render_vim_mode_switch(tab_index: &mut isize, cx: &mut App) -> impl IntoElement {
let toggle_state = if VimModeSetting::get_global(cx).0 { let editor_mode = EditorModeSetting::get_global(cx).0;
let toggle_state = if matches!(editor_mode, EditorMode::Vim | EditorMode::Helix) {
ui::ToggleState::Selected ui::ToggleState::Selected
} else { } else {
ui::ToggleState::Unselected ui::ToggleState::Unselected
}; };
SwitchField::new( SwitchField::new(
"onboarding-vim-mode", "onboarding-vim-mode",
"Vim Mode", "Vim Mode",
@ -344,10 +346,10 @@ fn render_vim_mode_switch(tab_index: &mut isize, cx: &mut App) -> impl IntoEleme
{ {
let fs = <dyn Fs>::global(cx); let fs = <dyn Fs>::global(cx);
move |&selection, _, cx| { move |&selection, _, cx| {
update_settings_file::<VimModeSetting>(fs.clone(), cx, move |setting, _| { update_settings_file::<EditorModeSetting>(fs.clone(), cx, move |setting, _| {
*setting = match selection { *setting = match selection {
ToggleState::Selected => Some(true), ToggleState::Selected => Some(EditorMode::Vim),
ToggleState::Unselected => Some(false), ToggleState::Unselected => Some(EditorMode::Default),
ToggleState::Indeterminate => None, ToggleState::Indeterminate => None,
} }
}); });

View file

@ -4,7 +4,7 @@ use gpui::{Action, Context, Window, actions};
use language::SelectionGoal; use language::SelectionGoal;
use settings::Settings; use settings::Settings;
use text::Point; use text::Point;
use vim_mode_setting::HelixModeSetting; use vim_mode_setting::{EditorMode, EditorModeSetting};
use workspace::searchable::Direction; use workspace::searchable::Direction;
actions!( actions!(
@ -53,7 +53,7 @@ impl Vim {
self.update_editor(cx, |_, editor, cx| { self.update_editor(cx, |_, editor, cx| {
editor.dismiss_menus_and_popups(false, window, cx); editor.dismiss_menus_and_popups(false, window, cx);
if !HelixModeSetting::get_global(cx).0 { if EditorModeSetting::get_global(cx).0 != EditorMode::Helix {
editor.change_selections(Default::default(), window, cx, |s| { editor.change_selections(Default::default(), window, cx, |s| {
s.move_cursors_with(|map, mut cursor, _| { s.move_cursors_with(|map, mut cursor, _| {
*cursor.column_mut() = cursor.column().saturating_sub(1); *cursor.column_mut() = cursor.column().saturating_sub(1);
@ -63,7 +63,7 @@ impl Vim {
} }
}); });
if HelixModeSetting::get_global(cx).0 { if EditorModeSetting::get_global(cx).0 == EditorMode::Helix {
self.switch_mode(Mode::HelixNormal, false, window, cx); self.switch_mode(Mode::HelixNormal, false, window, cx);
} else { } else {
self.switch_mode(Mode::Normal, false, window, cx); self.switch_mode(Mode::Normal, false, window, cx);

View file

@ -5,7 +5,7 @@ use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use settings::Settings; use settings::Settings;
use std::cmp; use std::cmp;
use vim_mode_setting::HelixModeSetting; use vim_mode_setting::{EditorMode, EditorModeSetting};
use crate::{ use crate::{
Vim, Vim,
@ -220,7 +220,7 @@ impl Vim {
}); });
}); });
if HelixModeSetting::get_global(cx).0 { if EditorModeSetting::get_global(cx).0 == EditorMode::Helix {
self.switch_mode(Mode::HelixNormal, true, window, cx); self.switch_mode(Mode::HelixNormal, true, window, cx);
} else { } else {
self.switch_mode(Mode::Normal, true, window, cx); self.switch_mode(Mode::Normal, true, window, cx);

View file

@ -64,7 +64,13 @@ impl VimTestContext {
pub fn init_keybindings(enabled: bool, cx: &mut App) { pub fn init_keybindings(enabled: bool, cx: &mut App) {
SettingsStore::update_global(cx, |store, cx| { SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled)); store.update_user_settings::<EditorModeSetting>(cx, |s| {
*s = Some(if enabled {
EditorMode::Vim
} else {
EditorMode::Default
})
});
}); });
let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure( let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
"keymaps/default-macos.json", "keymaps/default-macos.json",
@ -130,7 +136,7 @@ impl VimTestContext {
pub fn enable_vim(&mut self) { pub fn enable_vim(&mut self) {
self.cx.update(|_, cx| { self.cx.update(|_, cx| {
SettingsStore::update_global(cx, |store, cx| { SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(true)); store.update_user_settings::<EditorModeSetting>(cx, |s| *s = Some(EditorMode::Vim));
}); });
}) })
} }
@ -138,7 +144,7 @@ impl VimTestContext {
pub fn disable_vim(&mut self) { pub fn disable_vim(&mut self) {
self.cx.update(|_, cx| { self.cx.update(|_, cx| {
SettingsStore::update_global(cx, |store, cx| { SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(false)); store.update_user_settings::<EditorModeSetting>(cx, |s| *s = Some(EditorMode::Vim));
}); });
}) })
} }
@ -146,8 +152,8 @@ impl VimTestContext {
pub fn enable_helix(&mut self) { pub fn enable_helix(&mut self) {
self.cx.update(|_, cx| { self.cx.update(|_, cx| {
SettingsStore::update_global(cx, |store, cx| { SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<vim_mode_setting::HelixModeSetting>(cx, |s| { store.update_user_settings::<vim_mode_setting::EditorModeSetting>(cx, |s| {
*s = Some(true) *s = Some(EditorMode::Helix)
}); });
}); });
}) })

View file

@ -45,8 +45,7 @@ use std::{mem, ops::Range, sync::Arc};
use surrounds::SurroundsType; use surrounds::SurroundsType;
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{IntoElement, SharedString, px}; use ui::{IntoElement, SharedString, px};
use vim_mode_setting::HelixModeSetting; use vim_mode_setting::{EditorMode, EditorModeSetting};
use vim_mode_setting::VimModeSetting;
use workspace::{self, Pane, Workspace}; use workspace::{self, Pane, Workspace};
use crate::state::ReplayableAction; use crate::state::ReplayableAction;
@ -246,8 +245,12 @@ pub fn init(cx: &mut App) {
workspace.register_action(|workspace, _: &ToggleVimMode, _, cx| { workspace.register_action(|workspace, _: &ToggleVimMode, _, cx| {
let fs = workspace.app_state().fs.clone(); let fs = workspace.app_state().fs.clone();
let currently_enabled = Vim::enabled(cx); let currently_enabled = Vim::enabled(cx);
update_settings_file::<VimModeSetting>(fs, cx, move |setting, _| { update_settings_file::<EditorModeSetting>(fs, cx, move |setting, _| {
*setting = Some(!currently_enabled) *setting = Some(if currently_enabled {
EditorMode::Default
} else {
EditorMode::Vim
});
}) })
}); });
@ -405,7 +408,9 @@ impl Vim {
let editor = cx.entity(); let editor = cx.entity();
let mut initial_mode = VimSettings::get_global(cx).default_mode; let mut initial_mode = VimSettings::get_global(cx).default_mode;
if initial_mode == Mode::Normal && HelixModeSetting::get_global(cx).0 { if initial_mode == Mode::Normal
&& matches!(EditorModeSetting::get_global(cx).0, EditorMode::Helix)
{
initial_mode = Mode::HelixNormal; initial_mode = Mode::HelixNormal;
} }
@ -496,7 +501,7 @@ impl Vim {
vim.update(cx, |_, cx| { vim.update(cx, |_, cx| {
Vim::action(editor, cx, |vim, _: &SwitchToNormalMode, window, cx| { Vim::action(editor, cx, |vim, _: &SwitchToNormalMode, window, cx| {
if HelixModeSetting::get_global(cx).0 { if matches!(EditorModeSetting::get_global(cx).0, EditorMode::Helix) {
vim.switch_mode(Mode::HelixNormal, false, window, cx) vim.switch_mode(Mode::HelixNormal, false, window, cx)
} else { } else {
vim.switch_mode(Mode::Normal, false, window, cx) vim.switch_mode(Mode::Normal, false, window, cx)
@ -819,7 +824,11 @@ impl Vim {
} }
pub fn enabled(cx: &mut App) -> bool { pub fn enabled(cx: &mut App) -> bool {
VimModeSetting::get_global(cx).0 || HelixModeSetting::get_global(cx).0 if EditorModeSetting::get_global(cx).0 == EditorMode::Default {
return false;
}
return true;
// VimModeSetting::get_global(cx).0 || HelixModeSetting::get_global(cx).0
} }
/// Called whenever an keystroke is typed so vim can observe all actions /// Called whenever an keystroke is typed so vim can observe all actions

View file

@ -14,5 +14,7 @@ path = "src/vim_mode_setting.rs"
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
gpui.workspace = true gpui.workspace = true
schemars.workspace = true
settings.workspace = true settings.workspace = true
workspace-hack.workspace = true workspace-hack.workspace = true
serde.workspace = true

View file

@ -6,23 +6,32 @@
use anyhow::Result; use anyhow::Result;
use gpui::App; use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{Settings, SettingsSources};
/// Initializes the `vim_mode_setting` crate. /// Initializes the `vim_mode_setting` crate.
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
VimModeSetting::register(cx); EditorModeSetting::register(cx);
HelixModeSetting::register(cx);
} }
/// Whether or not to enable Vim mode. /// Whether or not to enable Vim mode.
/// ///
/// Default: false /// Default: `EditMode::Default`
pub struct VimModeSetting(pub bool); pub struct EditorModeSetting(pub EditorMode);
impl Settings for VimModeSetting { #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
const KEY: Option<&'static str> = Some("vim_mode"); pub enum EditorMode {
Vim,
Helix,
#[default]
Default,
}
type FileContent = Option<bool>; impl Settings for EditorModeSetting {
const KEY: Option<&'static str> = Some("editor_mode");
type FileContent = Option<EditorMode>;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> { fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
Ok(Self( Ok(Self(
@ -39,29 +48,3 @@ impl Settings for VimModeSetting {
// TODO: could possibly check if any of the `vim.<foo>` keys are set? // TODO: could possibly check if any of the `vim.<foo>` keys are set?
} }
} }
/// Whether or not to enable Helix mode.
///
/// Default: false
pub struct HelixModeSetting(pub bool);
impl Settings for HelixModeSetting {
const KEY: Option<&'static str> = Some("helix_mode");
type FileContent = Option<bool>;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
Ok(Self(
sources
.user
.or(sources.server)
.copied()
.flatten()
.unwrap_or(sources.default.ok_or_else(Self::missing_default)?),
))
}
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {
// TODO: could possibly check if any of the `helix.<foo>` keys are set?
}
}

View file

@ -70,7 +70,7 @@ use ui::{PopoverMenuHandle, prelude::*};
use util::markdown::MarkdownString; use util::markdown::MarkdownString;
use util::{ResultExt, asset_str}; use util::{ResultExt, asset_str};
use uuid::Uuid; use uuid::Uuid;
use vim_mode_setting::VimModeSetting; use vim_mode_setting::{EditorMode, EditorModeSetting};
use workspace::notifications::{ use workspace::notifications::{
NotificationId, SuppressEvent, dismiss_app_notification, show_app_notification, NotificationId, SuppressEvent, dismiss_app_notification, show_app_notification,
}; };
@ -1287,21 +1287,15 @@ pub fn handle_keymap_file_changes(
let (base_keymap_tx, mut base_keymap_rx) = mpsc::unbounded(); let (base_keymap_tx, mut base_keymap_rx) = mpsc::unbounded();
let (keyboard_layout_tx, mut keyboard_layout_rx) = mpsc::unbounded(); let (keyboard_layout_tx, mut keyboard_layout_rx) = mpsc::unbounded();
let mut old_base_keymap = *BaseKeymap::get_global(cx); let mut old_base_keymap = *BaseKeymap::get_global(cx);
let mut old_vim_enabled = VimModeSetting::get_global(cx).0; let mut old_editor_mode = EditorModeSetting::get_global(cx).0;
let mut old_helix_enabled = vim_mode_setting::HelixModeSetting::get_global(cx).0;
cx.observe_global::<SettingsStore>(move |cx| { cx.observe_global::<SettingsStore>(move |cx| {
let new_base_keymap = *BaseKeymap::get_global(cx); let new_base_keymap = *BaseKeymap::get_global(cx);
let new_vim_enabled = VimModeSetting::get_global(cx).0; let new_editor_mode = EditorModeSetting::get_global(cx).0;
let new_helix_enabled = vim_mode_setting::HelixModeSetting::get_global(cx).0;
if new_base_keymap != old_base_keymap if new_base_keymap != old_base_keymap || new_editor_mode != old_editor_mode {
|| new_vim_enabled != old_vim_enabled
|| new_helix_enabled != old_helix_enabled
{
old_base_keymap = new_base_keymap; old_base_keymap = new_base_keymap;
old_vim_enabled = new_vim_enabled; old_editor_mode = new_editor_mode;
old_helix_enabled = new_helix_enabled;
base_keymap_tx.unbounded_send(()).unwrap(); base_keymap_tx.unbounded_send(()).unwrap();
} }
@ -1499,7 +1493,10 @@ pub fn load_default_keymap(cx: &mut App) {
cx.bind_keys(KeymapFile::load_asset(asset_path, Some(KeybindSource::Base), cx).unwrap()); cx.bind_keys(KeymapFile::load_asset(asset_path, Some(KeybindSource::Base), cx).unwrap());
} }
if VimModeSetting::get_global(cx).0 || vim_mode_setting::HelixModeSetting::get_global(cx).0 { if matches!(
EditorModeSetting::get_global(cx).0,
EditorMode::Vim | EditorMode::Helix
) {
cx.bind_keys( cx.bind_keys(
KeymapFile::load_asset(VIM_KEYMAP_PATH, Some(KeybindSource::Vim), cx).unwrap(), KeymapFile::load_asset(VIM_KEYMAP_PATH, Some(KeybindSource::Vim), cx).unwrap(),
); );

View file

@ -23,7 +23,7 @@ use ui::{
ButtonStyle, ContextMenu, ContextMenuEntry, DocumentationSide, IconButton, IconName, IconSize, ButtonStyle, ContextMenu, ContextMenuEntry, DocumentationSide, IconButton, IconName, IconSize,
PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*,
}; };
use vim_mode_setting::VimModeSetting; use vim_mode_setting::{EditorMode, EditorModeSetting};
use workspace::{ use workspace::{
ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, item::ItemHandle,
}; };
@ -301,7 +301,8 @@ impl Render for QuickActionBar {
let editor_focus_handle = editor.focus_handle(cx); let editor_focus_handle = editor.focus_handle(cx);
let editor = editor.downgrade(); let editor = editor.downgrade();
let editor_settings_dropdown = { let editor_settings_dropdown = {
let vim_mode_enabled = VimModeSetting::get_global(cx).0; let editor_mode = EditorModeSetting::get_global(cx).0;
let vim_mode_enabled = matches!(editor_mode, EditorMode::Vim | EditorMode::Helix);
PopoverMenu::new("editor-settings") PopoverMenu::new("editor-settings")
.trigger_with_tooltip( .trigger_with_tooltip(
@ -576,8 +577,12 @@ impl Render for QuickActionBar {
None, None,
{ {
move |window, cx| { move |window, cx| {
let new_value = !vim_mode_enabled; let new_value = if vim_mode_enabled {
VimModeSetting::override_global(VimModeSetting(new_value), cx); EditorMode::Default
} else {
EditorMode::Vim
};
EditorModeSetting::override_global(EditorModeSetting(new_value), cx);
window.refresh(); window.refresh();
} }
}, },