Skip to content

Commit

Permalink
Add a setting to change Vim mode indicator location on the status bar
Browse files Browse the repository at this point in the history
  • Loading branch information
helgemahrt committed Dec 18, 2024
1 parent 56d20fc commit 84682ed
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 50 deletions.
5 changes: 4 additions & 1 deletion assets/settings/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -1168,7 +1168,10 @@
"use_multiline_find": false,
"use_smartcase_find": false,
"highlight_on_yank_duration": 200,
"custom_digraphs": {}
"custom_digraphs": {},
"mode_indicator": {
"location": "right"
}
},
// The server to connect to. If the environment variable
// ZED_SERVER_URL is set, it will override this setting.
Expand Down
3 changes: 2 additions & 1 deletion crates/vim/src/normal/paste.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,11 @@ impl Vim {

#[cfg(test)]
mod test {
use crate::vim_settings::UseSystemClipboard;
use crate::{
state::Mode,
test::{NeovimBackedTestContext, VimTestContext},
UseSystemClipboard, VimSettings,
VimSettings,
};
use gpui::ClipboardItem;
use indoc::indoc;
Expand Down
3 changes: 2 additions & 1 deletion crates/vim/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use std::{fmt::Display, ops::Range, sync::Arc};
use crate::command::command_interceptor;
use crate::normal::repeat::Replayer;
use crate::surrounds::SurroundsType;
use crate::vim_settings::UseSystemClipboard;
use crate::{motion::Motion, object::Object};
use crate::{UseSystemClipboard, Vim, VimSettings};
use crate::{Vim, VimSettings};
use collections::HashMap;
use command_palette_hooks::{CommandPaletteFilter, CommandPaletteInterceptor};
use editor::{Anchor, ClipboardSelection, Editor};
Expand Down
49 changes: 3 additions & 46 deletions crates/vim/src/vim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ mod replace;
mod rewrap;
mod state;
mod surrounds;
pub mod vim_settings;
mod visual;

use anyhow::Result;
use collections::HashMap;
use editor::{
movement::{self, FindRange},
Expand All @@ -34,16 +34,15 @@ use language::{CursorShape, Point, Selection, SelectionGoal, TransactionId};
pub use mode_indicator::ModeIndicator;
use motion::Motion;
use normal::search::SearchSubmit;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_derive::Serialize;
use settings::{update_settings_file, Settings, SettingsSources, SettingsStore};
use settings::{update_settings_file, Settings, SettingsStore};
use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals};
use std::{mem, ops::Range, sync::Arc};
use surrounds::SurroundsType;
use theme::ThemeSettings;
use ui::{px, IntoElement, VisualContext};
use vim_mode_setting::VimModeSetting;
use vim_settings::VimSettings;
use workspace::{self, Pane, ResizeIntent, Workspace};

use crate::state::ReplayableAction;
Expand Down Expand Up @@ -1204,45 +1203,3 @@ impl Vim {
cx.notify()
}
}

/// Controls when to use system clipboard.
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum UseSystemClipboard {
/// Don't use system clipboard.
Never,
/// Use system clipboard.
Always,
/// Use system clipboard for yank operations.
OnYank,
}

#[derive(Deserialize)]
struct VimSettings {
pub toggle_relative_line_numbers: bool,
pub use_system_clipboard: UseSystemClipboard,
pub use_multiline_find: bool,
pub use_smartcase_find: bool,
pub custom_digraphs: HashMap<String, Arc<str>>,
pub highlight_on_yank_duration: u64,
}

#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
struct VimSettingsContent {
pub toggle_relative_line_numbers: Option<bool>,
pub use_system_clipboard: Option<UseSystemClipboard>,
pub use_multiline_find: Option<bool>,
pub use_smartcase_find: Option<bool>,
pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
pub highlight_on_yank_duration: Option<u64>,
}

impl Settings for VimSettings {
const KEY: Option<&'static str> = Some("vim");

type FileContent = VimSettingsContent;

fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
sources.json_merge()
}
}
84 changes: 84 additions & 0 deletions crates/vim/src/vim_settings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use std::sync::Arc;

use anyhow::Result;
use collections::HashMap;
use gpui::AppContext;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_derive::Serialize;
use settings::{Settings, SettingsSources};

/// Controls when to use system clipboard.
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum UseSystemClipboard {
/// Don't use system clipboard.
Never,
/// Use system clipboard.
Always,
/// Use system clipboard for yank operations.
OnYank,
}

#[derive(Deserialize)]
pub struct VimSettings {
pub toggle_relative_line_numbers: bool,
pub use_system_clipboard: UseSystemClipboard,
pub use_multiline_find: bool,
pub use_smartcase_find: bool,
pub custom_digraphs: HashMap<String, Arc<str>>,
pub highlight_on_yank_duration: u64,
pub mode_indicator: ModeIndicatorSettings,
}

#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct VimSettingsContent {
pub toggle_relative_line_numbers: Option<bool>,
pub use_system_clipboard: Option<UseSystemClipboard>,
pub use_multiline_find: Option<bool>,
pub use_smartcase_find: Option<bool>,
pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
pub highlight_on_yank_duration: Option<u64>,
pub mode_indicator: Option<ModeIndicatorSettings>,
}

#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ModeIndicatorLocation {
/// Show the vim mode indicator on the left side of the status bar.
Left,
/// Show the vim mode indicator on the right side of the status bar.
#[default]
Right,
}

#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct ModeIndicatorSettings {
pub location: Option<ModeIndicatorLocation>,
}

#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct ModeIndicatorSettingsContent {
/// Whether to show the vim mode indicator to the left or to the right in the status bar.
pub location: Option<Option<ModeIndicatorLocation>>,
}

impl Settings for VimSettings {
const KEY: Option<&'static str> = Some("vim");

type FileContent = VimSettingsContent;

fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
sources.json_merge()
}
}

impl Settings for ModeIndicatorSettings {
const KEY: Option<&'static str> = Some("mode_indicator");

type FileContent = ModeIndicatorSettingsContent;

fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
sources.json_merge()
}
}
151 changes: 150 additions & 1 deletion crates/zed/src/zed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ use terminal_view::terminal_panel::{self, TerminalPanel};
use theme::ActiveTheme;
use util::{asset_str, ResultExt};
use uuid::Uuid;
use vim::vim_settings::{ModeIndicatorLocation, VimSettings};
use vim_mode_setting::VimModeSetting;
use welcome::{BaseKeymap, MultibufferHint};
use workspace::notifications::NotificationId;
Expand Down Expand Up @@ -185,10 +186,23 @@ pub fn initialize_workspace(
status_bar.add_right_item(inline_completion_button, cx);
status_bar.add_right_item(active_buffer_language, cx);
status_bar.add_right_item(active_toolchain_language, cx);
status_bar.add_right_item(vim_mode_indicator, cx);
match VimSettings::get_global(cx).mode_indicator.location {
Some(ModeIndicatorLocation::Left) => status_bar.add_left_item(vim_mode_indicator, cx),
Some(ModeIndicatorLocation::Right) | None => status_bar.add_right_item(vim_mode_indicator, cx),
}
status_bar.add_right_item(cursor_position, cx);
});

let mut current_location = VimSettings::get_global(cx).mode_indicator.location;
cx.observe_global::<SettingsStore>(move |workspace, cx| {
let new_location = VimSettings::get_global(cx).mode_indicator.location;
if current_location != new_location {
current_location = new_location;
update_vim_mode_indicator_position(workspace, new_location, cx);
}
})
.detach();

auto_update_ui::notify_of_any_new_update(cx);

let handle = cx.view().downgrade();
Expand All @@ -212,6 +226,33 @@ pub fn initialize_workspace(
feature_gate_zed_pro_actions(cx);
}

fn update_vim_mode_indicator_position(
workspace: &mut Workspace,
new_location: Option<ModeIndicatorLocation>,
cx: &mut ViewContext<Workspace>,
) {
if let Some(mode_indicator_position) = workspace
.status_bar()
.read(cx)
.position_of_item::<vim::ModeIndicator>()
{
workspace.status_bar().update(cx, |status_bar, cx| {
if let Some(vim_mode_indicator) = status_bar.item_of_type::<vim::ModeIndicator>() {
status_bar.remove_item_at(mode_indicator_position, cx);
match new_location {
Some(ModeIndicatorLocation::Left) => {
status_bar.add_left_item(vim_mode_indicator, cx)
}
Some(ModeIndicatorLocation::Right) | None => {
status_bar.add_right_item(vim_mode_indicator, cx)
}
}
}
});
cx.notify();
}
}

fn feature_gate_zed_pro_actions(cx: &mut AppContext) {
let zed_pro_actions = [TypeId::of::<OpenAccountSettings>()];

Expand Down Expand Up @@ -3504,6 +3545,113 @@ mod tests {
cx.run_until_parked();
}

#[gpui::test]
async fn test_vim_mode_indicator_location_setting(cx: &mut TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
"/root",
json!({
"a": {
"file": "contents",
},
}),
)
.await;

let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
project.update(cx, |project, _cx| {
project.languages().add(markdown_language())
});
let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));

let entries = cx.update(|cx| workspace.root(cx).unwrap().file_project_paths(cx));
let file = entries[0].clone();

let pane = workspace
.read_with(cx, |workspace, _| workspace.active_pane().clone())
.unwrap();

workspace
.update(cx, |w, cx| w.open_path(file.clone(), None, true, cx))
.unwrap()
.await
.unwrap();

workspace
.update(cx, |_, cx| {
pane.update(cx, |pane, cx| {
let editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
assert_eq!(editor.project_path(cx), Some(file.clone()));
let buffer = editor.update(cx, |editor, cx| {
editor.insert("dirt", cx);
editor.buffer().downgrade()
});
(editor.downgrade(), buffer)
})
})
.unwrap();

let right_position = cx.read(|cx| {
workspace
.read(cx)
.unwrap()
.status_bar()
.read(cx)
.position_of_item::<vim::ModeIndicator>()
});
assert!(right_position.is_some());
println!("right_position: {}", right_position.unwrap());

// move the indicator to the left
workspace
.update(cx, |workspace, cx| {
update_vim_mode_indicator_position(
workspace,
Some(ModeIndicatorLocation::Left),
cx,
);
})
.unwrap();

let left_position = cx.read(|cx| {
workspace
.read(cx)
.unwrap()
.status_bar()
.read(cx)
.position_of_item::<vim::ModeIndicator>()
});
assert!(left_position.is_some());
println!("left_position: {}", left_position.unwrap());

assert!(left_position.unwrap() < right_position.unwrap());

// move the indicator back to the right
workspace
.update(cx, |workspace, cx| {
update_vim_mode_indicator_position(
workspace,
Some(ModeIndicatorLocation::Right),
cx,
);
})
.unwrap();

let back_to_right_position = cx.read(|cx| {
workspace
.read(cx)
.unwrap()
.status_bar()
.read(cx)
.position_of_item::<vim::ModeIndicator>()
});
assert!(back_to_right_position.is_some());

assert!(back_to_right_position.unwrap() > left_position.unwrap());
}
pub(crate) fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
init_test_with_state(cx, cx.update(AppState::test))
}
Expand All @@ -3528,6 +3676,7 @@ mod tests {
notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
workspace::init(app_state.clone(), cx);
Project::init_settings(cx);
VimSettings::register(cx);
release_channel::init(SemanticVersion::default(), cx);
command_palette::init(cx);
language::init(cx);
Expand Down

0 comments on commit 84682ed

Please sign in to comment.