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:
parent
37e6533b28
commit
252694390a
185 changed files with 1933 additions and 29192 deletions
|
@ -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"] }
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"]);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"),
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)?,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue