Remove 2 suffix for vim, diagnostics, go_to_line, theme_selector, command_palette, file_finder

Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
Max Brunsfeld 2024-01-03 10:42:49 -08:00
parent 37e6533b28
commit 252694390a
185 changed files with 1933 additions and 29192 deletions

View file

@ -26,28 +26,28 @@ serde_json.workspace = true
collections = { path = "../collections" }
command_palette = { path = "../command_palette" }
editor = { path = "../editor" }
gpui = { path = "../gpui" }
language = { path = "../language" }
search = { path = "../search" }
settings = { path = "../settings" }
workspace = { path = "../workspace" }
theme = { path = "../theme" }
language_selector = { path = "../language_selector"}
editor = { package = "editor2", path = "../editor2" }
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
search = { package = "search2", path = "../search2" }
settings = { package = "settings2", path = "../settings2" }
workspace = { package = "workspace2", path = "../workspace2" }
theme = { package = "theme2", path = "../theme2" }
ui = { package = "ui2", path = "../ui2"}
diagnostics = { path = "../diagnostics" }
zed-actions = { path = "../zed-actions" }
zed_actions = { package = "zed_actions2", path = "../zed_actions2" }
[dev-dependencies]
indoc.workspace = true
parking_lot.workspace = true
futures.workspace = true
editor = { path = "../editor", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
language = { package = "language2", path = "../language2", features = ["test-support"] }
project = { package = "project2", path = "../project2", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
settings = { path = "../settings" }
workspace = { path = "../workspace", features = ["test-support"] }
theme = { path = "../theme", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
settings = { package = "settings2", path = "../settings2" }
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }

View file

@ -1,6 +1,6 @@
use command_palette::CommandInterceptResult;
use editor::{SortLinesCaseInsensitive, SortLinesCaseSensitive};
use gpui::{impl_actions, Action, AppContext};
use gpui::{impl_actions, Action, AppContext, ViewContext};
use serde_derive::Deserialize;
use workspace::{SaveIntent, Workspace};
@ -22,8 +22,8 @@ pub struct GoToLine {
impl_actions!(vim, [GoToLine]);
pub fn init(cx: &mut AppContext) {
cx.add_action(|_: &mut Workspace, action: &GoToLine, cx| {
pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(|_: &mut Workspace, action: &GoToLine, cx| {
Vim::update(cx, |vim, cx| {
vim.switch_mode(Mode::Normal, false, cx);
move_cursor(vim, Motion::StartOfDocument, Some(action.line as usize), cx);
@ -293,14 +293,11 @@ mod test {
use std::path::Path;
use crate::test::{NeovimBackedTestContext, VimTestContext};
use gpui::{executor::Foreground, TestAppContext};
use gpui::TestAppContext;
use indoc::indoc;
#[gpui::test]
async fn test_command_basics(cx: &mut TestAppContext) {
if let Foreground::Deterministic { cx_id: _, executor } = cx.foreground().as_ref() {
executor.run_until_parked();
}
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {"
@ -410,15 +407,14 @@ mod test {
// conflict!
cx.simulate_keystrokes(["i", "@", "escape"]);
cx.simulate_keystrokes([":", "w", "enter"]);
let window = cx.window;
assert!(window.has_pending_prompt(cx.cx));
assert!(cx.has_pending_prompt());
// "Cancel"
window.simulate_prompt_answer(0, cx.cx);
cx.simulate_prompt_answer(0);
assert_eq!(fs.load(&path).await.unwrap(), "oops\n");
assert!(!window.has_pending_prompt(cx.cx));
assert!(!cx.has_pending_prompt());
// force overwrite
cx.simulate_keystrokes([":", "w", "!", "enter"]);
assert!(!window.has_pending_prompt(cx.cx));
assert!(!cx.has_pending_prompt());
assert_eq!(fs.load(&path).await.unwrap(), "@@\n");
}

View file

@ -1,65 +1,67 @@
use crate::{Vim, VimEvent};
use editor::{EditorBlurred, EditorFocused, EditorReleased};
use gpui::AppContext;
use crate::Vim;
use editor::{Editor, EditorEvent};
use gpui::{AppContext, Entity, EntityId, View, ViewContext, WindowContext};
pub fn init(cx: &mut AppContext) {
cx.subscribe_global(focused).detach();
cx.subscribe_global(blurred).detach();
cx.subscribe_global(released).detach();
cx.observe_new_views(|_, cx: &mut ViewContext<Editor>| {
let editor = cx.view().clone();
cx.subscribe(&editor, |_, editor, event: &EditorEvent, cx| match event {
EditorEvent::Focused => cx.window_context().defer(|cx| focused(editor, cx)),
EditorEvent::Blurred => cx.window_context().defer(|cx| blurred(editor, cx)),
_ => {}
})
.detach();
let id = cx.view().entity_id();
cx.on_release(move |_, _, cx| released(id, cx)).detach();
})
.detach();
}
fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) {
if let Some(previously_active_editor) = Vim::read(cx).active_editor.clone() {
previously_active_editor.window().update(cx, |cx| {
Vim::update(cx, |vim, cx| {
vim.update_active_editor(cx, |previously_active_editor, cx| {
vim.unhook_vim_settings(previously_active_editor, cx)
});
fn focused(editor: View<Editor>, cx: &mut WindowContext) {
if Vim::read(cx).active_editor.clone().is_some() {
Vim::update(cx, |vim, cx| {
vim.update_active_editor(cx, |previously_active_editor, cx| {
vim.unhook_vim_settings(previously_active_editor, cx)
});
});
}
editor.window().update(cx, |cx| {
Vim::update(cx, |vim, cx| {
vim.set_active_editor(editor.clone(), cx);
if vim.enabled {
cx.emit_global(VimEvent::ModeChanged {
mode: vim.state().mode,
});
}
});
Vim::update(cx, |vim, cx| {
vim.set_active_editor(editor.clone(), cx);
});
}
fn blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut AppContext) {
editor.window().update(cx, |cx| {
Vim::update(cx, |vim, cx| {
vim.workspace_state.recording = false;
vim.workspace_state.recorded_actions.clear();
if let Some(previous_editor) = vim.active_editor.clone() {
if previous_editor == editor.clone() {
vim.clear_operator(cx);
vim.active_editor = None;
vim.editor_subscription = None;
}
fn blurred(editor: View<Editor>, cx: &mut WindowContext) {
Vim::update(cx, |vim, cx| {
vim.workspace_state.recording = false;
vim.workspace_state.recorded_actions.clear();
if let Some(previous_editor) = vim.active_editor.clone() {
if previous_editor
.upgrade()
.is_some_and(|previous| previous == editor.clone())
{
vim.clear_operator(cx);
vim.active_editor = None;
vim.editor_subscription = None;
}
}
editor.update(cx, |editor, cx| vim.unhook_vim_settings(editor, cx))
});
editor.update(cx, |editor, cx| vim.unhook_vim_settings(editor, cx))
});
}
fn released(EditorReleased(editor): &EditorReleased, cx: &mut AppContext) {
editor.window().update(cx, |cx| {
Vim::update(cx, |vim, _| {
if let Some(previous_editor) = vim.active_editor.clone() {
if previous_editor == editor.clone() {
vim.active_editor = None;
vim.editor_subscription = None;
}
}
vim.editor_states.remove(&editor.id())
});
fn released(entity_id: EntityId, cx: &mut AppContext) {
cx.update_global(|vim: &mut Vim, _| {
if vim
.active_editor
.as_ref()
.is_some_and(|previous| previous.entity_id() == entity_id)
{
vim.active_editor = None;
vim.editor_subscription = None;
}
vim.editor_states.remove(&entity_id)
});
}
@ -67,7 +69,7 @@ fn released(EditorReleased(editor): &EditorReleased, cx: &mut AppContext) {
mod test {
use crate::{test::VimTestContext, Vim};
use editor::Editor;
use gpui::View;
use gpui::{Context, Entity};
use language::Buffer;
// regression test for blur called with a different active editor
@ -75,18 +77,28 @@ mod test {
async fn test_blur_focus(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
let buffer = cx.add_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n"));
let buffer = cx.new_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n"));
let window2 = cx.add_window(|cx| Editor::for_buffer(buffer, None, cx));
let editor2 = cx.read(|cx| window2.root(cx)).unwrap();
let editor2 = cx
.update(|cx| {
window2.update(cx, |_, cx| {
cx.focus_self();
cx.view().clone()
})
})
.unwrap();
cx.update(|cx| {
let vim = Vim::read(cx);
assert_eq!(vim.active_editor.unwrap().id(), editor2.id())
assert_eq!(
vim.active_editor.as_ref().unwrap().entity_id(),
editor2.entity_id(),
)
});
// no panic when blurring an editor in a different window.
cx.update_editor(|editor1, cx| {
editor1.focus_out(cx.handle().into_any(), cx);
editor1.handle_blur(cx);
});
}
}

View file

@ -1,13 +1,13 @@
use crate::{normal::repeat, state::Mode, Vim};
use editor::{scroll::autoscroll::Autoscroll, Bias};
use gpui::{actions, Action, AppContext, ViewContext};
use gpui::{actions, Action, ViewContext};
use language::SelectionGoal;
use workspace::Workspace;
actions!(vim, [NormalBefore]);
pub fn init(cx: &mut AppContext) {
cx.add_action(normal_before);
pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(normal_before);
}
fn normal_before(_: &mut Workspace, action: &NormalBefore, cx: &mut ViewContext<Workspace>) {
@ -38,10 +38,6 @@ fn normal_before(_: &mut Workspace, action: &NormalBefore, cx: &mut ViewContext<
#[cfg(test)]
mod test {
use std::sync::Arc;
use gpui::executor::Deterministic;
use crate::{
state::Mode,
test::{NeovimBackedTestContext, VimTestContext},
@ -60,76 +56,70 @@ mod test {
}
#[gpui::test]
async fn test_insert_with_counts(
deterministic: Arc<Deterministic>,
cx: &mut gpui::TestAppContext,
) {
async fn test_insert_with_counts(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state("ˇhello\n").await;
cx.simulate_shared_keystrokes(["5", "i", "-", "escape"])
.await;
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_shared_state("----ˇ-hello\n").await;
cx.set_shared_state("ˇhello\n").await;
cx.simulate_shared_keystrokes(["5", "a", "-", "escape"])
.await;
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_shared_state("h----ˇ-ello\n").await;
cx.simulate_shared_keystrokes(["4", "shift-i", "-", "escape"])
.await;
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_shared_state("---ˇ-h-----ello\n").await;
cx.simulate_shared_keystrokes(["3", "shift-a", "-", "escape"])
.await;
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_shared_state("----h-----ello--ˇ-\n").await;
cx.set_shared_state("ˇhello\n").await;
cx.simulate_shared_keystrokes(["3", "o", "o", "i", "escape"])
.await;
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_shared_state("hello\noi\noi\noˇi\n").await;
cx.set_shared_state("ˇhello\n").await;
cx.simulate_shared_keystrokes(["3", "shift-o", "o", "i", "escape"])
.await;
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_shared_state("oi\noi\noˇi\nhello\n").await;
}
#[gpui::test]
async fn test_insert_with_repeat(
deterministic: Arc<Deterministic>,
cx: &mut gpui::TestAppContext,
) {
async fn test_insert_with_repeat(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state("ˇhello\n").await;
cx.simulate_shared_keystrokes(["3", "i", "-", "escape"])
.await;
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_shared_state("--ˇ-hello\n").await;
cx.simulate_shared_keystrokes(["."]).await;
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_shared_state("----ˇ--hello\n").await;
cx.simulate_shared_keystrokes(["2", "."]).await;
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_shared_state("-----ˇ---hello\n").await;
cx.set_shared_state("ˇhello\n").await;
cx.simulate_shared_keystrokes(["2", "o", "k", "k", "escape"])
.await;
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_shared_state("hello\nkk\nkˇk\n").await;
cx.simulate_shared_keystrokes(["."]).await;
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_shared_state("hello\nkk\nkk\nkk\nkˇk\n").await;
cx.simulate_shared_keystrokes(["1", "."]).await;
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_shared_state("hello\nkk\nkk\nkk\nkk\nkˇk\n").await;
}
}

View file

@ -1,58 +1,40 @@
use gpui::{
elements::{Empty, Label},
AnyElement, Element, Entity, Subscription, View, ViewContext,
};
use gpui::{div, Element, Render, Subscription, ViewContext};
use settings::SettingsStore;
use workspace::{item::ItemHandle, StatusItemView};
use workspace::{item::ItemHandle, ui::prelude::*, StatusItemView};
use crate::{state::Mode, Vim, VimEvent, VimModeSetting};
use crate::{state::Mode, Vim};
pub struct ModeIndicator {
pub mode: Option<Mode>,
_subscription: Subscription,
_subscriptions: Vec<Subscription>,
}
impl ModeIndicator {
pub fn new(cx: &mut ViewContext<Self>) -> Self {
let handle = cx.handle().downgrade();
let _subscriptions = vec![
cx.observe_global::<Vim>(|this, cx| this.update_mode(cx)),
cx.observe_global::<SettingsStore>(|this, cx| this.update_mode(cx)),
];
let _subscription = cx.subscribe_global::<VimEvent, _>(move |&event, cx| {
if let Some(mode_indicator) = handle.upgrade(cx) {
match event {
VimEvent::ModeChanged { mode } => {
mode_indicator.window().update(cx, |cx| {
mode_indicator.update(cx, move |mode_indicator, cx| {
mode_indicator.set_mode(mode, cx);
})
});
}
}
}
});
cx.observe_global::<SettingsStore, _>(move |mode_indicator, cx| {
if settings::get::<VimModeSetting>(cx).0 {
mode_indicator.mode = cx
.has_global::<Vim>()
.then(|| cx.global::<Vim>().state().mode);
} else {
mode_indicator.mode.take();
}
})
.detach();
let mut this = Self {
mode: None,
_subscriptions,
};
this.update_mode(cx);
this
}
fn update_mode(&mut self, cx: &mut ViewContext<Self>) {
// Vim doesn't exist in some tests
let mode = cx
.has_global::<Vim>()
.then(|| {
let vim = cx.global::<Vim>();
vim.enabled.then(|| vim.state().mode)
})
.flatten();
if !cx.has_global::<Vim>() {
return;
}
Self {
mode,
_subscription,
let vim = Vim::read(cx);
if vim.enabled {
self.mode = Some(vim.state().mode);
} else {
self.mode = None;
}
}
@ -64,22 +46,12 @@ impl ModeIndicator {
}
}
impl Entity for ModeIndicator {
type Event = ();
}
impl View for ModeIndicator {
fn ui_name() -> &'static str {
"ModeIndicatorView"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
impl Render for ModeIndicator {
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
let Some(mode) = self.mode.as_ref() else {
return Empty::new().into_any();
return div().into_any();
};
let theme = &theme::current(cx).workspace.status_bar;
let text = match mode {
Mode::Normal => "-- NORMAL --",
Mode::Insert => "-- INSERT --",
@ -87,10 +59,7 @@ impl View for ModeIndicator {
Mode::VisualLine => "-- VISUAL LINE --",
Mode::VisualBlock => "-- VISUAL BLOCK --",
};
Label::new(text, theme.vim_mode_indicator.text.clone())
.contained()
.with_style(theme.vim_mode_indicator.container)
.into_any()
Label::new(text).size(LabelSize::Small).into_any_element()
}
}

View file

@ -4,7 +4,7 @@ use editor::{
movement::{self, find_boundary, find_preceding_boundary, FindRange, TextLayoutDetails},
Bias, CharKind, DisplayPoint, ToOffset,
};
use gpui::{actions, impl_actions, AppContext, WindowContext};
use gpui::{actions, impl_actions, px, ViewContext, WindowContext};
use language::{Point, Selection, SelectionGoal};
use serde::Deserialize;
use workspace::Workspace;
@ -105,6 +105,21 @@ struct RepeatFind {
backwards: bool,
}
impl_actions!(
vim,
[
RepeatFind,
StartOfLine,
EndOfLine,
FirstNonWhitespace,
Down,
Up,
PreviousWordStart,
NextWordEnd,
NextWordStart
]
);
actions!(
vim,
[
@ -123,25 +138,12 @@ actions!(
GoToColumn,
]
);
impl_actions!(
vim,
[
NextWordStart,
NextWordEnd,
PreviousWordStart,
RepeatFind,
Up,
Down,
FirstNonWhitespace,
EndOfLine,
StartOfLine,
]
);
pub fn init(cx: &mut AppContext) {
cx.add_action(|_: &mut Workspace, _: &Left, cx: _| motion(Motion::Left, cx));
cx.add_action(|_: &mut Workspace, _: &Backspace, cx: _| motion(Motion::Backspace, cx));
cx.add_action(|_: &mut Workspace, action: &Down, cx: _| {
pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(|_: &mut Workspace, _: &Left, cx: _| motion(Motion::Left, cx));
workspace
.register_action(|_: &mut Workspace, _: &Backspace, cx: _| motion(Motion::Backspace, cx));
workspace.register_action(|_: &mut Workspace, action: &Down, cx: _| {
motion(
Motion::Down {
display_lines: action.display_lines,
@ -149,7 +151,7 @@ pub fn init(cx: &mut AppContext) {
cx,
)
});
cx.add_action(|_: &mut Workspace, action: &Up, cx: _| {
workspace.register_action(|_: &mut Workspace, action: &Up, cx: _| {
motion(
Motion::Up {
display_lines: action.display_lines,
@ -157,8 +159,8 @@ pub fn init(cx: &mut AppContext) {
cx,
)
});
cx.add_action(|_: &mut Workspace, _: &Right, cx: _| motion(Motion::Right, cx));
cx.add_action(|_: &mut Workspace, action: &FirstNonWhitespace, cx: _| {
workspace.register_action(|_: &mut Workspace, _: &Right, cx: _| motion(Motion::Right, cx));
workspace.register_action(|_: &mut Workspace, action: &FirstNonWhitespace, cx: _| {
motion(
Motion::FirstNonWhitespace {
display_lines: action.display_lines,
@ -166,7 +168,7 @@ pub fn init(cx: &mut AppContext) {
cx,
)
});
cx.add_action(|_: &mut Workspace, action: &StartOfLine, cx: _| {
workspace.register_action(|_: &mut Workspace, action: &StartOfLine, cx: _| {
motion(
Motion::StartOfLine {
display_lines: action.display_lines,
@ -174,7 +176,7 @@ pub fn init(cx: &mut AppContext) {
cx,
)
});
cx.add_action(|_: &mut Workspace, action: &EndOfLine, cx: _| {
workspace.register_action(|_: &mut Workspace, action: &EndOfLine, cx: _| {
motion(
Motion::EndOfLine {
display_lines: action.display_lines,
@ -182,45 +184,53 @@ pub fn init(cx: &mut AppContext) {
cx,
)
});
cx.add_action(|_: &mut Workspace, _: &CurrentLine, cx: _| motion(Motion::CurrentLine, cx));
cx.add_action(|_: &mut Workspace, _: &StartOfParagraph, cx: _| {
workspace.register_action(|_: &mut Workspace, _: &CurrentLine, cx: _| {
motion(Motion::CurrentLine, cx)
});
workspace.register_action(|_: &mut Workspace, _: &StartOfParagraph, cx: _| {
motion(Motion::StartOfParagraph, cx)
});
cx.add_action(|_: &mut Workspace, _: &EndOfParagraph, cx: _| {
workspace.register_action(|_: &mut Workspace, _: &EndOfParagraph, cx: _| {
motion(Motion::EndOfParagraph, cx)
});
cx.add_action(|_: &mut Workspace, _: &StartOfDocument, cx: _| {
workspace.register_action(|_: &mut Workspace, _: &StartOfDocument, cx: _| {
motion(Motion::StartOfDocument, cx)
});
cx.add_action(|_: &mut Workspace, _: &EndOfDocument, cx: _| motion(Motion::EndOfDocument, cx));
cx.add_action(|_: &mut Workspace, _: &Matching, cx: _| motion(Motion::Matching, cx));
workspace.register_action(|_: &mut Workspace, _: &EndOfDocument, cx: _| {
motion(Motion::EndOfDocument, cx)
});
workspace
.register_action(|_: &mut Workspace, _: &Matching, cx: _| motion(Motion::Matching, cx));
cx.add_action(
workspace.register_action(
|_: &mut Workspace, &NextWordStart { ignore_punctuation }: &NextWordStart, cx: _| {
motion(Motion::NextWordStart { ignore_punctuation }, cx)
},
);
cx.add_action(
workspace.register_action(
|_: &mut Workspace, &NextWordEnd { ignore_punctuation }: &NextWordEnd, cx: _| {
motion(Motion::NextWordEnd { ignore_punctuation }, cx)
},
);
cx.add_action(
workspace.register_action(
|_: &mut Workspace,
&PreviousWordStart { ignore_punctuation }: &PreviousWordStart,
cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) },
);
cx.add_action(|_: &mut Workspace, &NextLineStart, cx: _| motion(Motion::NextLineStart, cx));
cx.add_action(|_: &mut Workspace, &StartOfLineDownward, cx: _| {
workspace.register_action(|_: &mut Workspace, &NextLineStart, cx: _| {
motion(Motion::NextLineStart, cx)
});
workspace.register_action(|_: &mut Workspace, &StartOfLineDownward, cx: _| {
motion(Motion::StartOfLineDownward, cx)
});
cx.add_action(|_: &mut Workspace, &EndOfLineDownward, cx: _| {
workspace.register_action(|_: &mut Workspace, &EndOfLineDownward, cx: _| {
motion(Motion::EndOfLineDownward, cx)
});
cx.add_action(|_: &mut Workspace, &GoToColumn, cx: _| motion(Motion::GoToColumn, cx));
cx.add_action(|_: &mut Workspace, action: &RepeatFind, cx: _| {
workspace
.register_action(|_: &mut Workspace, &GoToColumn, cx: _| motion(Motion::GoToColumn, cx));
workspace.register_action(|_: &mut Workspace, action: &RepeatFind, cx: _| {
repeat_motion(action.backwards, cx)
})
});
}
pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
@ -578,9 +588,9 @@ fn up_down_buffer_rows(
SelectionGoal::HorizontalRange { end, .. } => (select_nth_wrapped_row, end),
SelectionGoal::HorizontalPosition(x) => (select_nth_wrapped_row, x),
_ => {
let x = map.x_for_point(point, text_layout_details);
goal = SelectionGoal::WrappedHorizontalPosition((select_nth_wrapped_row, x));
(select_nth_wrapped_row, x)
let x = map.x_for_display_point(point, text_layout_details);
goal = SelectionGoal::WrappedHorizontalPosition((select_nth_wrapped_row, x.0));
(select_nth_wrapped_row, x.0)
}
};
@ -608,7 +618,7 @@ fn up_down_buffer_rows(
}
let new_col = if i == goal_wrap {
map.column_for_x(begin_folded_line.row(), goal_x, text_layout_details)
map.display_column_for_x(begin_folded_line.row(), px(goal_x), text_layout_details)
} else {
map.line_len(begin_folded_line.row())
};
@ -943,7 +953,6 @@ pub(crate) fn next_line_end(
}
#[cfg(test)]
mod test {
use crate::test::NeovimBackedTestContext;

View file

@ -20,7 +20,7 @@ use crate::{
use collections::HashSet;
use editor::scroll::autoscroll::Autoscroll;
use editor::{Bias, DisplayPoint};
use gpui::{actions, AppContext, ViewContext, WindowContext};
use gpui::{actions, ViewContext, WindowContext};
use language::SelectionGoal;
use log::error;
use workspace::Workspace;
@ -52,38 +52,31 @@ actions!(
]
);
pub fn init(cx: &mut AppContext) {
paste::init(cx);
repeat::init(cx);
scroll::init(cx);
search::init(cx);
substitute::init(cx);
increment::init(cx);
pub(crate) fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
workspace.register_action(insert_after);
workspace.register_action(insert_before);
workspace.register_action(insert_first_non_whitespace);
workspace.register_action(insert_end_of_line);
workspace.register_action(insert_line_above);
workspace.register_action(insert_line_below);
workspace.register_action(change_case);
workspace.register_action(yank_line);
cx.add_action(insert_after);
cx.add_action(insert_before);
cx.add_action(insert_first_non_whitespace);
cx.add_action(insert_end_of_line);
cx.add_action(insert_line_above);
cx.add_action(insert_line_below);
cx.add_action(change_case);
cx.add_action(yank_line);
cx.add_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
workspace.register_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
Vim::update(cx, |vim, cx| {
vim.record_current_action(cx);
let times = vim.take_count(cx);
delete_motion(vim, Motion::Left, times, cx);
})
});
cx.add_action(|_: &mut Workspace, _: &DeleteRight, cx| {
workspace.register_action(|_: &mut Workspace, _: &DeleteRight, cx| {
Vim::update(cx, |vim, cx| {
vim.record_current_action(cx);
let times = vim.take_count(cx);
delete_motion(vim, Motion::Right, times, cx);
})
});
cx.add_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
workspace.register_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
Vim::update(cx, |vim, cx| {
vim.start_recording(cx);
let times = vim.take_count(cx);
@ -97,7 +90,7 @@ pub fn init(cx: &mut AppContext) {
);
})
});
cx.add_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
workspace.register_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
Vim::update(cx, |vim, cx| {
vim.record_current_action(cx);
let times = vim.take_count(cx);
@ -111,7 +104,7 @@ pub fn init(cx: &mut AppContext) {
);
})
});
cx.add_action(|_: &mut Workspace, _: &JoinLines, cx| {
workspace.register_action(|_: &mut Workspace, _: &JoinLines, cx| {
Vim::update(cx, |vim, cx| {
vim.record_current_action(cx);
let mut times = vim.take_count(cx).unwrap_or(1);
@ -129,8 +122,15 @@ pub fn init(cx: &mut AppContext) {
}
})
})
})
})
});
});
paste::register(workspace, cx);
repeat::register(workspace, cx);
scroll::register(workspace, cx);
search::register(workspace, cx);
substitute::register(workspace, cx);
increment::register(workspace, cx);
}
pub fn normal_motion(

View file

@ -1,7 +1,7 @@
use std::ops::Range;
use editor::{scroll::autoscroll::Autoscroll, MultiBufferSnapshot, ToOffset, ToPoint};
use gpui::{impl_actions, AppContext, WindowContext};
use gpui::{impl_actions, ViewContext, WindowContext};
use language::{Bias, Point};
use serde::Deserialize;
use workspace::Workspace;
@ -24,8 +24,8 @@ struct Decrement {
impl_actions!(vim, [Increment, Decrement]);
pub fn init(cx: &mut AppContext) {
cx.add_action(|_: &mut Workspace, action: &Increment, cx| {
pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(|_: &mut Workspace, action: &Increment, cx| {
Vim::update(cx, |vim, cx| {
vim.record_current_action(cx);
let count = vim.take_count(cx).unwrap_or(1);
@ -33,7 +33,7 @@ pub fn init(cx: &mut AppContext) {
increment(vim, count as i32, step, cx)
})
});
cx.add_action(|_: &mut Workspace, action: &Decrement, cx| {
workspace.register_action(|_: &mut Workspace, action: &Decrement, cx| {
Vim::update(cx, |vim, cx| {
vim.record_current_action(cx);
let count = vim.take_count(cx).unwrap_or(1);

View file

@ -4,7 +4,7 @@ use editor::{
display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, ClipboardSelection,
DisplayPoint,
};
use gpui::{impl_actions, AppContext, ViewContext};
use gpui::{impl_actions, ViewContext};
use language::{Bias, SelectionGoal};
use serde::Deserialize;
use workspace::Workspace;
@ -22,8 +22,8 @@ struct Paste {
impl_actions!(vim, [Paste]);
pub(crate) fn init(cx: &mut AppContext) {
cx.add_action(paste);
pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(paste);
}
fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {

View file

@ -5,14 +5,14 @@ use crate::{
visual::visual_motion,
Vim,
};
use gpui::{actions, Action, AppContext, WindowContext};
use gpui::{actions, Action, ViewContext, WindowContext};
use workspace::Workspace;
actions!(vim, [Repeat, EndRepeat,]);
actions!(vim, [Repeat, EndRepeat]);
fn should_replay(action: &Box<dyn Action>) -> bool {
// skip so that we don't leave the character palette open
if editor::ShowCharacterPalette.id() == action.id() {
if editor::ShowCharacterPalette.partial_eq(&**action) {
return false;
}
true
@ -21,14 +21,14 @@ fn should_replay(action: &Box<dyn Action>) -> bool {
fn repeatable_insert(action: &ReplayableAction) -> Option<Box<dyn Action>> {
match action {
ReplayableAction::Action(action) => {
if super::InsertBefore.id() == action.id()
|| super::InsertAfter.id() == action.id()
|| super::InsertFirstNonWhitespace.id() == action.id()
|| super::InsertEndOfLine.id() == action.id()
if super::InsertBefore.partial_eq(&**action)
|| super::InsertAfter.partial_eq(&**action)
|| super::InsertFirstNonWhitespace.partial_eq(&**action)
|| super::InsertEndOfLine.partial_eq(&**action)
{
Some(super::InsertBefore.boxed_clone())
} else if super::InsertLineAbove.id() == action.id()
|| super::InsertLineBelow.id() == action.id()
} else if super::InsertLineAbove.partial_eq(&**action)
|| super::InsertLineBelow.partial_eq(&**action)
{
Some(super::InsertLineBelow.boxed_clone())
} else {
@ -39,15 +39,15 @@ fn repeatable_insert(action: &ReplayableAction) -> Option<Box<dyn Action>> {
}
}
pub(crate) fn init(cx: &mut AppContext) {
cx.add_action(|_: &mut Workspace, _: &EndRepeat, cx| {
pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(|_: &mut Workspace, _: &EndRepeat, cx| {
Vim::update(cx, |vim, cx| {
vim.workspace_state.replaying = false;
vim.switch_mode(Mode::Normal, false, cx)
});
});
cx.add_action(|_: &mut Workspace, _: &Repeat, cx| repeat(cx, false));
workspace.register_action(|_: &mut Workspace, _: &Repeat, cx| repeat(cx, false));
}
pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
@ -142,7 +142,7 @@ pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
// 3 times, instead it inserts the content thrice at the insert position.
if let Some(to_repeat) = repeatable_insert(&actions[0]) {
if let Some(ReplayableAction::Action(action)) = actions.last() {
if action.id() == NormalBefore.id() {
if NormalBefore.partial_eq(&**action) {
actions.pop();
}
}
@ -166,50 +166,43 @@ pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
}
Vim::update(cx, |vim, _| vim.workspace_state.replaying = true);
let window = cx.window();
cx.app_context()
.spawn(move |mut cx| async move {
editor.update(&mut cx, |editor, _| {
editor.show_local_selections = false;
})?;
for action in actions {
match action {
ReplayableAction::Action(action) => {
if should_replay(&action) {
window
.dispatch_action(editor.id(), action.as_ref(), &mut cx)
.ok_or_else(|| anyhow::anyhow!("window was closed"))
} else {
Ok(())
}
let window = cx.window_handle();
cx.spawn(move |mut cx| async move {
editor.update(&mut cx, |editor, _| {
editor.show_local_selections = false;
})?;
for action in actions {
match action {
ReplayableAction::Action(action) => {
if should_replay(&action) {
window.update(&mut cx, |_, cx| cx.dispatch_action(action))
} else {
Ok(())
}
ReplayableAction::Insertion {
text,
utf16_range_to_replace,
} => editor.update(&mut cx, |editor, cx| {
editor.replay_insert_event(&text, utf16_range_to_replace.clone(), cx)
}),
}?
}
editor.update(&mut cx, |editor, _| {
editor.show_local_selections = true;
})?;
window
.dispatch_action(editor.id(), &EndRepeat, &mut cx)
.ok_or_else(|| anyhow::anyhow!("window was closed"))
})
.detach_and_log_err(cx);
}
ReplayableAction::Insertion {
text,
utf16_range_to_replace,
} => editor.update(&mut cx, |editor, cx| {
editor.replay_insert_event(&text, utf16_range_to_replace.clone(), cx)
}),
}?
}
editor.update(&mut cx, |editor, _| {
editor.show_local_selections = true;
})?;
window.update(&mut cx, |_, cx| cx.dispatch_action(EndRepeat.boxed_clone()))
})
.detach_and_log_err(cx);
}
#[cfg(test)]
mod test {
use std::sync::Arc;
use editor::test::editor_lsp_test_context::EditorLspTestContext;
use futures::StreamExt;
use indoc::indoc;
use gpui::{executor::Deterministic, View};
use gpui::InputHandler;
use crate::{
state::Mode,
@ -217,7 +210,7 @@ mod test {
};
#[gpui::test]
async fn test_dot_repeat(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
async fn test_dot_repeat(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
// "o"
@ -226,38 +219,32 @@ mod test {
.await;
cx.assert_shared_state("hello\nworlˇd").await;
cx.simulate_shared_keystrokes(["."]).await;
deterministic.run_until_parked();
cx.assert_shared_state("hello\nworld\nworlˇd").await;
// "d"
cx.simulate_shared_keystrokes(["^", "d", "f", "o"]).await;
cx.simulate_shared_keystrokes(["g", "g", "."]).await;
deterministic.run_until_parked();
cx.assert_shared_state("ˇ\nworld\nrld").await;
// "p" (note that it pastes the current clipboard)
cx.simulate_shared_keystrokes(["j", "y", "y", "p"]).await;
cx.simulate_shared_keystrokes(["shift-g", "y", "y", "."])
.await;
deterministic.run_until_parked();
cx.assert_shared_state("\nworld\nworld\nrld\nˇrld").await;
// "~" (note that counts apply to the action taken, not . itself)
cx.set_shared_state("ˇthe quick brown fox").await;
cx.simulate_shared_keystrokes(["2", "~", "."]).await;
deterministic.run_until_parked();
cx.set_shared_state("THE ˇquick brown fox").await;
cx.simulate_shared_keystrokes(["3", "."]).await;
deterministic.run_until_parked();
cx.set_shared_state("THE QUIˇck brown fox").await;
deterministic.run_until_parked();
cx.run_until_parked();
cx.simulate_shared_keystrokes(["."]).await;
deterministic.run_until_parked();
cx.assert_shared_state("THE QUICK ˇbrown fox").await;
}
#[gpui::test]
async fn test_repeat_ime(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
async fn test_repeat_ime(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state("hˇllo", Mode::Normal);
@ -271,15 +258,12 @@ mod test {
cx.simulate_keystrokes(["escape"]);
cx.assert_state("hˇällo", Mode::Normal);
cx.simulate_keystrokes(["."]);
deterministic.run_until_parked();
cx.assert_state("hˇäällo", Mode::Normal);
}
#[gpui::test]
async fn test_repeat_completion(
deterministic: Arc<Deterministic>,
cx: &mut gpui::TestAppContext,
) {
async fn test_repeat_completion(cx: &mut gpui::TestAppContext) {
VimTestContext::init(cx);
let cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
@ -340,7 +324,6 @@ mod test {
Mode::Normal,
);
cx.simulate_keystrokes(["j", "."]);
deterministic.run_until_parked();
cx.assert_state(
indoc! {"
one.second!
@ -352,7 +335,7 @@ mod test {
}
#[gpui::test]
async fn test_repeat_visual(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
async fn test_repeat_visual(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
// single-line (3 columns)
@ -371,7 +354,6 @@ mod test {
})
.await;
cx.simulate_shared_keystrokes(["j", "w", "."]).await;
deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
"o quick brown
fox ˇops over
@ -379,7 +361,6 @@ mod test {
})
.await;
cx.simulate_shared_keystrokes(["f", "r", "."]).await;
deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
"o quick brown
fox ops oveˇothe lazy dog"
@ -404,7 +385,6 @@ mod test {
})
.await;
cx.simulate_shared_keystrokes(["."]).await;
deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
"the ˇumps over
fox jumps over
@ -412,14 +392,12 @@ mod test {
})
.await;
cx.simulate_shared_keystrokes(["w", "."]).await;
deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
"the umps ˇumps over
the lazy dog"
})
.await;
cx.simulate_shared_keystrokes(["j", "."]).await;
deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
"the umps umps over
the ˇog"
@ -442,7 +420,6 @@ mod test {
})
.await;
cx.simulate_shared_keystrokes(["j", "4", "l", "."]).await;
deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
"othe quick brown
ofoxˇo jumps over
@ -466,7 +443,6 @@ mod test {
})
.await;
cx.simulate_shared_keystrokes(["j", "."]).await;
deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
"o
ˇo
@ -476,10 +452,7 @@ mod test {
}
#[gpui::test]
async fn test_repeat_motion_counts(
deterministic: Arc<Deterministic>,
cx: &mut gpui::TestAppContext,
) {
async fn test_repeat_motion_counts(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {
@ -496,7 +469,6 @@ mod test {
})
.await;
cx.simulate_shared_keystrokes(["j", "."]).await;
deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
" brown
ˇ over
@ -504,7 +476,6 @@ mod test {
})
.await;
cx.simulate_shared_keystrokes(["j", "2", "."]).await;
deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
" brown
over
@ -514,15 +485,12 @@ mod test {
}
#[gpui::test]
async fn test_record_interrupted(
deterministic: Arc<Deterministic>,
cx: &mut gpui::TestAppContext,
) {
async fn test_record_interrupted(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state("ˇhello\n", Mode::Normal);
cx.simulate_keystrokes(["4", "i", "j", "cmd-shift-p", "escape", "escape"]);
deterministic.run_until_parked();
cx.simulate_keystrokes(["4", "i", "j", "cmd-shift-p", "escape"]);
cx.simulate_keystrokes(["escape"]);
cx.assert_state("ˇjhello\n", Mode::Normal);
}
}

View file

@ -4,29 +4,29 @@ use editor::{
scroll::{scroll_amount::ScrollAmount, VERTICAL_SCROLL_MARGIN},
DisplayPoint, Editor,
};
use gpui::{actions, AppContext, ViewContext};
use gpui::{actions, ViewContext};
use language::Bias;
use workspace::Workspace;
actions!(
vim,
[LineUp, LineDown, ScrollUp, ScrollDown, PageUp, PageDown,]
[LineUp, LineDown, ScrollUp, ScrollDown, PageUp, PageDown]
);
pub fn init(cx: &mut AppContext) {
cx.add_action(|_: &mut Workspace, _: &LineDown, cx| {
pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(|_: &mut Workspace, _: &LineDown, cx| {
scroll(cx, false, |c| ScrollAmount::Line(c.unwrap_or(1.)))
});
cx.add_action(|_: &mut Workspace, _: &LineUp, cx| {
workspace.register_action(|_: &mut Workspace, _: &LineUp, cx| {
scroll(cx, false, |c| ScrollAmount::Line(-c.unwrap_or(1.)))
});
cx.add_action(|_: &mut Workspace, _: &PageDown, cx| {
workspace.register_action(|_: &mut Workspace, _: &PageDown, cx| {
scroll(cx, false, |c| ScrollAmount::Page(c.unwrap_or(1.)))
});
cx.add_action(|_: &mut Workspace, _: &PageUp, cx| {
workspace.register_action(|_: &mut Workspace, _: &PageUp, cx| {
scroll(cx, false, |c| ScrollAmount::Page(-c.unwrap_or(1.)))
});
cx.add_action(|_: &mut Workspace, _: &ScrollDown, cx| {
workspace.register_action(|_: &mut Workspace, _: &ScrollDown, cx| {
scroll(cx, true, |c| {
if let Some(c) = c {
ScrollAmount::Line(c)
@ -35,7 +35,7 @@ pub fn init(cx: &mut AppContext) {
}
})
});
cx.add_action(|_: &mut Workspace, _: &ScrollUp, cx| {
workspace.register_action(|_: &mut Workspace, _: &ScrollUp, cx| {
scroll(cx, true, |c| {
if let Some(c) = c {
ScrollAmount::Line(-c)
@ -114,7 +114,7 @@ mod test {
state::Mode,
test::{NeovimBackedTestContext, VimTestContext},
};
use gpui::geometry::vector::vec2f;
use gpui::{point, px, size, Context};
use indoc::indoc;
use language::Point;
@ -122,10 +122,27 @@ mod test {
async fn test_scroll(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
let (line_height, visible_line_count) = cx.editor(|editor, cx| {
(
editor
.style()
.unwrap()
.text
.line_height_in_pixels(cx.rem_size()),
editor.visible_line_count().unwrap(),
)
});
let window = cx.window;
let line_height =
cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
window.simulate_resize(vec2f(1000., 8.0 * line_height - 1.0), &mut cx);
let margin = cx
.update_window(window, |_, cx| {
cx.viewport_size().height - line_height * visible_line_count
})
.unwrap();
cx.simulate_window_resize(
cx.window,
size(px(1000.), margin + 8. * line_height - px(1.0)),
);
cx.set_state(
indoc!(
@ -147,29 +164,29 @@ mod test {
);
cx.update_editor(|editor, cx| {
assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.))
assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 0.))
});
cx.simulate_keystrokes(["ctrl-e"]);
cx.update_editor(|editor, cx| {
assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 1.))
assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 1.))
});
cx.simulate_keystrokes(["2", "ctrl-e"]);
cx.update_editor(|editor, cx| {
assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.))
assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 3.))
});
cx.simulate_keystrokes(["ctrl-y"]);
cx.update_editor(|editor, cx| {
assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 2.))
assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 2.))
});
// does not select in normal mode
cx.simulate_keystrokes(["g", "g"]);
cx.update_editor(|editor, cx| {
assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.))
assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 0.))
});
cx.simulate_keystrokes(["ctrl-d"]);
cx.update_editor(|editor, cx| {
assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.0));
assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 3.0));
assert_eq!(
editor.selections.newest(cx).range(),
Point::new(6, 0)..Point::new(6, 0)
@ -179,11 +196,11 @@ mod test {
// does select in visual mode
cx.simulate_keystrokes(["g", "g"]);
cx.update_editor(|editor, cx| {
assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.))
assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 0.))
});
cx.simulate_keystrokes(["v", "ctrl-d"]);
cx.update_editor(|editor, cx| {
assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.0));
assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 3.0));
assert_eq!(
editor.selections.newest(cx).range(),
Point::new(0, 0)..Point::new(6, 1)

View file

@ -1,7 +1,7 @@
use gpui::{actions, impl_actions, AppContext, ViewContext};
use gpui::{actions, impl_actions, ViewContext};
use search::{buffer_search, BufferSearchBar, SearchMode, SearchOptions};
use serde_derive::Deserialize;
use workspace::{searchable::Direction, Pane, Workspace};
use workspace::{searchable::Direction, Workspace};
use crate::{motion::Motion, normal::move_cursor, state::SearchState, Vim};
@ -44,21 +44,21 @@ struct Replacement {
is_case_sensitive: bool,
}
actions!(vim, [SearchSubmit]);
impl_actions!(
vim,
[MoveToNext, MoveToPrev, Search, FindCommand, ReplaceCommand]
[FindCommand, ReplaceCommand, Search, MoveToPrev, MoveToNext]
);
actions!(vim, [SearchSubmit]);
pub(crate) fn init(cx: &mut AppContext) {
cx.add_action(move_to_next);
cx.add_action(move_to_prev);
cx.add_action(search);
cx.add_action(search_submit);
cx.add_action(search_deploy);
pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(move_to_next);
workspace.register_action(move_to_prev);
workspace.register_action(search);
workspace.register_action(search_submit);
workspace.register_action(search_deploy);
cx.add_action(find_command);
cx.add_action(replace_command);
workspace.register_action(find_command);
workspace.register_action(replace_command);
}
fn move_to_next(workspace: &mut Workspace, action: &MoveToNext, cx: &mut ViewContext<Workspace>) {
@ -106,9 +106,9 @@ fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Works
}
// hook into the existing to clear out any vim search state on cmd+f or edit -> find.
fn search_deploy(_: &mut Pane, _: &buffer_search::Deploy, cx: &mut ViewContext<Pane>) {
fn search_deploy(_: &mut Workspace, _: &buffer_search::Deploy, cx: &mut ViewContext<Workspace>) {
Vim::update(cx, |vim, _| vim.workspace_state.search = Default::default());
cx.propagate_action();
cx.propagate();
}
fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewContext<Workspace>) {
@ -347,58 +347,50 @@ fn parse_replace_all(query: &str) -> Replacement {
#[cfg(test)]
mod test {
use std::sync::Arc;
use editor::DisplayPoint;
use search::BufferSearchBar;
use crate::{state::Mode, test::VimTestContext};
#[gpui::test]
async fn test_move_to_next(
cx: &mut gpui::TestAppContext,
deterministic: Arc<gpui::executor::Deterministic>,
) {
async fn test_move_to_next(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state("ˇhi\nhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes(["*"]);
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
cx.simulate_keystrokes(["*"]);
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes(["#"]);
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
cx.simulate_keystrokes(["#"]);
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes(["2", "*"]);
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes(["g", "*"]);
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes(["n"]);
cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
cx.simulate_keystrokes(["g", "#"]);
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
}
#[gpui::test]
async fn test_search(
cx: &mut gpui::TestAppContext,
deterministic: Arc<gpui::executor::Deterministic>,
) {
async fn test_search(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
@ -414,11 +406,11 @@ mod test {
.expect("Buffer search bar should be deployed")
});
search_bar.read_with(cx.cx, |bar, cx| {
cx.update_view(search_bar, |bar, cx| {
assert_eq!(bar.query(cx), "cc");
});
deterministic.run_until_parked();
cx.run_until_parked();
cx.update_editor(|editor, cx| {
let highlights = editor.all_text_background_highlights(cx);
@ -440,51 +432,41 @@ mod test {
// ?<enter> to go to previous
cx.simulate_keystrokes(["?", "enter"]);
deterministic.run_until_parked();
cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
cx.simulate_keystrokes(["?", "enter"]);
deterministic.run_until_parked();
cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);
// /<enter> to go to next
cx.simulate_keystrokes(["/", "enter"]);
deterministic.run_until_parked();
cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
// ?{search}<enter> to search backwards
cx.simulate_keystrokes(["?", "b", "enter"]);
deterministic.run_until_parked();
cx.assert_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
// works with counts
cx.simulate_keystrokes(["4", "/", "c"]);
deterministic.run_until_parked();
cx.simulate_keystrokes(["enter"]);
cx.assert_state("aa\nbb\ncc\ncˇc\ncc\n", Mode::Normal);
// check that searching resumes from cursor, not previous match
cx.set_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal);
cx.simulate_keystrokes(["/", "d"]);
deterministic.run_until_parked();
cx.simulate_keystrokes(["enter"]);
cx.assert_state("aa\nbb\nˇdd\ncc\nbb\n", Mode::Normal);
cx.update_editor(|editor, cx| editor.move_to_beginning(&Default::default(), cx));
cx.assert_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal);
cx.simulate_keystrokes(["/", "b"]);
deterministic.run_until_parked();
cx.simulate_keystrokes(["enter"]);
cx.assert_state("aa\nˇbb\ndd\ncc\nbb\n", Mode::Normal);
}
#[gpui::test]
async fn test_non_vim_search(
cx: &mut gpui::TestAppContext,
deterministic: Arc<gpui::executor::Deterministic>,
) {
async fn test_non_vim_search(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, false).await;
cx.set_state("ˇone one one one", Mode::Normal);
cx.simulate_keystrokes(["cmd-f"]);
deterministic.run_until_parked();
cx.run_until_parked();
cx.assert_editor_state("«oneˇ» one one one");
cx.simulate_keystrokes(["enter"]);

View file

@ -1,5 +1,5 @@
use editor::movement;
use gpui::{actions, AppContext, WindowContext};
use gpui::{actions, ViewContext, WindowContext};
use language::Point;
use workspace::Workspace;
@ -7,8 +7,8 @@ use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim};
actions!(vim, [Substitute, SubstituteLine]);
pub(crate) fn init(cx: &mut AppContext) {
cx.add_action(|_: &mut Workspace, _: &Substitute, cx| {
pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(|_: &mut Workspace, _: &Substitute, cx| {
Vim::update(cx, |vim, cx| {
vim.start_recording(cx);
let count = vim.take_count(cx);
@ -16,7 +16,7 @@ pub(crate) fn init(cx: &mut AppContext) {
})
});
cx.add_action(|_: &mut Workspace, _: &SubstituteLine, cx| {
workspace.register_action(|_: &mut Workspace, _: &SubstituteLine, cx| {
Vim::update(cx, |vim, cx| {
vim.start_recording(cx);
if matches!(vim.state().mode, Mode::VisualBlock | Mode::Visual) {

View file

@ -6,7 +6,7 @@ use editor::{
movement::{self, FindRange},
Bias, CharKind, DisplayPoint,
};
use gpui::{actions, impl_actions, AppContext, WindowContext};
use gpui::{actions, impl_actions, ViewContext, WindowContext};
use language::Selection;
use serde::Deserialize;
use workspace::Workspace;
@ -34,6 +34,8 @@ struct Word {
ignore_punctuation: bool,
}
impl_actions!(vim, [Word]);
actions!(
vim,
[
@ -48,25 +50,36 @@ actions!(
AngleBrackets
]
);
impl_actions!(vim, [Word]);
pub fn init(cx: &mut AppContext) {
cx.add_action(
pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(
|_: &mut Workspace, &Word { ignore_punctuation }: &Word, cx: _| {
object(Object::Word { ignore_punctuation }, cx)
},
);
cx.add_action(|_: &mut Workspace, _: &Sentence, cx: _| object(Object::Sentence, cx));
cx.add_action(|_: &mut Workspace, _: &Quotes, cx: _| object(Object::Quotes, cx));
cx.add_action(|_: &mut Workspace, _: &BackQuotes, cx: _| object(Object::BackQuotes, cx));
cx.add_action(|_: &mut Workspace, _: &DoubleQuotes, cx: _| object(Object::DoubleQuotes, cx));
cx.add_action(|_: &mut Workspace, _: &Parentheses, cx: _| object(Object::Parentheses, cx));
cx.add_action(|_: &mut Workspace, _: &SquareBrackets, cx: _| {
workspace
.register_action(|_: &mut Workspace, _: &Sentence, cx: _| object(Object::Sentence, cx));
workspace.register_action(|_: &mut Workspace, _: &Quotes, cx: _| object(Object::Quotes, cx));
workspace
.register_action(|_: &mut Workspace, _: &BackQuotes, cx: _| object(Object::BackQuotes, cx));
workspace.register_action(|_: &mut Workspace, _: &DoubleQuotes, cx: _| {
object(Object::DoubleQuotes, cx)
});
workspace.register_action(|_: &mut Workspace, _: &Parentheses, cx: _| {
object(Object::Parentheses, cx)
});
workspace.register_action(|_: &mut Workspace, _: &SquareBrackets, cx: _| {
object(Object::SquareBrackets, cx)
});
cx.add_action(|_: &mut Workspace, _: &CurlyBrackets, cx: _| object(Object::CurlyBrackets, cx));
cx.add_action(|_: &mut Workspace, _: &AngleBrackets, cx: _| object(Object::AngleBrackets, cx));
cx.add_action(|_: &mut Workspace, _: &VerticalBars, cx: _| object(Object::VerticalBars, cx));
workspace.register_action(|_: &mut Workspace, _: &CurlyBrackets, cx: _| {
object(Object::CurlyBrackets, cx)
});
workspace.register_action(|_: &mut Workspace, _: &AngleBrackets, cx: _| {
object(Object::AngleBrackets, cx)
});
workspace.register_action(|_: &mut Workspace, _: &VerticalBars, cx: _| {
object(Object::VerticalBars, cx)
});
}
fn object(object: Object, cx: &mut WindowContext) {

View file

@ -1,6 +1,6 @@
use std::{ops::Range, sync::Arc};
use gpui::{keymap_matcher::KeymapContext, Action};
use gpui::{Action, KeyContext};
use language::CursorShape;
use serde::{Deserialize, Serialize};
use workspace::searchable::Direction;
@ -167,10 +167,10 @@ impl EditorState {
self.operator_stack.last().copied()
}
pub fn keymap_context_layer(&self) -> KeymapContext {
let mut context = KeymapContext::default();
context.add_identifier("VimEnabled");
context.add_key(
pub fn keymap_context_layer(&self) -> KeyContext {
let mut context = KeyContext::default();
context.add("VimEnabled");
context.set(
"vim_mode",
match self.mode {
Mode::Normal => "normal",
@ -180,24 +180,24 @@ impl EditorState {
);
if self.vim_controlled() {
context.add_identifier("VimControl");
context.add("VimControl");
}
if self.active_operator().is_none() && self.pre_count.is_some()
|| self.active_operator().is_some() && self.post_count.is_some()
{
context.add_identifier("VimCount");
context.add("VimCount");
}
let active_operator = self.active_operator();
if let Some(active_operator) = active_operator {
for context_flag in active_operator.context_flags().into_iter() {
context.add_identifier(*context_flag);
context.add(*context_flag);
}
}
context.add_key(
context.set(
"vim_operator",
active_operator.map(|op| op.id()).unwrap_or_else(|| "none"),
);

View file

@ -3,8 +3,6 @@ mod neovim_backed_test_context;
mod neovim_connection;
mod vim_test_context;
use std::sync::Arc;
use command_palette::CommandPalette;
use editor::DisplayPoint;
pub use neovim_backed_binding_test_context::*;
@ -96,7 +94,7 @@ async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
.expect("Buffer search bar should be deployed")
});
search_bar.read_with(cx.cx, |bar, cx| {
cx.update_view(search_bar, |bar, cx| {
assert_eq!(bar.query(cx), "");
})
}
@ -149,9 +147,10 @@ async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
cx.set_state("aˇbc\n", Mode::Normal);
cx.simulate_keystrokes(["i", "cmd-shift-p"]);
assert!(cx.workspace(|workspace, _| workspace.modal::<CommandPalette>().is_some()));
assert!(cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
cx.simulate_keystroke("escape");
assert!(!cx.workspace(|workspace, _| workspace.modal::<CommandPalette>().is_some()));
cx.run_until_parked();
assert!(!cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
cx.assert_state("aˇbc\n", Mode::Insert);
}
@ -182,7 +181,7 @@ async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
.expect("Buffer search bar should be deployed")
});
search_bar.read_with(cx.cx, |bar, cx| {
cx.update_view(search_bar, |bar, cx| {
assert_eq!(bar.query(cx), "cc");
});
@ -204,12 +203,8 @@ async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
async fn test_status_indicator(
cx: &mut gpui::TestAppContext,
deterministic: Arc<gpui::executor::Deterministic>,
) {
async fn test_status_indicator(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
deterministic.run_until_parked();
let mode_indicator = cx.workspace(|workspace, cx| {
let status_bar = workspace.status_bar().read(cx);
@ -225,7 +220,6 @@ async fn test_status_indicator(
// shows the correct mode
cx.simulate_keystrokes(["i"]);
deterministic.run_until_parked();
assert_eq!(
cx.workspace(|_, cx| mode_indicator.read(cx).mode),
Some(Mode::Insert)
@ -233,7 +227,6 @@ async fn test_status_indicator(
// shows even in search
cx.simulate_keystrokes(["escape", "v", "/"]);
deterministic.run_until_parked();
assert_eq!(
cx.workspace(|_, cx| mode_indicator.read(cx).mode),
Some(Mode::Visual)
@ -241,7 +234,7 @@ async fn test_status_indicator(
// hides if vim mode is disabled
cx.disable_vim();
deterministic.run_until_parked();
cx.run_until_parked();
cx.workspace(|workspace, cx| {
let status_bar = workspace.status_bar().read(cx);
let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
@ -249,7 +242,7 @@ async fn test_status_indicator(
});
cx.enable_vim();
deterministic.run_until_parked();
cx.run_until_parked();
cx.workspace(|workspace, cx| {
let status_bar = workspace.status_bar().read(cx);
let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();

View file

@ -1,4 +1,5 @@
use editor::scroll::VERTICAL_SCROLL_MARGIN;
use editor::{scroll::VERTICAL_SCROLL_MARGIN, test::editor_test_context::ContextHandle};
use gpui::{px, size, Context};
use indoc::indoc;
use settings::SettingsStore;
use std::{
@ -7,7 +8,6 @@ use std::{
};
use collections::{HashMap, HashSet};
use gpui::{geometry::vector::vec2f, ContextHandle};
use language::language_settings::{AllLanguageSettings, SoftWrap};
use util::test::marked_text_offsets;
@ -158,11 +158,28 @@ impl<'a> NeovimBackedTestContext<'a> {
.await;
// +2 to account for the vim command UI at the bottom.
self.neovim.set_option(&format!("lines={}", rows + 2)).await;
let window = self.window;
let line_height =
self.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
let (line_height, visible_line_count) = self.editor(|editor, cx| {
(
editor
.style()
.unwrap()
.text
.line_height_in_pixels(cx.rem_size()),
editor.visible_line_count().unwrap(),
)
});
window.simulate_resize(vec2f(1000., (rows as f32) * line_height), &mut self.cx);
let window = self.window;
let margin = self
.update_window(window, |_, cx| {
cx.viewport_size().height - line_height * visible_line_count
})
.unwrap();
self.simulate_window_resize(
self.window,
size(px(1000.), margin + (rows as f32) * line_height),
);
}
pub async fn set_neovim_option(&mut self, option: &str) {
@ -211,12 +228,7 @@ impl<'a> NeovimBackedTestContext<'a> {
pub async fn assert_shared_clipboard(&mut self, text: &str) {
let neovim = self.neovim.read_register('"').await;
let editor = self
.platform()
.read_from_clipboard()
.unwrap()
.text()
.clone();
let editor = self.read_from_clipboard().unwrap().text().clone();
if text == neovim && text == editor {
return;

View file

@ -10,7 +10,7 @@ use async_compat::Compat;
#[cfg(feature = "neovim")]
use async_trait::async_trait;
#[cfg(feature = "neovim")]
use gpui::keymap_matcher::Keystroke;
use gpui::Keystroke;
#[cfg(feature = "neovim")]
use language::Point;
@ -116,16 +116,24 @@ impl NeovimConnection {
keystroke.key = "lt".to_string()
}
let special = keystroke.shift
|| keystroke.ctrl
|| keystroke.alt
|| keystroke.cmd
let special = keystroke.modifiers.shift
|| keystroke.modifiers.control
|| keystroke.modifiers.alt
|| keystroke.modifiers.command
|| keystroke.key.len() > 1;
let start = if special { "<" } else { "" };
let shift = if keystroke.shift { "S-" } else { "" };
let ctrl = if keystroke.ctrl { "C-" } else { "" };
let alt = if keystroke.alt { "M-" } else { "" };
let cmd = if keystroke.cmd { "D-" } else { "" };
let shift = if keystroke.modifiers.shift { "S-" } else { "" };
let ctrl = if keystroke.modifiers.control {
"C-"
} else {
""
};
let alt = if keystroke.modifiers.alt { "M-" } else { "" };
let cmd = if keystroke.modifiers.command {
"D-"
} else {
""
};
let end = if special { ">" } else { "" };
let key = format!("{start}{shift}{ctrl}{alt}{cmd}{}{end}", keystroke.key);

View file

@ -4,9 +4,9 @@ use editor::test::{
editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
};
use futures::Future;
use gpui::ContextHandle;
use gpui::{Context, View, VisualContext};
use lsp::request;
use search::{BufferSearchBar, ProjectSearchBar};
use search::BufferSearchBar;
use crate::{state::Operator, *};
@ -15,12 +15,28 @@ pub struct VimTestContext<'a> {
}
impl<'a> VimTestContext<'a> {
pub fn init(cx: &mut gpui::TestAppContext) {
if cx.has_global::<Vim>() {
dbg!("OOPS");
return;
}
cx.update(|cx| {
search::init(cx);
let settings = SettingsStore::test(cx);
cx.set_global(settings);
command_palette::init(cx);
crate::init(cx);
});
}
pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
Self::init(cx);
let lsp = EditorLspTestContext::new_rust(Default::default(), cx).await;
Self::new_with_lsp(lsp, enabled)
}
pub async fn new_typescript(cx: &'a mut gpui::TestAppContext) -> VimTestContext<'a> {
Self::init(cx);
Self::new_with_lsp(
EditorLspTestContext::new_typescript(Default::default(), cx).await,
true,
@ -28,12 +44,6 @@ impl<'a> VimTestContext<'a> {
}
pub fn new_with_lsp(mut cx: EditorLspTestContext<'a>, enabled: bool) -> VimTestContext<'a> {
cx.update(|cx| {
search::init(cx);
crate::init(cx);
command_palette::init(cx);
});
cx.update(|cx| {
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled));
@ -47,14 +57,15 @@ impl<'a> VimTestContext<'a> {
observe_keystrokes(cx);
workspace.active_pane().update(cx, |pane, cx| {
pane.toolbar().update(cx, |toolbar, cx| {
let buffer_search_bar = cx.add_view(BufferSearchBar::new);
let buffer_search_bar = cx.new_view(BufferSearchBar::new);
toolbar.add_item(buffer_search_bar, cx);
let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
toolbar.add_item(project_search_bar, cx);
// todo!();
// let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
// toolbar.add_item(project_search_bar, cx);
})
});
workspace.status_bar().update(cx, |status_bar, cx| {
let vim_mode_indicator = cx.add_view(ModeIndicator::new);
let vim_mode_indicator = cx.new_view(ModeIndicator::new);
status_bar.add_right_item(vim_mode_indicator, cx);
});
});
@ -62,11 +73,21 @@ impl<'a> VimTestContext<'a> {
Self { cx }
}
pub fn workspace<F, T>(&mut self, read: F) -> T
pub fn update_view<F, T, R>(&mut self, view: View<T>, update: F) -> R
where
F: FnOnce(&Workspace, &ViewContext<Workspace>) -> T,
T: 'static,
F: FnOnce(&mut T, &mut ViewContext<T>) -> R + 'static,
{
self.cx.workspace.read_with(self.cx.cx.cx, read)
let window = self.window.clone();
self.update_window(window, move |_, cx| view.update(cx, update))
.unwrap()
}
pub fn workspace<F, T>(&mut self, update: F) -> T
where
F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
{
self.cx.update_workspace(update)
}
pub fn enable_vim(&mut self) {
@ -94,16 +115,16 @@ impl<'a> VimTestContext<'a> {
.read(|cx| cx.global::<Vim>().state().operator_stack.last().copied())
}
pub fn set_state(&mut self, text: &str, mode: Mode) -> ContextHandle {
pub fn set_state(&mut self, text: &str, mode: Mode) {
let window = self.window;
let context_handle = self.cx.set_state(text);
window.update(self.cx.cx.cx, |cx| {
self.cx.set_state(text);
self.update_window(window, |_, cx| {
Vim::update(cx, |vim, cx| {
vim.switch_mode(mode, true, cx);
})
});
self.cx.foreground().run_until_parked();
context_handle
})
.unwrap();
self.cx.cx.cx.run_until_parked();
}
#[track_caller]

View file

@ -1,5 +1,3 @@
#![allow(unused)]
#[cfg(test)]
mod test;
@ -17,17 +15,17 @@ mod visual;
use anyhow::Result;
use collections::{CommandPaletteFilter, HashMap};
use command_palette::CommandPaletteInterceptor;
use editor::{movement, Editor, EditorMode, Event};
use editor::{movement, Editor, EditorEvent, EditorMode};
use gpui::{
actions, impl_actions, keymap_matcher::KeymapContext, keymap_matcher::MatchResult, Action,
AppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
actions, impl_actions, Action, AppContext, EntityId, KeyContext, Subscription, View,
ViewContext, WeakView, WindowContext,
};
use language::{CursorShape, Point, Selection, SelectionGoal};
pub use mode_indicator::ModeIndicator;
use motion::Motion;
use normal::normal_replace;
use serde::Deserialize;
use settings::{update_settings_file, Setting, SettingsStore};
use settings::{update_settings_file, Settings, SettingsStore};
use state::{EditorState, Mode, Operator, RecordedSelection, WorkspaceState};
use std::{ops::Range, sync::Arc};
use visual::{visual_block_motion, visual_replace};
@ -50,83 +48,86 @@ actions!(
vim,
[Tab, Enter, Object, InnerObject, FindForward, FindBackward]
);
// in the workspace namespace so it's not filtered out when vim is disabled.
actions!(workspace, [ToggleVimMode]);
impl_actions!(vim, [Number, SwitchMode, PushOperator]);
#[derive(Copy, Clone, Debug)]
enum VimEvent {
ModeChanged { mode: Mode },
}
impl_actions!(vim, [SwitchMode, PushOperator, Number]);
pub fn init(cx: &mut AppContext) {
cx.set_global(Vim::default());
settings::register::<VimModeSetting>(cx);
VimModeSetting::register(cx);
editor_events::init(cx);
normal::init(cx);
visual::init(cx);
insert::init(cx);
object::init(cx);
motion::init(cx);
command::init(cx);
// Vim Actions
cx.add_action(|_: &mut Workspace, &SwitchMode(mode): &SwitchMode, cx| {
Vim::update(cx, |vim, cx| vim.switch_mode(mode, false, cx))
});
cx.add_action(
|_: &mut Workspace, &PushOperator(operator): &PushOperator, cx| {
Vim::update(cx, |vim, cx| vim.push_operator(operator, cx))
},
);
cx.add_action(|_: &mut Workspace, n: &Number, cx: _| {
Vim::update(cx, |vim, cx| vim.push_count_digit(n.0, cx));
});
cx.add_action(|_: &mut Workspace, _: &Tab, cx| {
Vim::active_editor_input_ignored(" ".into(), cx)
});
cx.add_action(|_: &mut Workspace, _: &Enter, cx| {
Vim::active_editor_input_ignored("\n".into(), cx)
});
cx.add_action(|workspace: &mut Workspace, _: &ToggleVimMode, cx| {
let fs = workspace.app_state().fs.clone();
let currently_enabled = settings::get::<VimModeSetting>(cx).0;
update_settings_file::<VimModeSetting>(fs, cx, move |setting| {
*setting = Some(!currently_enabled)
})
});
cx.observe_new_views(|workspace: &mut Workspace, cx| register(workspace, cx))
.detach();
// Any time settings change, update vim mode to match. The Vim struct
// will be initialized as disabled by default, so we filter its commands
// out when starting up.
cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
filter.hidden_namespaces.insert("vim");
});
cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
vim.set_enabled(settings::get::<VimModeSetting>(cx).0, cx)
vim.set_enabled(VimModeSetting::get_global(cx).0, cx)
});
cx.observe_global::<SettingsStore, _>(|cx| {
cx.observe_global::<SettingsStore>(|cx| {
cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
vim.set_enabled(settings::get::<VimModeSetting>(cx).0, cx)
vim.set_enabled(VimModeSetting::get_global(cx).0, cx)
});
})
.detach();
}
fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
workspace.register_action(|_: &mut Workspace, &SwitchMode(mode): &SwitchMode, cx| {
Vim::update(cx, |vim, cx| vim.switch_mode(mode, false, cx))
});
workspace.register_action(
|_: &mut Workspace, &PushOperator(operator): &PushOperator, cx| {
Vim::update(cx, |vim, cx| vim.push_operator(operator, cx))
},
);
workspace.register_action(|_: &mut Workspace, n: &Number, cx: _| {
Vim::update(cx, |vim, cx| vim.push_count_digit(n.0, cx));
});
workspace.register_action(|_: &mut Workspace, _: &Tab, cx| {
Vim::active_editor_input_ignored(" ".into(), cx)
});
workspace.register_action(|_: &mut Workspace, _: &Enter, cx| {
Vim::active_editor_input_ignored("\n".into(), cx)
});
workspace.register_action(|workspace: &mut Workspace, _: &ToggleVimMode, cx| {
let fs = workspace.app_state().fs.clone();
let currently_enabled = VimModeSetting::get_global(cx).0;
update_settings_file::<VimModeSetting>(fs, cx, move |setting| {
*setting = Some(!currently_enabled)
})
});
normal::register(workspace, cx);
insert::register(workspace, cx);
motion::register(workspace, cx);
command::register(workspace, cx);
object::register(workspace, cx);
visual::register(workspace, cx);
}
pub fn observe_keystrokes(cx: &mut WindowContext) {
cx.observe_keystrokes(|_keystroke, result, handled_by, cx| {
if result == &MatchResult::Pending {
return true;
}
if let Some(handled_by) = handled_by {
cx.observe_keystrokes(|keystroke_event, cx| {
if let Some(action) = keystroke_event
.action
.as_ref()
.map(|action| action.boxed_clone())
{
Vim::update(cx, |vim, _| {
if vim.workspace_state.recording {
vim.workspace_state
.recorded_actions
.push(ReplayableAction::Action(handled_by.boxed_clone()));
.push(ReplayableAction::Action(action.boxed_clone()));
if vim.workspace_state.stop_recording_after_next_action {
vim.workspace_state.recording = false;
@ -136,9 +137,11 @@ pub fn observe_keystrokes(cx: &mut WindowContext) {
});
// Keystroke is handled by the vim system, so continue forward
if handled_by.namespace() == "vim" {
return true;
if action.name().starts_with("vim::") {
return;
}
} else if cx.has_pending_keystrokes() {
return;
}
Vim::update(cx, |vim, cx| match vim.active_operator() {
@ -150,24 +153,23 @@ pub fn observe_keystrokes(cx: &mut WindowContext) {
}
_ => {}
});
true
})
.detach()
}
#[derive(Default)]
pub struct Vim {
active_editor: Option<WeakViewHandle<Editor>>,
active_editor: Option<WeakView<Editor>>,
editor_subscription: Option<Subscription>,
enabled: bool,
editor_states: HashMap<usize, EditorState>,
editor_states: HashMap<EntityId, EditorState>,
workspace_state: WorkspaceState,
default_state: EditorState,
}
impl Vim {
fn read(cx: &mut AppContext) -> &Self {
cx.default_global()
cx.global::<Self>()
}
fn update<F, S>(cx: &mut WindowContext, update: F) -> S
@ -177,21 +179,21 @@ impl Vim {
cx.update_global(update)
}
fn set_active_editor(&mut self, editor: ViewHandle<Editor>, cx: &mut WindowContext) {
fn set_active_editor(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
self.active_editor = Some(editor.clone().downgrade());
self.editor_subscription = Some(cx.subscribe(&editor, |editor, event, cx| match event {
Event::SelectionsChanged { local: true } => {
EditorEvent::SelectionsChanged { local: true } => {
let editor = editor.read(cx);
if editor.leader_peer_id().is_none() {
let newest = editor.selections.newest::<usize>(cx);
local_selections_changed(newest, cx);
}
}
Event::InputIgnored { text } => {
EditorEvent::InputIgnored { text } => {
Vim::active_editor_input_ignored(text.clone(), cx);
Vim::record_insertion(text, None, cx)
}
Event::InputHandled {
EditorEvent::InputHandled {
text,
utf16_range_to_replace: range_to_replace,
} => Vim::record_insertion(text, range_to_replace.clone(), cx),
@ -242,7 +244,7 @@ impl Vim {
cx: &mut WindowContext,
update: impl FnOnce(&mut Editor, &mut ViewContext<Editor>) -> S,
) -> Option<S> {
let editor = self.active_editor.clone()?.upgrade(cx)?;
let editor = self.active_editor.clone()?.upgrade()?;
Some(editor.update(cx, update))
}
@ -254,7 +256,8 @@ impl Vim {
let selections = self
.active_editor
.and_then(|editor| editor.upgrade(cx))
.as_ref()
.and_then(|editor| editor.upgrade())
.map(|editor| {
let editor = editor.read(cx);
(
@ -323,8 +326,6 @@ impl Vim {
self.take_count(cx);
}
cx.emit_global(VimEvent::ModeChanged { mode });
// Sync editor settings like clip mode
self.sync_vim_settings(cx);
@ -477,7 +478,7 @@ impl Vim {
if self.enabled != enabled {
self.enabled = enabled;
cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
if self.enabled {
filter.hidden_namespaces.remove("vim");
} else {
@ -491,26 +492,30 @@ impl Vim {
let _ = cx.remove_global::<CommandPaletteInterceptor>();
}
cx.update_active_window(|cx| {
if self.enabled {
let active_editor = cx
.root_view()
.downcast_ref::<Workspace>()
.and_then(|workspace| workspace.read(cx).active_item(cx))
.and_then(|item| item.downcast::<Editor>());
if let Some(active_editor) = active_editor {
self.set_active_editor(active_editor, cx);
}
self.switch_mode(Mode::Normal, false, cx);
}
self.sync_vim_settings(cx);
});
if let Some(active_window) = cx.active_window() {
active_window
.update(cx, |root_view, cx| {
if self.enabled {
let active_editor = root_view
.downcast::<Workspace>()
.ok()
.and_then(|workspace| workspace.read(cx).active_item(cx))
.and_then(|item| item.downcast::<Editor>());
if let Some(active_editor) = active_editor {
self.set_active_editor(active_editor, cx);
}
self.switch_mode(Mode::Normal, false, cx);
}
self.sync_vim_settings(cx);
})
.ok();
}
}
}
pub fn state(&self) -> &EditorState {
if let Some(active_editor) = self.active_editor.as_ref() {
if let Some(state) = self.editor_states.get(&active_editor.id()) {
if let Some(state) = self.editor_states.get(&active_editor.entity_id()) {
return state;
}
}
@ -523,7 +528,7 @@ impl Vim {
let ret = func(&mut state);
if let Some(active_editor) = self.active_editor.as_ref() {
self.editor_states.insert(active_editor.id(), state);
self.editor_states.insert(active_editor.entity_id(), state);
}
ret
@ -564,8 +569,8 @@ impl Vim {
// This is a bit of a hack, but currently the search crate does not depend on vim,
// and it seems nice to keep it that way.
if self.enabled {
let mut context = KeymapContext::default();
context.add_identifier("VimEnabled");
let mut context = KeyContext::default();
context.add("VimEnabled");
editor.set_keymap_context_layer::<Self>(context, cx)
} else {
editor.remove_keymap_context_layer::<Self>(cx);
@ -573,7 +578,7 @@ impl Vim {
}
}
impl Setting for VimModeSetting {
impl Settings for VimModeSetting {
const KEY: Option<&'static str> = Some("vim_mode");
type FileContent = Option<bool>;
@ -581,7 +586,7 @@ impl Setting for VimModeSetting {
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_: &AppContext,
_: &mut AppContext,
) -> Result<Self> {
Ok(Self(user_values.iter().rev().find_map(|v| **v).unwrap_or(
default_value.ok_or_else(Self::missing_default)?,

View file

@ -8,7 +8,7 @@ use editor::{
scroll::autoscroll::Autoscroll,
Bias, DisplayPoint, Editor,
};
use gpui::{actions, AppContext, ViewContext, WindowContext};
use gpui::{actions, ViewContext, WindowContext};
use language::{Selection, SelectionGoal};
use workspace::Workspace;
@ -34,24 +34,28 @@ actions!(
]
);
pub fn init(cx: &mut AppContext) {
cx.add_action(|_, _: &ToggleVisual, cx: &mut ViewContext<Workspace>| {
pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(|_, _: &ToggleVisual, cx: &mut ViewContext<Workspace>| {
toggle_mode(Mode::Visual, cx)
});
cx.add_action(|_, _: &ToggleVisualLine, cx: &mut ViewContext<Workspace>| {
workspace.register_action(|_, _: &ToggleVisualLine, cx: &mut ViewContext<Workspace>| {
toggle_mode(Mode::VisualLine, cx)
});
cx.add_action(
workspace.register_action(
|_, _: &ToggleVisualBlock, cx: &mut ViewContext<Workspace>| {
toggle_mode(Mode::VisualBlock, cx)
},
);
cx.add_action(other_end);
cx.add_action(delete);
cx.add_action(yank);
workspace.register_action(other_end);
workspace.register_action(delete);
workspace.register_action(yank);
cx.add_action(select_next);
cx.add_action(select_previous);
workspace.register_action(|workspace, action, cx| {
select_next(workspace, action, cx).ok();
});
workspace.register_action(|workspace, action, cx| {
select_previous(workspace, action, cx).ok();
});
}
pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
@ -146,13 +150,13 @@ pub fn visual_block_motion(
let mut head = s.newest_anchor().head().to_display_point(map);
let mut tail = s.oldest_anchor().tail().to_display_point(map);
let mut head_x = map.x_for_point(head, &text_layout_details);
let mut tail_x = map.x_for_point(tail, &text_layout_details);
let mut head_x = map.x_for_display_point(head, &text_layout_details);
let mut tail_x = map.x_for_display_point(tail, &text_layout_details);
let (start, end) = match s.newest_anchor().goal {
SelectionGoal::HorizontalRange { start, end } if preserve_goal => (start, end),
SelectionGoal::HorizontalPosition(start) if preserve_goal => (start, start),
_ => (tail_x, head_x),
_ => (tail_x.0, head_x.0),
};
let mut goal = SelectionGoal::HorizontalRange { start, end };
@ -165,19 +169,19 @@ pub fn visual_block_motion(
return;
};
head = new_head;
head_x = map.x_for_point(head, &text_layout_details);
head_x = map.x_for_display_point(head, &text_layout_details);
let is_reversed = tail_x > head_x;
if was_reversed && !is_reversed {
tail = movement::saturating_left(map, tail);
tail_x = map.x_for_point(tail, &text_layout_details);
tail_x = map.x_for_display_point(tail, &text_layout_details);
} else if !was_reversed && is_reversed {
tail = movement::saturating_right(map, tail);
tail_x = map.x_for_point(tail, &text_layout_details);
tail_x = map.x_for_display_point(tail, &text_layout_details);
}
if !is_reversed && !preserve_goal {
head = movement::saturating_right(map, head);
head_x = map.x_for_point(head, &text_layout_details);
head_x = map.x_for_display_point(head, &text_layout_details);
}
let positions = if is_reversed {
@ -188,8 +192,8 @@ pub fn visual_block_motion(
if !preserve_goal {
goal = SelectionGoal::HorizontalRange {
start: positions.start,
end: positions.end,
start: positions.start.0,
end: positions.end.0,
};
}
@ -197,7 +201,7 @@ pub fn visual_block_motion(
let mut row = tail.row();
loop {
let layed_out_line = map.lay_out_line_for_row(row, &text_layout_details);
let layed_out_line = map.layout_row(row, &text_layout_details);
let start = DisplayPoint::new(
row,
layed_out_line.closest_index_for_x(positions.start) as u32,
@ -214,7 +218,7 @@ pub fn visual_block_motion(
}
}
if positions.start <= layed_out_line.width() {
if positions.start <= layed_out_line.width {
let selection = Selection {
id: s.new_selection_id(),
start: start.to_point(map),
@ -749,7 +753,12 @@ mod test {
fox jumps over
the lazy dog"})
.await;
cx.assert_clipboard_content(Some("The q"));
assert_eq!(
cx.read_from_clipboard()
.map(|item| item.text().clone())
.unwrap(),
"The q"
);
cx.set_shared_state(indoc! {"
The quick brown