Merge branch 'main' into language-api-docs
This commit is contained in:
commit
b65cf6d2d9
382 changed files with 12764 additions and 7823 deletions
|
@ -1,5 +1,5 @@
|
|||
use command_palette::CommandInterceptResult;
|
||||
use editor::{SortLinesCaseInsensitive, SortLinesCaseSensitive};
|
||||
use editor::actions::{SortLinesCaseInsensitive, SortLinesCaseSensitive};
|
||||
use gpui::{impl_actions, Action, AppContext, ViewContext};
|
||||
use serde_derive::Deserialize;
|
||||
use workspace::{SaveIntent, Workspace};
|
||||
|
@ -204,25 +204,31 @@ pub fn command_interceptor(mut query: &str, _: &AppContext) -> Option<CommandInt
|
|||
|
||||
// quickfix / loclist (merged together for now)
|
||||
"cl" | "cli" | "clis" | "clist" => ("clist", diagnostics::Deploy.boxed_clone()),
|
||||
"cc" => ("cc", editor::Hover.boxed_clone()),
|
||||
"ll" => ("ll", editor::Hover.boxed_clone()),
|
||||
"cn" | "cne" | "cnex" | "cnext" => ("cnext", editor::GoToDiagnostic.boxed_clone()),
|
||||
"lne" | "lnex" | "lnext" => ("cnext", editor::GoToDiagnostic.boxed_clone()),
|
||||
"cc" => ("cc", editor::actions::Hover.boxed_clone()),
|
||||
"ll" => ("ll", editor::actions::Hover.boxed_clone()),
|
||||
"cn" | "cne" | "cnex" | "cnext" => ("cnext", editor::actions::GoToDiagnostic.boxed_clone()),
|
||||
"lne" | "lnex" | "lnext" => ("cnext", editor::actions::GoToDiagnostic.boxed_clone()),
|
||||
|
||||
"cpr" | "cpre" | "cprev" | "cprevi" | "cprevio" | "cpreviou" | "cprevious" => {
|
||||
("cprevious", editor::GoToPrevDiagnostic.boxed_clone())
|
||||
"cpr" | "cpre" | "cprev" | "cprevi" | "cprevio" | "cpreviou" | "cprevious" => (
|
||||
"cprevious",
|
||||
editor::actions::GoToPrevDiagnostic.boxed_clone(),
|
||||
),
|
||||
"cN" | "cNe" | "cNex" | "cNext" => {
|
||||
("cNext", editor::actions::GoToPrevDiagnostic.boxed_clone())
|
||||
}
|
||||
"cN" | "cNe" | "cNex" | "cNext" => ("cNext", editor::GoToPrevDiagnostic.boxed_clone()),
|
||||
"lp" | "lpr" | "lpre" | "lprev" | "lprevi" | "lprevio" | "lpreviou" | "lprevious" => {
|
||||
("lprevious", editor::GoToPrevDiagnostic.boxed_clone())
|
||||
"lp" | "lpr" | "lpre" | "lprev" | "lprevi" | "lprevio" | "lpreviou" | "lprevious" => (
|
||||
"lprevious",
|
||||
editor::actions::GoToPrevDiagnostic.boxed_clone(),
|
||||
),
|
||||
"lN" | "lNe" | "lNex" | "lNext" => {
|
||||
("lNext", editor::actions::GoToPrevDiagnostic.boxed_clone())
|
||||
}
|
||||
"lN" | "lNe" | "lNex" | "lNext" => ("lNext", editor::GoToPrevDiagnostic.boxed_clone()),
|
||||
|
||||
// modify the buffer (should accept [range])
|
||||
"j" | "jo" | "joi" | "join" => ("join", JoinLines.boxed_clone()),
|
||||
"d" | "de" | "del" | "dele" | "delet" | "delete" | "dl" | "dell" | "delel" | "deletl"
|
||||
| "deletel" | "dp" | "dep" | "delp" | "delep" | "deletp" | "deletep" => {
|
||||
("delete", editor::DeleteLine.boxed_clone())
|
||||
("delete", editor::actions::DeleteLine.boxed_clone())
|
||||
}
|
||||
"sor" | "sor " | "sort" | "sort " => ("sort", SortLinesCaseSensitive.boxed_clone()),
|
||||
"sor i" | "sort i" => ("sort i", SortLinesCaseInsensitive.boxed_clone()),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::Vim;
|
||||
use crate::{insert::NormalBefore, Vim};
|
||||
use editor::{Editor, EditorEvent};
|
||||
use gpui::{AppContext, Entity, EntityId, View, ViewContext, WindowContext};
|
||||
use gpui::{Action, AppContext, Entity, EntityId, View, ViewContext, WindowContext};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(|_, cx: &mut ViewContext<Editor>| {
|
||||
|
@ -34,8 +34,7 @@ fn focused(editor: View<Editor>, cx: &mut WindowContext) {
|
|||
|
||||
fn blurred(editor: View<Editor>, cx: &mut WindowContext) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
vim.workspace_state.recording = false;
|
||||
vim.workspace_state.recorded_actions.clear();
|
||||
vim.stop_recording_immediately(NormalBefore.boxed_clone());
|
||||
if let Some(previous_editor) = vim.active_editor.clone() {
|
||||
if previous_editor
|
||||
.upgrade()
|
||||
|
@ -69,7 +68,7 @@ fn released(entity_id: EntityId, cx: &mut AppContext) {
|
|||
mod test {
|
||||
use crate::{test::VimTestContext, Vim};
|
||||
use editor::Editor;
|
||||
use gpui::{Context, Entity};
|
||||
use gpui::{Context, Entity, VisualTestContext};
|
||||
use language::Buffer;
|
||||
|
||||
// regression test for blur called with a different active editor
|
||||
|
@ -82,11 +81,13 @@ mod test {
|
|||
let editor2 = cx
|
||||
.update(|cx| {
|
||||
window2.update(cx, |_, cx| {
|
||||
cx.activate_window();
|
||||
cx.focus_self();
|
||||
cx.view().clone()
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
cx.update(|cx| {
|
||||
let vim = Vim::read(cx);
|
||||
|
@ -101,4 +102,41 @@ mod test {
|
|||
editor1.handle_blur(cx);
|
||||
});
|
||||
}
|
||||
|
||||
// regression test for focus_in/focus_out being called on window activation
|
||||
#[gpui::test]
|
||||
async fn test_focus_across_windows(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
|
||||
let mut cx1 = VisualTestContext::from_window(cx.window, &cx);
|
||||
let editor1 = cx.editor.clone();
|
||||
|
||||
let buffer = cx.new_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n"));
|
||||
let (editor2, cx2) = cx.add_window_view(|cx| Editor::for_buffer(buffer, None, cx));
|
||||
|
||||
editor2.update(cx2, |_, cx| {
|
||||
cx.focus_self();
|
||||
cx.activate_window();
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
cx1.update(|cx| {
|
||||
assert_eq!(
|
||||
Vim::read(cx).active_editor.as_ref().unwrap().entity_id(),
|
||||
editor2.entity_id(),
|
||||
)
|
||||
});
|
||||
|
||||
cx1.update(|cx| {
|
||||
cx.activate_window();
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
cx.update(|cx| {
|
||||
assert_eq!(
|
||||
Vim::read(cx).active_editor.as_ref().unwrap().entity_id(),
|
||||
editor1.entity_id(),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{normal::repeat, state::Mode, Vim};
|
||||
use editor::{scroll::autoscroll::Autoscroll, Bias};
|
||||
use editor::{scroll::Autoscroll, Bias};
|
||||
use gpui::{actions, Action, ViewContext};
|
||||
use language::SelectionGoal;
|
||||
use workspace::Workspace;
|
||||
|
|
|
@ -4,12 +4,14 @@ use workspace::{item::ItemHandle, ui::prelude::*, StatusItemView};
|
|||
|
||||
use crate::{state::Mode, Vim};
|
||||
|
||||
/// The ModeIndicator displays the current mode in the status bar.
|
||||
pub struct ModeIndicator {
|
||||
pub mode: Option<Mode>,
|
||||
pub(crate) mode: Option<Mode>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl ModeIndicator {
|
||||
/// Construct a new mode indicator in this window.
|
||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
let _subscriptions = vec![
|
||||
cx.observe_global::<Vim>(|this, cx| this.update_mode(cx)),
|
||||
|
@ -26,24 +28,16 @@ impl ModeIndicator {
|
|||
|
||||
fn update_mode(&mut self, cx: &mut ViewContext<Self>) {
|
||||
// Vim doesn't exist in some tests
|
||||
if !cx.has_global::<Vim>() {
|
||||
let Some(vim) = cx.try_global::<Vim>() else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let vim = Vim::read(cx);
|
||||
if vim.enabled {
|
||||
self.mode = Some(vim.state().mode);
|
||||
} else {
|
||||
self.mode = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_mode(&mut self, mode: Mode, cx: &mut ViewContext<Self>) {
|
||||
if self.mode != Some(mode) {
|
||||
self.mode = Some(mode);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ModeIndicator {
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
use editor::{
|
||||
char_kind,
|
||||
display_map::{DisplaySnapshot, FoldPoint, ToDisplayPoint},
|
||||
movement::{self, find_boundary, find_preceding_boundary, FindRange, TextLayoutDetails},
|
||||
Bias, CharKind, DisplayPoint, ToOffset,
|
||||
Bias, DisplayPoint, ToOffset,
|
||||
};
|
||||
use gpui::{actions, impl_actions, px, ViewContext, WindowContext};
|
||||
use language::{Point, Selection, SelectionGoal};
|
||||
use language::{char_kind, CharKind, Point, Selection, SelectionGoal};
|
||||
use serde::Deserialize;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::{
|
||||
normal::normal_motion,
|
||||
state::{Mode, Operator},
|
||||
utils::coerce_punctuation,
|
||||
visual::visual_motion,
|
||||
Vim,
|
||||
};
|
||||
|
@ -712,7 +712,7 @@ fn next_word_end(
|
|||
}
|
||||
point = movement::find_boundary(map, point, FindRange::MultiLine, |left, right| {
|
||||
let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation);
|
||||
let right_kind = ccoerce_punctuation(har_kind(&scope, right), ignore_punctuation);
|
||||
let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation);
|
||||
|
||||
left_kind != right_kind && left_kind != CharKind::Whitespace
|
||||
});
|
||||
|
@ -952,14 +952,6 @@ pub(crate) fn next_line_end(
|
|||
end_of_line(map, false, point)
|
||||
}
|
||||
|
||||
fn coerce_punctuation(kind: CharKind, treat_punctuation_as_word: bool) -> Self {
|
||||
if treat_punctuation_as_word && kind == CharKind::Punctuation {
|
||||
CharKind::Word
|
||||
} else {
|
||||
kind
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ use crate::{
|
|||
Vim,
|
||||
};
|
||||
use collections::HashSet;
|
||||
use editor::scroll::autoscroll::Autoscroll;
|
||||
use editor::scroll::Autoscroll;
|
||||
use editor::{Bias, DisplayPoint};
|
||||
use gpui::{actions, ViewContext, WindowContext};
|
||||
use language::SelectionGoal;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use editor::scroll::autoscroll::Autoscroll;
|
||||
use editor::scroll::Autoscroll;
|
||||
use gpui::ViewContext;
|
||||
use language::{Bias, Point};
|
||||
use workspace::Workspace;
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
use crate::{motion::Motion, object::Object, state::Mode, utils::copy_selections_content, Vim};
|
||||
use crate::{
|
||||
motion::Motion,
|
||||
object::Object,
|
||||
state::Mode,
|
||||
utils::{coerce_punctuation, copy_selections_content},
|
||||
Vim,
|
||||
};
|
||||
use editor::{
|
||||
char_kind,
|
||||
display_map::DisplaySnapshot,
|
||||
movement::{self, FindRange, TextLayoutDetails},
|
||||
scroll::autoscroll::Autoscroll,
|
||||
CharKind, DisplayPoint,
|
||||
scroll::Autoscroll,
|
||||
DisplayPoint,
|
||||
};
|
||||
use gpui::WindowContext;
|
||||
use language::Selection;
|
||||
use language::{char_kind, CharKind, Selection};
|
||||
|
||||
pub fn change_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
|
||||
// Some motions ignore failure when switching to normal mode
|
||||
|
@ -103,9 +108,9 @@ fn expand_changed_word_selection(
|
|||
if in_word {
|
||||
selection.end =
|
||||
movement::find_boundary(map, selection.end, FindRange::MultiLine, |left, right| {
|
||||
let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
|
||||
let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation);
|
||||
let right_kind =
|
||||
char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
|
||||
coerce_punctuation(char_kind(&scope, right), ignore_punctuation);
|
||||
|
||||
left_kind != right_kind && left_kind != CharKind::Whitespace
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Bias};
|
||||
use editor::{display_map::ToDisplayPoint, scroll::Autoscroll, Bias};
|
||||
use gpui::WindowContext;
|
||||
use language::Point;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use editor::{scroll::autoscroll::Autoscroll, MultiBufferSnapshot, ToOffset, ToPoint};
|
||||
use editor::{scroll::Autoscroll, MultiBufferSnapshot, ToOffset, ToPoint};
|
||||
use gpui::{impl_actions, ViewContext, WindowContext};
|
||||
use language::{Bias, Point};
|
||||
use serde::Deserialize;
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use std::{borrow::Cow, cmp};
|
||||
|
||||
use editor::{
|
||||
display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, ClipboardSelection,
|
||||
DisplayPoint,
|
||||
display_map::ToDisplayPoint, movement, scroll::Autoscroll, ClipboardSelection, DisplayPoint,
|
||||
};
|
||||
use gpui::{impl_actions, ViewContext};
|
||||
use language::{Bias, SelectionGoal};
|
||||
|
|
|
@ -12,7 +12,7 @@ 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.partial_eq(&**action) {
|
||||
if editor::actions::ShowCharacterPalette.partial_eq(&**action) {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
|
@ -152,7 +152,7 @@ pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
|
|||
|
||||
let mut count = Vim::read(cx).workspace_state.recorded_count.unwrap_or(1);
|
||||
|
||||
// if we came from insert mode we're just doing repititions 2 onwards.
|
||||
// if we came from insert mode we're just doing repetitions 2 onwards.
|
||||
if from_insert_mode {
|
||||
count -= 1;
|
||||
new_actions[0] = actions[0].clone();
|
||||
|
@ -493,4 +493,17 @@ mod test {
|
|||
cx.simulate_keystrokes(["escape"]);
|
||||
cx.assert_state("ˇjhello\n", Mode::Normal);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_repeat_over_blur(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.set_shared_state("ˇhello hello hello\n").await;
|
||||
cx.simulate_shared_keystrokes(["c", "f", "o", "x", "escape"])
|
||||
.await;
|
||||
cx.assert_shared_state("ˇx hello hello\n").await;
|
||||
cx.simulate_shared_keystrokes([":", "escape"]).await;
|
||||
cx.simulate_shared_keystrokes(["."]).await;
|
||||
cx.assert_shared_state("ˇx hello\n").await;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::Vim;
|
||||
use editor::{
|
||||
display_map::ToDisplayPoint,
|
||||
scroll::{scroll_amount::ScrollAmount, VERTICAL_SCROLL_MARGIN},
|
||||
scroll::{ScrollAmount, VERTICAL_SCROLL_MARGIN},
|
||||
DisplayPoint, Editor,
|
||||
};
|
||||
use gpui::{actions, ViewContext};
|
||||
|
|
|
@ -278,7 +278,7 @@ fn parse_replace_all(query: &str) -> Replacement {
|
|||
return Replacement::default();
|
||||
}
|
||||
|
||||
let Some(delimeter) = chars.next() else {
|
||||
let Some(delimiter) = chars.next() else {
|
||||
return Replacement::default();
|
||||
};
|
||||
|
||||
|
@ -301,13 +301,13 @@ fn parse_replace_all(query: &str) -> Replacement {
|
|||
buffer.push('$')
|
||||
// unescape escaped parens
|
||||
} else if phase == 0 && c == '(' || c == ')' {
|
||||
} else if c != delimeter {
|
||||
} else if c != delimiter {
|
||||
buffer.push('\\')
|
||||
}
|
||||
buffer.push(c)
|
||||
} else if c == '\\' {
|
||||
escaped = true;
|
||||
} else if c == delimeter {
|
||||
} else if c == delimiter {
|
||||
if phase == 0 {
|
||||
buffer = &mut replacement;
|
||||
phase = 1;
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use editor::{
|
||||
char_kind,
|
||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||
movement::{self, FindRange},
|
||||
Bias, CharKind, DisplayPoint,
|
||||
Bias, DisplayPoint,
|
||||
};
|
||||
use gpui::{actions, impl_actions, ViewContext, WindowContext};
|
||||
use language::Selection;
|
||||
use language::{char_kind, CharKind, Selection};
|
||||
use serde::Deserialize;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::{motion::right, normal::normal_object, state::Mode, visual::visual_object, Vim};
|
||||
use crate::{
|
||||
motion::right, normal::normal_object, state::Mode, utils::coerce_punctuation,
|
||||
visual::visual_object, Vim,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum Object {
|
||||
|
@ -213,14 +215,14 @@ fn in_word(
|
|||
right(map, relative_to, 1),
|
||||
movement::FindRange::SingleLine,
|
||||
|left, right| {
|
||||
char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
|
||||
!= char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
|
||||
coerce_punctuation(char_kind(&scope, left), ignore_punctuation)
|
||||
!= coerce_punctuation(char_kind(&scope, right), ignore_punctuation)
|
||||
},
|
||||
);
|
||||
|
||||
let end = movement::find_boundary(map, relative_to, FindRange::SingleLine, |left, right| {
|
||||
char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
|
||||
!= char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
|
||||
coerce_punctuation(char_kind(&scope, left), ignore_punctuation)
|
||||
!= coerce_punctuation(char_kind(&scope, right), ignore_punctuation)
|
||||
});
|
||||
|
||||
Some(start..end)
|
||||
|
@ -282,15 +284,15 @@ fn around_next_word(
|
|||
right(map, relative_to, 1),
|
||||
FindRange::SingleLine,
|
||||
|left, right| {
|
||||
char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
|
||||
!= char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
|
||||
coerce_punctuation(char_kind(&scope, left), ignore_punctuation)
|
||||
!= coerce_punctuation(char_kind(&scope, right), ignore_punctuation)
|
||||
},
|
||||
);
|
||||
|
||||
let mut word_found = false;
|
||||
let end = movement::find_boundary(map, relative_to, FindRange::MultiLine, |left, right| {
|
||||
let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
|
||||
let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
|
||||
let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation);
|
||||
let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation);
|
||||
|
||||
let found = (word_found && left_kind != right_kind) || right == '\n' && left == '\n';
|
||||
|
||||
|
|
|
@ -71,6 +71,30 @@ async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
|
|||
assert_eq!(cx.mode(), Mode::Normal);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_cancel_selection(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
|
||||
cx.set_state(
|
||||
indoc! {"The quick brown fox juˇmps over the lazy dog"},
|
||||
Mode::Normal,
|
||||
);
|
||||
// jumps
|
||||
cx.simulate_keystrokes(["v", "l", "l"]);
|
||||
cx.assert_editor_state("The quick brown fox ju«mpsˇ» over the lazy dog");
|
||||
|
||||
cx.simulate_keystrokes(["escape"]);
|
||||
cx.assert_editor_state("The quick brown fox jumpˇs over the lazy dog");
|
||||
|
||||
// go back to the same selection state
|
||||
cx.simulate_keystrokes(["v", "h", "h"]);
|
||||
cx.assert_editor_state("The quick brown fox ju«ˇmps» over the lazy dog");
|
||||
|
||||
// Ctrl-[ should behave like Esc
|
||||
cx.simulate_keystrokes(["ctrl-["]);
|
||||
cx.assert_editor_state("The quick brown fox juˇmps over the lazy dog");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
|
|
|
@ -62,6 +62,8 @@ pub struct NeovimBackedTestContext {
|
|||
|
||||
impl NeovimBackedTestContext {
|
||||
pub async fn new(cx: &mut gpui::TestAppContext) -> NeovimBackedTestContext {
|
||||
#[cfg(feature = "neovim")]
|
||||
cx.executor().allow_parking();
|
||||
// rust stores the name of the test on the current thread.
|
||||
// We use this to automatically name a file that will store
|
||||
// the neovim connection's requests/responses so that we can
|
||||
|
|
|
@ -359,7 +359,7 @@ impl NeovimConnection {
|
|||
// to add one to the end in visual mode.
|
||||
match mode {
|
||||
Some(Mode::VisualBlock) if selection_row != cursor_row => {
|
||||
// in zed we fake a block selecrtion by using multiple cursors (one per line)
|
||||
// in zed we fake a block selection by using multiple cursors (one per line)
|
||||
// this code emulates that.
|
||||
// to deal with casees where the selection is not perfectly rectangular we extract
|
||||
// the content of the selection via the "a register to get the shape correctly.
|
||||
|
|
|
@ -6,7 +6,7 @@ use editor::test::{
|
|||
use futures::Future;
|
||||
use gpui::{Context, View, VisualContext};
|
||||
use lsp::request;
|
||||
use search::BufferSearchBar;
|
||||
use search::{project_search::ProjectSearchBar, BufferSearchBar};
|
||||
|
||||
use crate::{state::Operator, *};
|
||||
|
||||
|
@ -17,7 +17,6 @@ pub struct VimTestContext {
|
|||
impl VimTestContext {
|
||||
pub fn init(cx: &mut gpui::TestAppContext) {
|
||||
if cx.has_global::<Vim>() {
|
||||
dbg!("OOPS");
|
||||
return;
|
||||
}
|
||||
cx.update(|cx| {
|
||||
|
@ -59,9 +58,9 @@ impl VimTestContext {
|
|||
pane.toolbar().update(cx, |toolbar, cx| {
|
||||
let buffer_search_bar = cx.new_view(BufferSearchBar::new);
|
||||
toolbar.add_item(buffer_search_bar, cx);
|
||||
// todo!();
|
||||
// let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
|
||||
// toolbar.add_item(project_search_bar, cx);
|
||||
|
||||
let project_search_bar = cx.new_view(|_| ProjectSearchBar::new());
|
||||
toolbar.add_item(project_search_bar, cx);
|
||||
})
|
||||
});
|
||||
workspace.status_bar().update(cx, |status_bar, cx| {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use editor::{ClipboardSelection, Editor};
|
||||
use gpui::{AppContext, ClipboardItem};
|
||||
use language::Point;
|
||||
use language::{CharKind, Point};
|
||||
|
||||
pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut AppContext) {
|
||||
let selections = editor.selections.all_adjusted(cx);
|
||||
|
@ -48,3 +48,11 @@ pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut App
|
|||
|
||||
cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
|
||||
}
|
||||
|
||||
pub fn coerce_punctuation(kind: CharKind, treat_punctuation_as_word: bool) -> CharKind {
|
||||
if treat_punctuation_as_word && kind == CharKind::Punctuation {
|
||||
CharKind::Word
|
||||
} else {
|
||||
kind
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Vim support for Zed.
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
|
@ -38,12 +40,18 @@ use crate::state::ReplayableAction;
|
|||
/// Default: false
|
||||
pub struct VimModeSetting(pub bool);
|
||||
|
||||
/// An Action to Switch between modes
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
pub struct SwitchMode(pub Mode);
|
||||
|
||||
/// PushOperator is used to put vim into a "minor" mode,
|
||||
/// where it's waiting for a specific next set of keystrokes.
|
||||
/// For example 'd' needs a motion to complete.
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
pub struct PushOperator(pub Operator);
|
||||
|
||||
/// Number is used to manage vim's count. Pushing a digit
|
||||
/// multiplis the current value by 10 and adds the digit.
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
struct Number(usize);
|
||||
|
||||
|
@ -51,11 +59,13 @@ 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, [SwitchMode, PushOperator, Number]);
|
||||
|
||||
/// Initializes the `vim` crate.
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.set_global(Vim::default());
|
||||
VimModeSetting::register(cx);
|
||||
|
@ -119,6 +129,7 @@ fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
|||
visual::register(workspace, cx);
|
||||
}
|
||||
|
||||
/// Registers a keystroke observer to observe keystrokes for the Vim integration.
|
||||
pub fn observe_keystrokes(cx: &mut WindowContext) {
|
||||
cx.observe_keystrokes(|keystroke_event, cx| {
|
||||
if let Some(action) = keystroke_event
|
||||
|
@ -160,6 +171,7 @@ pub fn observe_keystrokes(cx: &mut WindowContext) {
|
|||
.detach()
|
||||
}
|
||||
|
||||
/// The state pertaining to Vim mode. Stored as a global.
|
||||
#[derive(Default)]
|
||||
pub struct Vim {
|
||||
active_editor: Option<WeakView<Editor>>,
|
||||
|
@ -251,6 +263,8 @@ impl Vim {
|
|||
Some(editor.update(cx, update))
|
||||
}
|
||||
|
||||
/// When doing an action that modifies the buffer, we start recording so that `.`
|
||||
/// will replay the action.
|
||||
pub fn start_recording(&mut self, cx: &mut WindowContext) {
|
||||
if !self.workspace_state.replaying {
|
||||
self.workspace_state.recording = true;
|
||||
|
@ -295,12 +309,19 @@ impl Vim {
|
|||
}
|
||||
}
|
||||
|
||||
/// When finishing an action that modifies the buffer, stop recording.
|
||||
/// as you usually call this within a keystroke handler we also ensure that
|
||||
/// the current action is recorded.
|
||||
pub fn stop_recording(&mut self) {
|
||||
if self.workspace_state.recording {
|
||||
self.workspace_state.stop_recording_after_next_action = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops recording actions immediately rather than waiting until after the
|
||||
/// next action to stop recording.
|
||||
///
|
||||
/// This doesn't include the current action.
|
||||
pub fn stop_recording_immediately(&mut self, action: Box<dyn Action>) {
|
||||
if self.workspace_state.recording {
|
||||
self.workspace_state
|
||||
|
@ -311,6 +332,7 @@ impl Vim {
|
|||
}
|
||||
}
|
||||
|
||||
/// Explicitly record one action (equivalents to start_recording and stop_recording)
|
||||
pub fn record_current_action(&mut self, cx: &mut WindowContext) {
|
||||
self.start_recording(cx);
|
||||
self.stop_recording();
|
||||
|
@ -516,6 +538,7 @@ impl Vim {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the state of the active editor.
|
||||
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.entity_id()) {
|
||||
|
@ -526,6 +549,7 @@ impl Vim {
|
|||
&self.default_state
|
||||
}
|
||||
|
||||
/// Updates the state of the active editor.
|
||||
pub fn update_state<T>(&mut self, func: impl FnOnce(&mut EditorState) -> T) -> T {
|
||||
let mut state = self.state().clone();
|
||||
let ret = func(&mut state);
|
||||
|
|
|
@ -5,7 +5,7 @@ use collections::HashMap;
|
|||
use editor::{
|
||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||
movement,
|
||||
scroll::autoscroll::Autoscroll,
|
||||
scroll::Autoscroll,
|
||||
Bias, DisplayPoint, Editor,
|
||||
};
|
||||
use gpui::{actions, ViewContext, WindowContext};
|
||||
|
@ -201,15 +201,13 @@ pub fn visual_block_motion(
|
|||
let mut row = tail.row();
|
||||
|
||||
loop {
|
||||
let layed_out_line = map.layout_row(row, &text_layout_details);
|
||||
let laid_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,
|
||||
);
|
||||
let mut end = DisplayPoint::new(
|
||||
row,
|
||||
layed_out_line.closest_index_for_x(positions.end) as u32,
|
||||
laid_out_line.closest_index_for_x(positions.start) as u32,
|
||||
);
|
||||
let mut end =
|
||||
DisplayPoint::new(row, laid_out_line.closest_index_for_x(positions.end) as u32);
|
||||
if end <= start {
|
||||
if start.column() == map.line_len(start.row()) {
|
||||
end = start;
|
||||
|
@ -218,7 +216,7 @@ pub fn visual_block_motion(
|
|||
}
|
||||
}
|
||||
|
||||
if positions.start <= layed_out_line.width {
|
||||
if positions.start <= laid_out_line.width {
|
||||
let selection = Selection {
|
||||
id: s.new_selection_id(),
|
||||
start: start.to_point(map),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue