mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-10 05:37:29 +03:00
Merge pull request #2186 from zed-industries/better-vim-matching-motion
Better vim matching motion
This commit is contained in:
commit
ac3e8f61ef
@ -77,14 +77,14 @@ use std::{
|
||||
cmp::{self, Ordering, Reverse},
|
||||
mem,
|
||||
num::NonZeroU32,
|
||||
ops::{Deref, DerefMut, Range, RangeInclusive},
|
||||
ops::{Deref, DerefMut, Range},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
pub use sum_tree::Bias;
|
||||
use theme::{DiagnosticStyle, Theme};
|
||||
use util::{post_inc, ResultExt, TryFutureExt};
|
||||
use util::{post_inc, ResultExt, TryFutureExt, RangeExt};
|
||||
use workspace::{ItemNavHistory, ViewId, Workspace, WorkspaceId};
|
||||
|
||||
use crate::git::diff_hunk_to_display;
|
||||
@ -6993,21 +6993,6 @@ pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str
|
||||
.flat_map(|word| word.split_inclusive('_'))
|
||||
}
|
||||
|
||||
trait RangeExt<T> {
|
||||
fn sorted(&self) -> Range<T>;
|
||||
fn to_inclusive(&self) -> RangeInclusive<T>;
|
||||
}
|
||||
|
||||
impl<T: Ord + Clone> RangeExt<T> for Range<T> {
|
||||
fn sorted(&self) -> Self {
|
||||
cmp::min(&self.start, &self.end).clone()..cmp::max(&self.start, &self.end).clone()
|
||||
}
|
||||
|
||||
fn to_inclusive(&self) -> RangeInclusive<T> {
|
||||
self.start.clone()..=self.end.clone()
|
||||
}
|
||||
}
|
||||
|
||||
trait RangeToAnchorExt {
|
||||
fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
|
||||
}
|
||||
|
@ -385,9 +385,13 @@ impl MultiBuffer {
|
||||
_ => Default::default(),
|
||||
};
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
let mut buffer_edits: HashMap<usize, Vec<(Range<usize>, Arc<str>, bool, u32)>> =
|
||||
Default::default();
|
||||
struct BufferEdit {
|
||||
range: Range<usize>,
|
||||
new_text: Arc<str>,
|
||||
is_insertion: bool,
|
||||
original_indent_column: u32,
|
||||
}
|
||||
let mut buffer_edits: HashMap<usize, Vec<BufferEdit>> = Default::default();
|
||||
let mut cursor = snapshot.excerpts.cursor::<usize>();
|
||||
for (ix, (range, new_text)) in edits.enumerate() {
|
||||
let new_text: Arc<str> = new_text.into();
|
||||
@ -422,12 +426,12 @@ impl MultiBuffer {
|
||||
buffer_edits
|
||||
.entry(start_excerpt.buffer_id)
|
||||
.or_insert(Vec::new())
|
||||
.push((
|
||||
buffer_start..buffer_end,
|
||||
.push(BufferEdit {
|
||||
range: buffer_start..buffer_end,
|
||||
new_text,
|
||||
true,
|
||||
is_insertion: true,
|
||||
original_indent_column,
|
||||
));
|
||||
});
|
||||
} else {
|
||||
let start_excerpt_range = buffer_start
|
||||
..start_excerpt
|
||||
@ -444,21 +448,21 @@ impl MultiBuffer {
|
||||
buffer_edits
|
||||
.entry(start_excerpt.buffer_id)
|
||||
.or_insert(Vec::new())
|
||||
.push((
|
||||
start_excerpt_range,
|
||||
new_text.clone(),
|
||||
true,
|
||||
.push(BufferEdit {
|
||||
range: start_excerpt_range,
|
||||
new_text: new_text.clone(),
|
||||
is_insertion: true,
|
||||
original_indent_column,
|
||||
));
|
||||
});
|
||||
buffer_edits
|
||||
.entry(end_excerpt.buffer_id)
|
||||
.or_insert(Vec::new())
|
||||
.push((
|
||||
end_excerpt_range,
|
||||
new_text.clone(),
|
||||
false,
|
||||
.push(BufferEdit {
|
||||
range: end_excerpt_range,
|
||||
new_text: new_text.clone(),
|
||||
is_insertion: false,
|
||||
original_indent_column,
|
||||
));
|
||||
});
|
||||
|
||||
cursor.seek(&range.start, Bias::Right, &());
|
||||
cursor.next(&());
|
||||
@ -469,19 +473,19 @@ impl MultiBuffer {
|
||||
buffer_edits
|
||||
.entry(excerpt.buffer_id)
|
||||
.or_insert(Vec::new())
|
||||
.push((
|
||||
excerpt.range.context.to_offset(&excerpt.buffer),
|
||||
new_text.clone(),
|
||||
false,
|
||||
.push(BufferEdit {
|
||||
range: excerpt.range.context.to_offset(&excerpt.buffer),
|
||||
new_text: new_text.clone(),
|
||||
is_insertion: false,
|
||||
original_indent_column,
|
||||
));
|
||||
});
|
||||
cursor.next(&());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (buffer_id, mut edits) in buffer_edits {
|
||||
edits.sort_unstable_by_key(|(range, _, _, _)| range.start);
|
||||
edits.sort_unstable_by_key(|edit| edit.range.start);
|
||||
self.buffers.borrow()[&buffer_id]
|
||||
.buffer
|
||||
.update(cx, |buffer, cx| {
|
||||
@ -490,14 +494,19 @@ impl MultiBuffer {
|
||||
let mut original_indent_columns = Vec::new();
|
||||
let mut deletions = Vec::new();
|
||||
let empty_str: Arc<str> = "".into();
|
||||
while let Some((
|
||||
while let Some(BufferEdit {
|
||||
mut range,
|
||||
new_text,
|
||||
mut is_insertion,
|
||||
original_indent_column,
|
||||
)) = edits.next()
|
||||
}) = edits.next()
|
||||
{
|
||||
while let Some((next_range, _, next_is_insertion, _)) = edits.peek() {
|
||||
while let Some(BufferEdit {
|
||||
range: next_range,
|
||||
is_insertion: next_is_insertion,
|
||||
..
|
||||
}) = edits.peek()
|
||||
{
|
||||
if range.end >= next_range.start {
|
||||
range.end = cmp::max(next_range.end, range.end);
|
||||
is_insertion |= *next_is_insertion;
|
||||
@ -2621,6 +2630,9 @@ impl MultiBufferSnapshot {
|
||||
self.parse_count
|
||||
}
|
||||
|
||||
/// Returns the smallest enclosing bracket ranges containing the given range or
|
||||
/// None if no brackets contain range or the range is not contained in a single
|
||||
/// excerpt
|
||||
pub fn innermost_enclosing_bracket_ranges<T: ToOffset>(
|
||||
&self,
|
||||
range: Range<T>,
|
||||
@ -2648,46 +2660,59 @@ impl MultiBufferSnapshot {
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns enclosinng bracket ranges containing the given range or returns None if the range is
|
||||
/// Returns enclosing bracket ranges containing the given range or returns None if the range is
|
||||
/// not contained in a single excerpt
|
||||
pub fn enclosing_bracket_ranges<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
) -> Option<impl Iterator<Item = (Range<usize>, Range<usize>)> + 'a> {
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
self.excerpt_containing(range.clone())
|
||||
.map(|(excerpt, excerpt_offset)| {
|
||||
let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
|
||||
let excerpt_buffer_end = excerpt_buffer_start + excerpt.text_summary.len;
|
||||
|
||||
let start_in_buffer =
|
||||
excerpt_buffer_start + range.start.saturating_sub(excerpt_offset);
|
||||
let end_in_buffer = excerpt_buffer_start + range.end.saturating_sub(excerpt_offset);
|
||||
self.bracket_ranges(range.clone()).map(|range_pairs| {
|
||||
range_pairs
|
||||
.filter(move |(open, close)| open.start <= range.start && close.end >= range.end)
|
||||
})
|
||||
}
|
||||
|
||||
excerpt
|
||||
.buffer
|
||||
.enclosing_bracket_ranges(start_in_buffer..end_in_buffer)
|
||||
.filter_map(move |(start_bracket_range, end_bracket_range)| {
|
||||
if start_bracket_range.start < excerpt_buffer_start
|
||||
|| end_bracket_range.end > excerpt_buffer_end
|
||||
{
|
||||
return None;
|
||||
}
|
||||
/// Returns bracket range pairs overlapping the given `range` or returns None if the `range` is
|
||||
/// not contained in a single excerpt
|
||||
pub fn bracket_ranges<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
) -> Option<impl Iterator<Item = (Range<usize>, Range<usize>)> + 'a> {
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
let excerpt = self.excerpt_containing(range.clone());
|
||||
excerpt.map(|(excerpt, excerpt_offset)| {
|
||||
let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
|
||||
let excerpt_buffer_end = excerpt_buffer_start + excerpt.text_summary.len;
|
||||
|
||||
let mut start_bracket_range = start_bracket_range.clone();
|
||||
start_bracket_range.start =
|
||||
excerpt_offset + (start_bracket_range.start - excerpt_buffer_start);
|
||||
start_bracket_range.end =
|
||||
excerpt_offset + (start_bracket_range.end - excerpt_buffer_start);
|
||||
let start_in_buffer = excerpt_buffer_start + range.start.saturating_sub(excerpt_offset);
|
||||
let end_in_buffer = excerpt_buffer_start + range.end.saturating_sub(excerpt_offset);
|
||||
|
||||
let mut end_bracket_range = end_bracket_range.clone();
|
||||
end_bracket_range.start =
|
||||
excerpt_offset + (end_bracket_range.start - excerpt_buffer_start);
|
||||
end_bracket_range.end =
|
||||
excerpt_offset + (end_bracket_range.end - excerpt_buffer_start);
|
||||
Some((start_bracket_range, end_bracket_range))
|
||||
})
|
||||
})
|
||||
excerpt
|
||||
.buffer
|
||||
.bracket_ranges(start_in_buffer..end_in_buffer)
|
||||
.filter_map(move |(start_bracket_range, end_bracket_range)| {
|
||||
if start_bracket_range.start < excerpt_buffer_start
|
||||
|| end_bracket_range.end > excerpt_buffer_end
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut start_bracket_range = start_bracket_range.clone();
|
||||
start_bracket_range.start =
|
||||
excerpt_offset + (start_bracket_range.start - excerpt_buffer_start);
|
||||
start_bracket_range.end =
|
||||
excerpt_offset + (start_bracket_range.end - excerpt_buffer_start);
|
||||
|
||||
let mut end_bracket_range = end_bracket_range.clone();
|
||||
end_bracket_range.start =
|
||||
excerpt_offset + (end_bracket_range.start - excerpt_buffer_start);
|
||||
end_bracket_range.end =
|
||||
excerpt_offset + (end_bracket_range.end - excerpt_buffer_start);
|
||||
Some((start_bracket_range, end_bracket_range))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn diagnostics_update_count(&self) -> usize {
|
||||
@ -2939,6 +2964,10 @@ impl MultiBufferSnapshot {
|
||||
cursor.seek(&range.start, Bias::Right, &());
|
||||
let start_excerpt = cursor.item();
|
||||
|
||||
if range.start == range.end {
|
||||
return start_excerpt.map(|excerpt| (excerpt, *cursor.start()));
|
||||
}
|
||||
|
||||
cursor.seek(&range.end, Bias::Right, &());
|
||||
let end_excerpt = cursor.item();
|
||||
|
||||
|
@ -62,7 +62,7 @@ impl<'a> EditorLspTestContext<'a> {
|
||||
params
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree("/root", json!({ "dir": { file_name: "" }}))
|
||||
.insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
|
||||
.await;
|
||||
|
||||
let (window_id, workspace) = cx.add_window(|cx| {
|
||||
@ -107,7 +107,7 @@ impl<'a> EditorLspTestContext<'a> {
|
||||
},
|
||||
lsp,
|
||||
workspace,
|
||||
buffer_lsp_url: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
|
||||
buffer_lsp_url: lsp::Url::from_file_path(format!("/root/dir/{file_name}")).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,7 +122,33 @@ impl<'a> EditorLspTestContext<'a> {
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
)
|
||||
.with_queries(LanguageQueries {
|
||||
indents: Some(Cow::from(indoc! {r#"
|
||||
[
|
||||
((where_clause) _ @end)
|
||||
(field_expression)
|
||||
(call_expression)
|
||||
(assignment_expression)
|
||||
(let_declaration)
|
||||
(let_chain)
|
||||
(await_expression)
|
||||
] @indent
|
||||
|
||||
(_ "[" "]" @end) @indent
|
||||
(_ "<" ">" @end) @indent
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent"#})),
|
||||
brackets: Some(Cow::from(indoc! {r#"
|
||||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("<" @open ">" @close)
|
||||
("\"" @open "\"" @close)
|
||||
(closure_parameters "|" @open "|" @close)"#})),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("Could not parse queries");
|
||||
|
||||
Self::new(language, capabilities, cx).await
|
||||
}
|
||||
@ -148,7 +174,7 @@ impl<'a> EditorLspTestContext<'a> {
|
||||
("\"" @open "\"" @close)"#})),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("Could not parse brackets");
|
||||
.expect("Could not parse queries");
|
||||
|
||||
Self::new(language, capabilities, cx).await
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ pub use text::{Buffer as TextBuffer, BufferSnapshot as TextBufferSnapshot, Opera
|
||||
use theme::SyntaxTheme;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use util::RandomCharIter;
|
||||
use util::TryFutureExt as _;
|
||||
use util::{RangeExt, TryFutureExt as _};
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use {tree_sitter_rust, tree_sitter_typescript};
|
||||
@ -1389,12 +1389,12 @@ impl Buffer {
|
||||
.enumerate()
|
||||
.zip(&edit_operation.as_edit().unwrap().new_text)
|
||||
.map(|((ix, (range, _)), new_text)| {
|
||||
let new_text_len = new_text.len();
|
||||
let new_text_length = new_text.len();
|
||||
let old_start = range.start.to_point(&before_edit);
|
||||
let new_start = (delta + range.start as isize) as usize;
|
||||
delta += new_text_len as isize - (range.end as isize - range.start as isize);
|
||||
delta += new_text_length as isize - (range.end as isize - range.start as isize);
|
||||
|
||||
let mut range_of_insertion_to_indent = 0..new_text_len;
|
||||
let mut range_of_insertion_to_indent = 0..new_text_length;
|
||||
let mut first_line_is_new = false;
|
||||
let mut original_indent_column = None;
|
||||
|
||||
@ -2358,18 +2358,18 @@ impl BufferSnapshot {
|
||||
Some(items)
|
||||
}
|
||||
|
||||
pub fn enclosing_bracket_ranges<'a, T: ToOffset>(
|
||||
/// Returns bracket range pairs overlapping or adjacent to `range`
|
||||
pub fn bracket_ranges<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
) -> impl Iterator<Item = (Range<usize>, Range<usize>)> + 'a {
|
||||
// Find bracket pairs that *inclusively* contain the given range.
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
let range = range.start.to_offset(self).saturating_sub(1)
|
||||
..self.len().min(range.end.to_offset(self) + 1);
|
||||
|
||||
let mut matches = self.syntax.matches(
|
||||
range.start.saturating_sub(1)..self.len().min(range.end + 1),
|
||||
&self.text,
|
||||
|grammar| grammar.brackets_config.as_ref().map(|c| &c.query),
|
||||
);
|
||||
let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| {
|
||||
grammar.brackets_config.as_ref().map(|c| &c.query)
|
||||
});
|
||||
let configs = matches
|
||||
.grammars()
|
||||
.iter()
|
||||
@ -2393,7 +2393,8 @@ impl BufferSnapshot {
|
||||
|
||||
let Some((open, close)) = open.zip(close) else { continue };
|
||||
|
||||
if open.start > range.start || close.end < range.end {
|
||||
let bracket_range = open.start..=close.end;
|
||||
if !bracket_range.overlaps(&range) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -578,7 +578,7 @@ async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
|
||||
#[gpui::test]
|
||||
fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
|
||||
let mut assert = |selection_text, range_markers| {
|
||||
assert_enclosing_bracket_pairs(selection_text, range_markers, rust_lang(), cx)
|
||||
assert_bracket_pairs(selection_text, range_markers, rust_lang(), cx)
|
||||
};
|
||||
|
||||
assert(
|
||||
@ -696,7 +696,7 @@ fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(
|
||||
cx: &mut MutableAppContext,
|
||||
) {
|
||||
let mut assert = |selection_text, bracket_pair_texts| {
|
||||
assert_enclosing_bracket_pairs(selection_text, bracket_pair_texts, javascript_lang(), cx)
|
||||
assert_bracket_pairs(selection_text, bracket_pair_texts, javascript_lang(), cx)
|
||||
};
|
||||
|
||||
assert(
|
||||
@ -710,6 +710,7 @@ fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(
|
||||
}"}],
|
||||
);
|
||||
|
||||
eprintln!("-----------------------");
|
||||
// Regression test: even though the parent node of the parentheses (the for loop) does
|
||||
// intersect the given range, the parentheses themselves do not contain the range, so
|
||||
// they should not be returned. Only the curly braces contain the range.
|
||||
@ -2047,7 +2048,7 @@ fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> Str
|
||||
}
|
||||
|
||||
// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers`
|
||||
fn assert_enclosing_bracket_pairs(
|
||||
fn assert_bracket_pairs(
|
||||
selection_text: &'static str,
|
||||
bracket_pair_texts: Vec<&'static str>,
|
||||
language: Language,
|
||||
@ -2072,9 +2073,7 @@ fn assert_enclosing_bracket_pairs(
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_set_eq!(
|
||||
buffer
|
||||
.enclosing_bracket_ranges(selection_range)
|
||||
.collect::<Vec<_>>(),
|
||||
buffer.bracket_ranges(selection_range).collect::<Vec<_>>(),
|
||||
bracket_pairs
|
||||
);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/util.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
@ -22,7 +23,6 @@ serde_json = { version = "1.0", features = ["preserve_order"], optional = true }
|
||||
git2 = { version = "0.15", default-features = false, optional = true }
|
||||
dirs = "3.0"
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
tempdir = { version = "0.3.7" }
|
||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||
|
@ -3,16 +3,17 @@ pub mod paths;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod test;
|
||||
|
||||
pub use backtrace::Backtrace;
|
||||
use futures::Future;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
ops::AddAssign,
|
||||
cmp::{self, Ordering},
|
||||
ops::{AddAssign, Range, RangeInclusive},
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
pub use backtrace::Backtrace;
|
||||
use futures::Future;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct StaffMode(pub bool);
|
||||
|
||||
@ -245,6 +246,46 @@ macro_rules! async_iife {
|
||||
};
|
||||
}
|
||||
|
||||
pub trait RangeExt<T> {
|
||||
fn sorted(&self) -> Self;
|
||||
fn to_inclusive(&self) -> RangeInclusive<T>;
|
||||
fn overlaps(&self, other: &Range<T>) -> bool;
|
||||
}
|
||||
|
||||
impl<T: Ord + Clone> RangeExt<T> for Range<T> {
|
||||
fn sorted(&self) -> Self {
|
||||
cmp::min(&self.start, &self.end).clone()..cmp::max(&self.start, &self.end).clone()
|
||||
}
|
||||
|
||||
fn to_inclusive(&self) -> RangeInclusive<T> {
|
||||
self.start.clone()..=self.end.clone()
|
||||
}
|
||||
|
||||
fn overlaps(&self, other: &Range<T>) -> bool {
|
||||
self.contains(&other.start)
|
||||
|| self.contains(&other.end)
|
||||
|| other.contains(&self.start)
|
||||
|| other.contains(&self.end)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Ord + Clone> RangeExt<T> for RangeInclusive<T> {
|
||||
fn sorted(&self) -> Self {
|
||||
cmp::min(self.start(), self.end()).clone()..=cmp::max(self.start(), self.end()).clone()
|
||||
}
|
||||
|
||||
fn to_inclusive(&self) -> RangeInclusive<T> {
|
||||
self.clone()
|
||||
}
|
||||
|
||||
fn overlaps(&self, other: &Range<T>) -> bool {
|
||||
self.contains(&other.start)
|
||||
|| self.contains(&other.end)
|
||||
|| other.contains(&self.start())
|
||||
|| other.contains(&self.end())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use editor::{
|
||||
char_kind,
|
||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||
movement, Bias, CharKind, DisplayPoint,
|
||||
movement, Bias, CharKind, DisplayPoint, ToOffset,
|
||||
};
|
||||
use gpui::{actions, impl_actions, MutableAppContext};
|
||||
use language::{Point, Selection, SelectionGoal};
|
||||
@ -450,19 +450,53 @@ fn end_of_document(map: &DisplaySnapshot, point: DisplayPoint, line: usize) -> D
|
||||
map.clip_point(new_point, Bias::Left)
|
||||
}
|
||||
|
||||
fn matching(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
||||
let offset = point.to_offset(map, Bias::Left);
|
||||
if let Some((open_range, close_range)) = map
|
||||
.buffer_snapshot
|
||||
.innermost_enclosing_bracket_ranges(offset..offset)
|
||||
{
|
||||
if open_range.contains(&offset) {
|
||||
close_range.start.to_display_point(map)
|
||||
} else {
|
||||
open_range.start.to_display_point(map)
|
||||
fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
|
||||
// https://github.com/vim/vim/blob/1d87e11a1ef201b26ed87585fba70182ad0c468a/runtime/doc/motion.txt#L1200
|
||||
let point = display_point.to_point(map);
|
||||
let offset = point.to_offset(&map.buffer_snapshot);
|
||||
|
||||
// Ensure the range is contained by the current line.
|
||||
let mut line_end = map.next_line_boundary(point).0;
|
||||
if line_end == point {
|
||||
line_end = map.max_point().to_point(map);
|
||||
}
|
||||
line_end.column = line_end.column.saturating_sub(1);
|
||||
|
||||
let line_range = map.prev_line_boundary(point).0..line_end;
|
||||
let ranges = map.buffer_snapshot.bracket_ranges(line_range.clone());
|
||||
if let Some(ranges) = ranges {
|
||||
let line_range = line_range.start.to_offset(&map.buffer_snapshot)
|
||||
..line_range.end.to_offset(&map.buffer_snapshot);
|
||||
let mut closest_pair_destination = None;
|
||||
let mut closest_distance = usize::MAX;
|
||||
|
||||
for (open_range, close_range) in ranges {
|
||||
if open_range.start >= offset && line_range.contains(&open_range.start) {
|
||||
let distance = open_range.start - offset;
|
||||
if distance < closest_distance {
|
||||
closest_pair_destination = Some(close_range.start);
|
||||
closest_distance = distance;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if close_range.start >= offset && line_range.contains(&close_range.start) {
|
||||
let distance = close_range.start - offset;
|
||||
if distance < closest_distance {
|
||||
closest_pair_destination = Some(open_range.start);
|
||||
closest_distance = distance;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
closest_pair_destination
|
||||
.map(|destination| destination.to_display_point(map))
|
||||
.unwrap_or(display_point)
|
||||
} else {
|
||||
point
|
||||
display_point
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -824,17 +824,34 @@ mod test {
|
||||
ˇ
|
||||
brown fox"})
|
||||
.await;
|
||||
cx.assert(indoc! {"
|
||||
|
||||
cx.assert_manual(
|
||||
indoc! {"
|
||||
fn test() {
|
||||
println!(ˇ);
|
||||
}
|
||||
"})
|
||||
.await;
|
||||
cx.assert(indoc! {"
|
||||
}"},
|
||||
Mode::Normal,
|
||||
indoc! {"
|
||||
fn test() {
|
||||
println!();
|
||||
ˇ
|
||||
}"},
|
||||
Mode::Insert,
|
||||
);
|
||||
|
||||
cx.assert_manual(
|
||||
indoc! {"
|
||||
fn test(ˇ) {
|
||||
println!();
|
||||
}"})
|
||||
.await;
|
||||
}"},
|
||||
Mode::Normal,
|
||||
indoc! {"
|
||||
fn test() {
|
||||
ˇ
|
||||
println!();
|
||||
}"},
|
||||
Mode::Insert,
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@ -857,13 +874,15 @@ mod test {
|
||||
// Our indentation is smarter than vims. So we don't match here
|
||||
cx.assert_manual(
|
||||
indoc! {"
|
||||
fn test()
|
||||
println!(ˇ);"},
|
||||
fn test() {
|
||||
println!(ˇ);
|
||||
}"},
|
||||
Mode::Normal,
|
||||
indoc! {"
|
||||
fn test()
|
||||
fn test() {
|
||||
ˇ
|
||||
println!();"},
|
||||
println!();
|
||||
}"},
|
||||
Mode::Insert,
|
||||
);
|
||||
cx.assert_manual(
|
||||
@ -994,14 +1013,14 @@ mod test {
|
||||
#[gpui::test]
|
||||
async fn test_capital_f_and_capital_t(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
for count in 1..=3 {
|
||||
let test_case = indoc! {"
|
||||
ˇaaaˇbˇ ˇbˇ ˇbˇbˇ aˇaaˇbaaa
|
||||
ˇ ˇbˇaaˇa ˇbˇbˇb
|
||||
ˇ
|
||||
ˇb
|
||||
let test_case = indoc! {"
|
||||
ˇaaaˇbˇ ˇbˇ ˇbˇbˇ aˇaaˇbaaa
|
||||
ˇ ˇbˇaaˇa ˇbˇbˇb
|
||||
ˇ
|
||||
ˇb
|
||||
"};
|
||||
|
||||
for count in 1..=3 {
|
||||
cx.assert_binding_matches_all([&count.to_string(), "shift-f", "b"], test_case)
|
||||
.await;
|
||||
|
||||
@ -1009,4 +1028,13 @@ mod test {
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_percent(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await.binding(["%"]);
|
||||
cx.assert_all("ˇconsole.logˇ(ˇvaˇrˇ)ˇ;").await;
|
||||
cx.assert_all("ˇconsole.logˇ(ˇ'var', ˇ[ˇ1, ˇ2, 3ˇ]ˇ)ˇ;")
|
||||
.await;
|
||||
cx.assert_all("let result = curried_funˇ(ˇ)ˇ(ˇ)ˇ;").await;
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,29 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use editor::test::editor_test_context::EditorTestContext;
|
||||
use gpui::{json::json, AppContext, ContextHandle, ViewHandle};
|
||||
use project::Project;
|
||||
use editor::test::{
|
||||
editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
|
||||
};
|
||||
use gpui::{AppContext, ContextHandle};
|
||||
use search::{BufferSearchBar, ProjectSearchBar};
|
||||
use workspace::{pane, AppState, WorkspaceHandle};
|
||||
|
||||
use crate::{state::Operator, *};
|
||||
|
||||
use super::VimBindingTestContext;
|
||||
|
||||
pub struct VimTestContext<'a> {
|
||||
cx: EditorTestContext<'a>,
|
||||
workspace: ViewHandle<Workspace>,
|
||||
cx: EditorLspTestContext<'a>,
|
||||
}
|
||||
|
||||
impl<'a> VimTestContext<'a> {
|
||||
pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
|
||||
cx.update(|cx| {
|
||||
editor::init(cx);
|
||||
pane::init(cx);
|
||||
search::init(cx);
|
||||
crate::init(cx);
|
||||
|
||||
settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap();
|
||||
});
|
||||
|
||||
let params = cx.update(AppState::test);
|
||||
let project = Project::test(params.fs.clone(), [], cx).await;
|
||||
let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.update_global(|settings: &mut Settings, _| {
|
||||
@ -35,24 +31,10 @@ impl<'a> VimTestContext<'a> {
|
||||
});
|
||||
});
|
||||
|
||||
params
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree("/root", json!({ "dir": { "test.txt": "" } }))
|
||||
.await;
|
||||
|
||||
let (window_id, workspace) = cx.add_window(|cx| {
|
||||
Workspace::new(
|
||||
Default::default(),
|
||||
0,
|
||||
project.clone(),
|
||||
|_, _| unimplemented!(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let window_id = cx.window_id;
|
||||
|
||||
// Setup search toolbars and keypress hook
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
cx.update_workspace(|workspace, cx| {
|
||||
observe_keystrokes(window_id, cx);
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.toolbar().update(cx, |toolbar, cx| {
|
||||
@ -64,44 +46,14 @@ impl<'a> VimTestContext<'a> {
|
||||
});
|
||||
});
|
||||
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_local_worktree("/root", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
||||
.await;
|
||||
|
||||
let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
|
||||
let item = workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.open_path(file, None, true, cx)
|
||||
})
|
||||
.await
|
||||
.expect("Could not open test file");
|
||||
|
||||
let editor = cx.update(|cx| {
|
||||
item.act_as::<Editor>(cx)
|
||||
.expect("Opened test file wasn't an editor")
|
||||
});
|
||||
editor.update(cx, |_, cx| cx.focus_self());
|
||||
|
||||
Self {
|
||||
cx: EditorTestContext {
|
||||
cx,
|
||||
window_id,
|
||||
editor,
|
||||
},
|
||||
workspace,
|
||||
}
|
||||
Self { cx }
|
||||
}
|
||||
|
||||
pub fn workspace<F, T>(&mut self, read: F) -> T
|
||||
where
|
||||
F: FnOnce(&Workspace, &AppContext) -> T,
|
||||
{
|
||||
self.workspace.read_with(self.cx.cx, read)
|
||||
self.cx.workspace.read_with(self.cx.cx.cx, read)
|
||||
}
|
||||
|
||||
pub fn enable_vim(&mut self) {
|
||||
|
@ -650,7 +650,7 @@ mod test {
|
||||
The quick brown
|
||||
the
|
||||
ˇfox jumps over
|
||||
dog"},
|
||||
dog"},
|
||||
Mode::Normal,
|
||||
);
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
[{"Text":"\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\n\nbrown fox\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\n\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\njumps over\n"},{"Mode":"Insert"},{"Selection":{"start":[3,0],"end":[3,0]}},{"Mode":"Insert"},{"Text":"The quick\n\n\nbrown fox"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"},{"Text":"fn test() {\n println!();\n \n}\n"},{"Mode":"Insert"},{"Selection":{"start":[2,4],"end":[2,4]}},{"Mode":"Insert"},{"Text":"fn test() {\n\n println!();\n}"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"}]
|
||||
[{"Text":"\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\n\nbrown fox\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\n\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\njumps over\n"},{"Mode":"Insert"},{"Selection":{"start":[3,0],"end":[3,0]}},{"Mode":"Insert"},{"Text":"The quick\n\n\nbrown fox"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"}]
|
1
crates/vim/test_data/test_percent.json
Normal file
1
crates/vim/test_data/test_percent.json
Normal file
@ -0,0 +1 @@
|
||||
[{"Text":"console.log(var);"},{"Mode":"Normal"},{"Selection":{"start":[0,15],"end":[0,15]}},{"Mode":"Normal"},{"Text":"console.log(var);"},{"Mode":"Normal"},{"Selection":{"start":[0,15],"end":[0,15]}},{"Mode":"Normal"},{"Text":"console.log(var);"},{"Mode":"Normal"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Normal"},{"Text":"console.log(var);"},{"Mode":"Normal"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Normal"},{"Text":"console.log(var);"},{"Mode":"Normal"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Normal"},{"Text":"console.log(var);"},{"Mode":"Normal"},{"Selection":{"start":[0,16],"end":[0,16]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,28],"end":[0,28]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,28],"end":[0,28]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,27],"end":[0,27]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,27],"end":[0,27]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,19],"end":[0,19]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,19],"end":[0,19]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,19],"end":[0,19]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,29],"end":[0,29]}},{"Mode":"Normal"},{"Text":"let result = curried_fun()();"},{"Mode":"Normal"},{"Selection":{"start":[0,25],"end":[0,25]}},{"Mode":"Normal"},{"Text":"let result = curried_fun()();"},{"Mode":"Normal"},{"Selection":{"start":[0,24],"end":[0,24]}},{"Mode":"Normal"},{"Text":"let result = curried_fun()();"},{"Mode":"Normal"},{"Selection":{"start":[0,27],"end":[0,27]}},{"Mode":"Normal"},{"Text":"let result = curried_fun()();"},{"Mode":"Normal"},{"Selection":{"start":[0,26],"end":[0,26]}},{"Mode":"Normal"},{"Text":"let result = curried_fun()();"},{"Mode":"Normal"},{"Selection":{"start":[0,28],"end":[0,28]}},{"Mode":"Normal"}]
|
@ -1330,7 +1330,19 @@ impl Workspace {
|
||||
focus_item: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
|
||||
let pane = pane.unwrap_or_else(|| self.active_pane().downgrade());
|
||||
let pane = pane.unwrap_or_else(|| {
|
||||
if !self.dock_active() {
|
||||
self.active_pane().downgrade()
|
||||
} else {
|
||||
self.last_active_center_pane.clone().unwrap_or_else(|| {
|
||||
self.panes
|
||||
.first()
|
||||
.expect("There must be an active pane")
|
||||
.downgrade()
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
let task = self.load_path(path.into(), cx);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let (project_entry_id, build_item) = task.await?;
|
||||
@ -1637,6 +1649,10 @@ impl Workspace {
|
||||
self.dock.pane()
|
||||
}
|
||||
|
||||
fn dock_active(&self) -> bool {
|
||||
&self.active_pane == self.dock.pane()
|
||||
}
|
||||
|
||||
fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
|
||||
if let Some(remote_id) = remote_id {
|
||||
self.remote_entity_subscription =
|
||||
|
Loading…
Reference in New Issue
Block a user