Merge branch 'main' into multi-server-completions-tailwind

This commit is contained in:
Julia 2023-08-30 22:41:12 -04:00
commit ff3865a4ad
427 changed files with 43123 additions and 12861 deletions

View file

@ -13,15 +13,15 @@ pub fn change_case(_: &mut Workspace, _: &ChangeCase, cx: &mut ViewContext<Works
let mut cursor_positions = Vec::new();
let snapshot = editor.buffer().read(cx).snapshot(cx);
for selection in editor.selections.all::<Point>(cx) {
match vim.state.mode {
Mode::Visual { line: true } => {
match vim.state().mode {
Mode::VisualLine => {
let start = Point::new(selection.start.row, 0);
let end =
Point::new(selection.end.row, snapshot.line_len(selection.end.row));
ranges.push(start..end);
cursor_positions.push(start..start);
}
Mode::Visual { line: false } => {
Mode::Visual | Mode::VisualBlock => {
ranges.push(selection.start..selection.end);
cursor_positions.push(selection.start..selection.start);
}

View file

@ -10,7 +10,11 @@ pub fn change_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &m
// Some motions ignore failure when switching to normal mode
let mut motion_succeeded = matches!(
motion,
Motion::Left | Motion::Right | Motion::EndOfLine | Motion::Backspace | Motion::StartOfLine
Motion::Left
| Motion::Right
| Motion::EndOfLine { .. }
| Motion::Backspace
| Motion::StartOfLine { .. }
);
vim.update_active_editor(cx, |editor, cx| {
editor.transact(cx, |editor, cx| {

View file

@ -0,0 +1,468 @@
use std::{borrow::Cow, cmp};
use editor::{
display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, ClipboardSelection,
DisplayPoint,
};
use gpui::{impl_actions, AppContext, ViewContext};
use language::{Bias, SelectionGoal};
use serde::Deserialize;
use workspace::Workspace;
use crate::{state::Mode, utils::copy_selections_content, Vim};
#[derive(Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct Paste {
#[serde(default)]
before: bool,
#[serde(default)]
preserve_clipboard: bool,
}
impl_actions!(vim, [Paste]);
pub(crate) fn init(cx: &mut AppContext) {
cx.add_action(paste);
}
fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {
Vim::update(cx, |vim, cx| {
vim.update_active_editor(cx, |editor, cx| {
editor.transact(cx, |editor, cx| {
editor.set_clip_at_line_ends(false, cx);
let Some(item) = cx.read_from_clipboard() else {
return;
};
let clipboard_text = Cow::Borrowed(item.text());
if clipboard_text.is_empty() {
return;
}
if !action.preserve_clipboard && vim.state().mode.is_visual() {
copy_selections_content(editor, vim.state().mode == Mode::VisualLine, cx);
}
// if we are copying from multi-cursor (of visual block mode), we want
// to
let clipboard_selections =
item.metadata::<Vec<ClipboardSelection>>()
.filter(|clipboard_selections| {
clipboard_selections.len() > 1 && vim.state().mode != Mode::VisualLine
});
let (display_map, current_selections) = editor.selections.all_adjusted_display(cx);
// unlike zed, if you have a multi-cursor selection from vim block mode,
// pasting it will paste it on subsequent lines, even if you don't yet
// have a cursor there.
let mut selections_to_process = Vec::new();
let mut i = 0;
while i < current_selections.len() {
selections_to_process
.push((current_selections[i].start..current_selections[i].end, true));
i += 1;
}
if let Some(clipboard_selections) = clipboard_selections.as_ref() {
let left = current_selections
.iter()
.map(|selection| cmp::min(selection.start.column(), selection.end.column()))
.min()
.unwrap();
let mut row = current_selections.last().unwrap().end.row() + 1;
while i < clipboard_selections.len() {
let cursor =
display_map.clip_point(DisplayPoint::new(row, left), Bias::Left);
selections_to_process.push((cursor..cursor, false));
i += 1;
row += 1;
}
}
let first_selection_indent_column =
clipboard_selections.as_ref().and_then(|zed_selections| {
zed_selections
.first()
.map(|selection| selection.first_line_indent)
});
let before = action.before || vim.state().mode == Mode::VisualLine;
let mut edits = Vec::new();
let mut new_selections = Vec::new();
let mut original_indent_columns = Vec::new();
let mut start_offset = 0;
for (ix, (selection, preserve)) in selections_to_process.iter().enumerate() {
let (mut to_insert, original_indent_column) =
if let Some(clipboard_selections) = &clipboard_selections {
if let Some(clipboard_selection) = clipboard_selections.get(ix) {
let end_offset = start_offset + clipboard_selection.len;
let text = clipboard_text[start_offset..end_offset].to_string();
start_offset = end_offset + 1;
(text, Some(clipboard_selection.first_line_indent))
} else {
("".to_string(), first_selection_indent_column)
}
} else {
(clipboard_text.to_string(), first_selection_indent_column)
};
let line_mode = to_insert.ends_with("\n");
let is_multiline = to_insert.contains("\n");
if line_mode && !before {
if selection.is_empty() {
to_insert =
"\n".to_owned() + &to_insert[..to_insert.len() - "\n".len()];
} else {
to_insert = "\n".to_owned() + &to_insert;
}
} else if !line_mode && vim.state().mode == Mode::VisualLine {
to_insert = to_insert + "\n";
}
let display_range = if !selection.is_empty() {
selection.start..selection.end
} else if line_mode {
let point = if before {
movement::line_beginning(&display_map, selection.start, false)
} else {
movement::line_end(&display_map, selection.start, false)
};
point..point
} else {
let point = if before {
selection.start
} else {
movement::saturating_right(&display_map, selection.start)
};
point..point
};
let point_range = display_range.start.to_point(&display_map)
..display_range.end.to_point(&display_map);
let anchor = if is_multiline || vim.state().mode == Mode::VisualLine {
display_map.buffer_snapshot.anchor_before(point_range.start)
} else {
display_map.buffer_snapshot.anchor_after(point_range.end)
};
if *preserve {
new_selections.push((anchor, line_mode, is_multiline));
}
edits.push((point_range, to_insert));
original_indent_columns.extend(original_indent_column);
}
editor.edit_with_block_indent(edits, original_indent_columns, cx);
// in line_mode vim will insert the new text on the next (or previous if before) line
// and put the cursor on the first non-blank character of the first inserted line (or at the end if the first line is blank).
// otherwise vim will insert the next text at (or before) the current cursor position,
// the cursor will go to the last (or first, if is_multiline) inserted character.
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.replace_cursors_with(|map| {
let mut cursors = Vec::new();
for (anchor, line_mode, is_multiline) in &new_selections {
let mut cursor = anchor.to_display_point(map);
if *line_mode {
if !before {
cursor =
movement::down(map, cursor, SelectionGoal::None, false).0;
}
cursor = movement::indented_line_beginning(map, cursor, true);
} else if !is_multiline {
cursor = movement::saturating_left(map, cursor)
}
cursors.push(cursor);
if vim.state().mode == Mode::VisualBlock {
break;
}
}
cursors
});
})
});
});
vim.switch_mode(Mode::Normal, true, cx);
});
}
#[cfg(test)]
mod test {
use crate::{
state::Mode,
test::{NeovimBackedTestContext, VimTestContext},
};
use indoc::indoc;
#[gpui::test]
async fn test_paste(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
// single line
cx.set_shared_state(indoc! {"
The quick brown
fox ˇjumps over
the lazy dog"})
.await;
cx.simulate_shared_keystrokes(["v", "w", "y"]).await;
cx.assert_shared_clipboard("jumps o").await;
cx.set_shared_state(indoc! {"
The quick brown
fox jumps oveˇr
the lazy dog"})
.await;
cx.simulate_shared_keystroke("p").await;
cx.assert_shared_state(indoc! {"
The quick brown
fox jumps overjumps ˇo
the lazy dog"})
.await;
cx.set_shared_state(indoc! {"
The quick brown
fox jumps oveˇr
the lazy dog"})
.await;
cx.simulate_shared_keystroke("shift-p").await;
cx.assert_shared_state(indoc! {"
The quick brown
fox jumps ovejumps ˇor
the lazy dog"})
.await;
// line mode
cx.set_shared_state(indoc! {"
The quick brown
fox juˇmps over
the lazy dog"})
.await;
cx.simulate_shared_keystrokes(["d", "d"]).await;
cx.assert_shared_clipboard("fox jumps over\n").await;
cx.assert_shared_state(indoc! {"
The quick brown
the laˇzy dog"})
.await;
cx.simulate_shared_keystroke("p").await;
cx.assert_shared_state(indoc! {"
The quick brown
the lazy dog
ˇfox jumps over"})
.await;
cx.simulate_shared_keystrokes(["k", "shift-p"]).await;
cx.assert_shared_state(indoc! {"
The quick brown
ˇfox jumps over
the lazy dog
fox jumps over"})
.await;
// multiline, cursor to first character of pasted text.
cx.set_shared_state(indoc! {"
The quick brown
fox jumps ˇover
the lazy dog"})
.await;
cx.simulate_shared_keystrokes(["v", "j", "y"]).await;
cx.assert_shared_clipboard("over\nthe lazy do").await;
cx.simulate_shared_keystroke("p").await;
cx.assert_shared_state(indoc! {"
The quick brown
fox jumps oˇover
the lazy dover
the lazy dog"})
.await;
cx.simulate_shared_keystrokes(["u", "shift-p"]).await;
cx.assert_shared_state(indoc! {"
The quick brown
fox jumps ˇover
the lazy doover
the lazy dog"})
.await;
}
#[gpui::test]
async fn test_paste_visual(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
// copy in visual mode
cx.set_shared_state(indoc! {"
The quick brown
fox jˇumps over
the lazy dog"})
.await;
cx.simulate_shared_keystrokes(["v", "i", "w", "y"]).await;
cx.assert_shared_state(indoc! {"
The quick brown
fox ˇjumps over
the lazy dog"})
.await;
// paste in visual mode
cx.simulate_shared_keystrokes(["w", "v", "i", "w", "p"])
.await;
cx.assert_shared_state(indoc! {"
The quick brown
fox jumps jumpˇs
the lazy dog"})
.await;
cx.assert_shared_clipboard("over").await;
// paste in visual line mode
cx.simulate_shared_keystrokes(["up", "shift-v", "shift-p"])
.await;
cx.assert_shared_state(indoc! {"
ˇover
fox jumps jumps
the lazy dog"})
.await;
cx.assert_shared_clipboard("over").await;
// paste in visual block mode
cx.simulate_shared_keystrokes(["ctrl-v", "down", "down", "p"])
.await;
cx.assert_shared_state(indoc! {"
oveˇrver
overox jumps jumps
overhe lazy dog"})
.await;
// copy in visual line mode
cx.set_shared_state(indoc! {"
The quick brown
fox juˇmps over
the lazy dog"})
.await;
cx.simulate_shared_keystrokes(["shift-v", "d"]).await;
cx.assert_shared_state(indoc! {"
The quick brown
the laˇzy dog"})
.await;
// paste in visual mode
cx.simulate_shared_keystrokes(["v", "i", "w", "p"]).await;
cx.assert_shared_state(
&indoc! {"
The quick brown
the_
ˇfox jumps over
_dog"}
.replace("_", " "), // Hack for trailing whitespace
)
.await;
cx.assert_shared_clipboard("lazy").await;
cx.set_shared_state(indoc! {"
The quick brown
fox juˇmps over
the lazy dog"})
.await;
cx.simulate_shared_keystrokes(["shift-v", "d"]).await;
cx.assert_shared_state(indoc! {"
The quick brown
the laˇzy dog"})
.await;
// paste in visual line mode
cx.simulate_shared_keystrokes(["k", "shift-v", "p"]).await;
cx.assert_shared_state(indoc! {"
ˇfox jumps over
the lazy dog"})
.await;
cx.assert_shared_clipboard("The quick brown\n").await;
}
#[gpui::test]
async fn test_paste_visual_block(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
// copy in visual block mode
cx.set_shared_state(indoc! {"
The ˇquick brown
fox jumps over
the lazy dog"})
.await;
cx.simulate_shared_keystrokes(["ctrl-v", "2", "j", "y"])
.await;
cx.assert_shared_clipboard("q\nj\nl").await;
cx.simulate_shared_keystrokes(["p"]).await;
cx.assert_shared_state(indoc! {"
The qˇquick brown
fox jjumps over
the llazy dog"})
.await;
cx.simulate_shared_keystrokes(["v", "i", "w", "shift-p"])
.await;
cx.assert_shared_state(indoc! {"
The ˇq brown
fox jjjumps over
the lllazy dog"})
.await;
cx.simulate_shared_keystrokes(["v", "i", "w", "shift-p"])
.await;
cx.set_shared_state(indoc! {"
The ˇquick brown
fox jumps over
the lazy dog"})
.await;
cx.simulate_shared_keystrokes(["ctrl-v", "j", "y"]).await;
cx.assert_shared_clipboard("q\nj").await;
cx.simulate_shared_keystrokes(["l", "ctrl-v", "2", "j", "shift-p"])
.await;
cx.assert_shared_state(indoc! {"
The qˇqick brown
fox jjmps over
the lzy dog"})
.await;
cx.simulate_shared_keystrokes(["shift-v", "p"]).await;
cx.assert_shared_state(indoc! {"
ˇq
j
fox jjmps over
the lzy dog"})
.await;
}
#[gpui::test]
async fn test_paste_indent(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new_typescript(cx).await;
cx.set_state(
indoc! {"
class A {ˇ
}
"},
Mode::Normal,
);
cx.simulate_keystrokes(["o", "a", "(", ")", "{", "escape"]);
cx.assert_state(
indoc! {"
class A {
a()ˇ{}
}
"},
Mode::Normal,
);
// cursor goes to the first non-blank character in the line;
cx.simulate_keystrokes(["y", "y", "p"]);
cx.assert_state(
indoc! {"
class A {
a(){}
ˇa(){}
}
"},
Mode::Normal,
);
// indentation is preserved when pasting
cx.simulate_keystrokes(["u", "shift-v", "up", "y", "shift-p"]);
cx.assert_state(
indoc! {"
ˇclass A {
a(){}
class A {
a(){}
}
"},
Mode::Normal,
);
}
}

View file

@ -1,7 +1,9 @@
use std::cmp::Ordering;
use crate::Vim;
use editor::{display_map::ToDisplayPoint, scroll::scroll_amount::ScrollAmount, Editor};
use editor::{
display_map::ToDisplayPoint,
scroll::{scroll_amount::ScrollAmount, VERTICAL_SCROLL_MARGIN},
DisplayPoint, Editor,
};
use gpui::{actions, AppContext, ViewContext};
use language::Bias;
use workspace::Workspace;
@ -53,13 +55,9 @@ fn scroll(cx: &mut ViewContext<Workspace>, by: fn(c: Option<f32>) -> ScrollAmoun
fn scroll_editor(editor: &mut Editor, amount: &ScrollAmount, cx: &mut ViewContext<Editor>) {
let should_move_cursor = editor.newest_selection_on_screen(cx).is_eq();
editor.scroll_screen(amount, cx);
if should_move_cursor {
let selection_ordering = editor.newest_selection_on_screen(cx);
if selection_ordering.is_eq() {
return;
}
let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
visible_rows as u32
} else {
@ -69,21 +67,19 @@ fn scroll_editor(editor: &mut Editor, amount: &ScrollAmount, cx: &mut ViewContex
let top_anchor = editor.scroll_manager.anchor().anchor;
editor.change_selections(None, cx, |s| {
s.replace_cursors_with(|snapshot| {
let mut new_point = top_anchor.to_display_point(&snapshot);
s.move_heads_with(|map, head, goal| {
let top = top_anchor.to_display_point(map);
let min_row = top.row() + VERTICAL_SCROLL_MARGIN as u32;
let max_row = top.row() + visible_rows - VERTICAL_SCROLL_MARGIN as u32 - 1;
match selection_ordering {
Ordering::Less => {
new_point = snapshot.clip_point(new_point, Bias::Right);
}
Ordering::Greater => {
*new_point.row_mut() += visible_rows - 1;
new_point = snapshot.clip_point(new_point, Bias::Left);
}
Ordering::Equal => unreachable!(),
}
vec![new_point]
let new_head = if head.row() < min_row {
map.clip_point(DisplayPoint::new(min_row, head.column()), Bias::Left)
} else if head.row() > max_row {
map.clip_point(DisplayPoint::new(max_row, head.column()), Bias::Left)
} else {
head
};
(new_head, goal)
})
});
}

View file

@ -1,5 +1,5 @@
use gpui::{actions, impl_actions, AppContext, ViewContext};
use search::{buffer_search, BufferSearchBar, SearchOptions};
use search::{buffer_search, BufferSearchBar, SearchMode, SearchOptions};
use serde_derive::Deserialize;
use workspace::{searchable::Direction, Pane, Workspace};
@ -65,15 +65,13 @@ fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Works
cx.focus_self();
if query.is_empty() {
search_bar.set_search_options(
SearchOptions::CASE_SENSITIVE | SearchOptions::REGEX,
cx,
);
search_bar.set_search_options(SearchOptions::CASE_SENSITIVE, cx);
search_bar.activate_search_mode(SearchMode::Regex, cx);
}
vim.state.search = SearchState {
vim.workspace_state.search = SearchState {
direction,
count,
initial_query: query,
initial_query: query.clone(),
};
});
}
@ -83,7 +81,7 @@ 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>) {
Vim::update(cx, |vim, _| vim.state.search = Default::default());
Vim::update(cx, |vim, _| vim.workspace_state.search = Default::default());
cx.propagate_action();
}
@ -93,8 +91,9 @@ fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewConte
pane.update(cx, |pane, cx| {
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
search_bar.update(cx, |search_bar, cx| {
let state = &mut vim.state.search;
let state = &mut vim.workspace_state.search;
let mut count = state.count;
let direction = state.direction;
// in the case that the query has changed, the search bar
// will have selected the next match already.
@ -103,8 +102,8 @@ fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewConte
{
count = count.saturating_sub(1)
}
search_bar.select_match(state.direction, count, cx);
state.count = 1;
search_bar.select_match(direction, count, cx);
search_bar.focus_editor(&Default::default(), cx);
});
}

View file

@ -4,9 +4,9 @@ use language::Point;
use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim};
pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
let line_mode = vim.state.mode == Mode::Visual { line: true };
vim.switch_mode(Mode::Insert, true, cx);
let line_mode = vim.state().mode == Mode::VisualLine;
vim.update_active_editor(cx, |editor, cx| {
editor.set_clip_at_line_ends(false, cx);
editor.transact(cx, |editor, cx| {
editor.change_selections(None, cx, |s| {
s.move_with(|map, selection| {
@ -15,7 +15,10 @@ pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
}
if line_mode {
Motion::CurrentLine.expand_selection(map, selection, None, false);
if let Some((point, _)) = Motion::FirstNonWhitespace.move_point(
if let Some((point, _)) = (Motion::FirstNonWhitespace {
display_lines: false,
})
.move_point(
map,
selection.start,
selection.goal,
@ -32,6 +35,7 @@ pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
editor.edit(edits, cx);
});
});
vim.switch_mode(Mode::Insert, true, cx);
}
#[cfg(test)]
@ -52,7 +56,7 @@ mod test {
cx.assert_editor_state("xˇbc\n");
// supports a selection
cx.set_state(indoc! {"a«bcˇ»\n"}, Mode::Visual { line: false });
cx.set_state(indoc! {"a«bcˇ»\n"}, Mode::Visual);
cx.assert_editor_state("a«bcˇ»\n");
cx.simulate_keystrokes(["s", "x"]);
cx.assert_editor_state("axˇ\n");