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

vim: Add a setting to change the Vim mode indicator position on the status bar from right to left #22048

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
}
}
160 changes: 159 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,42 @@ 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) => {
if let (Some(diagnostics_indicator), Some(diagnostics_indicator_position)) =
(status_bar.item_of_type::<diagnostics::items::DiagnosticIndicator>(),
status_bar.position_of_item::<diagnostics::items::DiagnosticIndicator>())
{
status_bar.remove_item_at(diagnostics_indicator_position, cx);
status_bar.add_left_item(vim_mode_indicator, cx);
status_bar.add_left_item(diagnostics_indicator, cx);
} else {
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 +3554,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 +3685,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
Loading