mirror of https://github.com/helix-editor/helix
Merge 70650ff55a
into 4281228da3
commit
d51abbe4f0
|
@ -25,38 +25,59 @@ pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: Selection)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
||||||
select_node_impl(
|
selection.transform(move |range| {
|
||||||
syntax,
|
let (from, to) = range.into_byte_range(text);
|
||||||
text,
|
let mut cursor = syntax.walk();
|
||||||
selection,
|
cursor.reset_to_byte_range(from, to);
|
||||||
|cursor| {
|
|
||||||
cursor.goto_first_child();
|
if let Some(node) = cursor
|
||||||
},
|
.into_iter()
|
||||||
None,
|
.find(|node| node.is_named() && node.is_contained_within(from..to))
|
||||||
)
|
{
|
||||||
|
return Range::from_node(node, text, range.direction());
|
||||||
|
}
|
||||||
|
|
||||||
|
range
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_next_sibling(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
pub fn select_next_sibling(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
||||||
select_node_impl(
|
selection.transform(move |range| {
|
||||||
syntax,
|
let (from, to) = range.into_byte_range(text);
|
||||||
text,
|
let mut cursor = syntax.walk();
|
||||||
selection,
|
cursor.reset_to_byte_range(from, to);
|
||||||
|cursor| {
|
|
||||||
while !cursor.goto_next_sibling() {
|
while !cursor.goto_next_sibling() {
|
||||||
if !cursor.goto_parent() {
|
if !cursor.goto_parent() {
|
||||||
break;
|
return range;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Some(Direction::Forward),
|
Range::from_node(cursor.node(), text, Direction::Forward)
|
||||||
)
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_prev_sibling(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
||||||
|
selection.transform(move |range| {
|
||||||
|
let (from, to) = range.into_byte_range(text);
|
||||||
|
let mut cursor = syntax.walk();
|
||||||
|
cursor.reset_to_byte_range(from, to);
|
||||||
|
|
||||||
|
while !cursor.goto_previous_sibling() {
|
||||||
|
if !cursor.goto_parent() {
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Range::from_node(cursor.node(), text, Direction::Backward)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_all_siblings(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
pub fn select_all_siblings(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
||||||
let mut cursor = syntax.walk();
|
let mut cursor = syntax.walk();
|
||||||
selection.transform_iter(move |range| {
|
selection.transform_iter(move |range| {
|
||||||
let (from, to) = range.into_byte_range(text);
|
let (from, to) = range.into_byte_range(text);
|
||||||
cursor.reset_to_byte_range(from as u32, to as u32);
|
cursor.reset_to_byte_range(from, to);
|
||||||
|
|
||||||
if !cursor.goto_parent_with(|parent| parent.child_count() > 1) {
|
if !cursor.goto_parent_with(|parent| parent.child_count() > 1) {
|
||||||
return vec![range].into_iter();
|
return vec![range].into_iter();
|
||||||
|
@ -70,7 +91,7 @@ pub fn select_all_children(syntax: &Syntax, text: RopeSlice, selection: Selectio
|
||||||
let mut cursor = syntax.walk();
|
let mut cursor = syntax.walk();
|
||||||
selection.transform_iter(move |range| {
|
selection.transform_iter(move |range| {
|
||||||
let (from, to) = range.into_byte_range(text);
|
let (from, to) = range.into_byte_range(text);
|
||||||
cursor.reset_to_byte_range(from as u32, to as u32);
|
cursor.reset_to_byte_range(from, to);
|
||||||
select_children(&mut cursor, text, range).into_iter()
|
select_children(&mut cursor, text, range).into_iter()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -88,47 +109,3 @@ fn select_children(cursor: &mut TreeCursor, text: RopeSlice, range: Range) -> Ve
|
||||||
vec![range]
|
vec![range]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_prev_sibling(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
|
||||||
select_node_impl(
|
|
||||||
syntax,
|
|
||||||
text,
|
|
||||||
selection,
|
|
||||||
|cursor| {
|
|
||||||
while !cursor.goto_previous_sibling() {
|
|
||||||
if !cursor.goto_parent() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(Direction::Backward),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select_node_impl<F>(
|
|
||||||
syntax: &Syntax,
|
|
||||||
text: RopeSlice,
|
|
||||||
selection: Selection,
|
|
||||||
motion: F,
|
|
||||||
direction: Option<Direction>,
|
|
||||||
) -> Selection
|
|
||||||
where
|
|
||||||
F: Fn(&mut TreeCursor),
|
|
||||||
{
|
|
||||||
let cursor = &mut syntax.walk();
|
|
||||||
|
|
||||||
selection.transform(|range| {
|
|
||||||
let from = text.char_to_byte(range.from()) as u32;
|
|
||||||
let to = text.char_to_byte(range.to()) as u32;
|
|
||||||
|
|
||||||
cursor.reset_to_byte_range(from, to);
|
|
||||||
|
|
||||||
motion(cursor);
|
|
||||||
|
|
||||||
let node = cursor.node();
|
|
||||||
let from = text.byte_to_char(node.start_byte() as usize);
|
|
||||||
let to = text.byte_to_char(node.end_byte() as usize);
|
|
||||||
|
|
||||||
Range::new(from, to).with_direction(direction.unwrap_or_else(|| range.direction()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -387,8 +387,11 @@ impl Range {
|
||||||
|
|
||||||
/// Converts this char range into an in order byte range, discarding
|
/// Converts this char range into an in order byte range, discarding
|
||||||
/// direction.
|
/// direction.
|
||||||
pub fn into_byte_range(&self, text: RopeSlice) -> (usize, usize) {
|
pub fn into_byte_range(&self, text: RopeSlice) -> (u32, u32) {
|
||||||
(text.char_to_byte(self.from()), text.char_to_byte(self.to()))
|
(
|
||||||
|
text.char_to_byte(self.from()) as u32,
|
||||||
|
text.char_to_byte(self.to()) as u32,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -700,22 +703,161 @@ impl Selection {
|
||||||
pub fn contains(&self, other: &Selection) -> bool {
|
pub fn contains(&self, other: &Selection) -> bool {
|
||||||
is_subset::<true>(self.range_bounds(), other.range_bounds())
|
is_subset::<true>(self.range_bounds(), other.range_bounds())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// returns true if self has at least one range that overlaps with at least one range from other
|
||||||
|
pub fn overlaps(&self, other: &Selection) -> bool {
|
||||||
|
let (mut iter_self, mut iter_other) = (self.iter(), other.iter());
|
||||||
|
let (mut ele_self, mut ele_other) = (iter_self.next(), iter_other.next());
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match (ele_self, ele_other) {
|
||||||
|
(Some(ra), Some(rb)) => {
|
||||||
|
if ra.overlaps(rb) {
|
||||||
|
return true;
|
||||||
|
} else if ra.from() > rb.from() {
|
||||||
|
ele_self = iter_self.next();
|
||||||
|
} else {
|
||||||
|
ele_other = iter_other.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the given selection with the overlapping portions of `other`
|
||||||
|
/// cut out. If one range from this selection is equal to one from `other`,
|
||||||
|
/// this range is removed. If this results in an entirely empty selection,
|
||||||
|
/// `None` is returned.
|
||||||
|
pub fn without(self, other: &Selection) -> Option<Self> {
|
||||||
|
if other.contains(&self) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut primary_index = self.primary_index;
|
||||||
|
let mut ranges = smallvec!();
|
||||||
|
let (mut iter_self, mut iter_other) = (self.into_iter(), other.iter());
|
||||||
|
let (mut ele_self, mut ele_other) = (iter_self.next(), iter_other.next());
|
||||||
|
let mut cur_index = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match (ele_self, ele_other) {
|
||||||
|
(Some(ra), Some(rb)) => {
|
||||||
|
if !ra.overlaps(rb) {
|
||||||
|
// there's no overlap and it's on the left of rb
|
||||||
|
if ra.to() <= rb.from() {
|
||||||
|
ranges.push(ra);
|
||||||
|
ele_self = iter_self.next();
|
||||||
|
cur_index += 1;
|
||||||
|
|
||||||
|
// otherwise it must be on the right, so move rb forward
|
||||||
|
} else {
|
||||||
|
ele_other = iter_other.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise there is overlap, so truncate or split
|
||||||
|
if rb.contains_range(&ra) {
|
||||||
|
ele_self = iter_self.next();
|
||||||
|
cur_index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [ ra ]
|
||||||
|
// [ rb ]
|
||||||
|
if ra.from() <= rb.from() && ra.to() <= rb.to() && ra.to() >= rb.from() {
|
||||||
|
let new = if ra.direction() == Direction::Backward {
|
||||||
|
Range::new(rb.from(), ra.head)
|
||||||
|
} else {
|
||||||
|
Range::new(ra.anchor, rb.from())
|
||||||
|
};
|
||||||
|
|
||||||
|
ranges.push(new);
|
||||||
|
ele_self = iter_self.next();
|
||||||
|
cur_index += 1;
|
||||||
|
|
||||||
|
// [ ra ]
|
||||||
|
// [ rb ]
|
||||||
|
} else if ra.from() >= rb.from() && ra.to() >= rb.to() && ra.from() <= rb.to() {
|
||||||
|
let new = if ra.direction() == Direction::Backward {
|
||||||
|
Range::new(ra.anchor, rb.to() + 1)
|
||||||
|
} else {
|
||||||
|
Range::new(rb.to(), ra.head)
|
||||||
|
};
|
||||||
|
|
||||||
|
// don't settle on the new range yet because the next
|
||||||
|
// rb could chop off the other end of ra
|
||||||
|
ele_self = Some(new);
|
||||||
|
ele_other = iter_other.next();
|
||||||
|
|
||||||
|
// [ ra ]
|
||||||
|
// [ rb ]
|
||||||
|
} else if ra.from() < rb.from() && ra.to() > rb.to() {
|
||||||
|
// we must split the range into two
|
||||||
|
let left = if ra.direction() == Direction::Backward {
|
||||||
|
Range::new(rb.from(), ra.head)
|
||||||
|
} else {
|
||||||
|
Range::new(ra.anchor, rb.from())
|
||||||
|
};
|
||||||
|
|
||||||
|
let right = if ra.direction() == Direction::Backward {
|
||||||
|
Range::new(ra.anchor, rb.to())
|
||||||
|
} else {
|
||||||
|
Range::new(rb.to(), ra.head)
|
||||||
|
};
|
||||||
|
|
||||||
|
// We do NOT push right onto the results right away.
|
||||||
|
// We must put it back into the iterator and check it
|
||||||
|
// again in case a further range splits it again.
|
||||||
|
ranges.push(left);
|
||||||
|
ele_other = iter_other.next();
|
||||||
|
|
||||||
|
// offset the primary index whenever we split
|
||||||
|
if cur_index < primary_index {
|
||||||
|
primary_index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_index += 1;
|
||||||
|
ele_self = Some(right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the rest just get included as is
|
||||||
|
(Some(range), None) => {
|
||||||
|
ranges.push(range);
|
||||||
|
ele_self = iter_self.next();
|
||||||
|
cur_index += 1;
|
||||||
|
}
|
||||||
|
// exhausted `self`, nothing left to do
|
||||||
|
(None, _) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ranges.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Selection::new(ranges, primary_index))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoIterator for &'a Selection {
|
impl<'a> IntoIterator for &'a Selection {
|
||||||
type Item = &'a Range;
|
type Item = &'a Range;
|
||||||
type IntoIter = std::slice::Iter<'a, Range>;
|
type IntoIter = std::slice::Iter<'a, Range>;
|
||||||
|
|
||||||
fn into_iter(self) -> std::slice::Iter<'a, Range> {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.ranges().iter()
|
self.ranges().iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoIterator for Selection {
|
impl IntoIterator for Selection {
|
||||||
type Item = Range;
|
type Item = Range;
|
||||||
type IntoIter = smallvec::IntoIter<[Range; 1]>;
|
type IntoIter = smallvec::IntoIter<[Self::Item; 1]>;
|
||||||
|
|
||||||
fn into_iter(self) -> smallvec::IntoIter<[Range; 1]> {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.ranges.into_iter()
|
self.ranges.into_iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -882,6 +1024,7 @@ pub fn split_on_matches(text: RopeSlice, selection: &Selection, regex: &rope::Re
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::test;
|
||||||
use crate::Rope;
|
use crate::Rope;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -972,7 +1115,7 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_overlaps() {
|
fn test_range_overlaps() {
|
||||||
fn overlaps(a: (usize, usize), b: (usize, usize)) -> bool {
|
fn overlaps(a: (usize, usize), b: (usize, usize)) -> bool {
|
||||||
Range::new(a.0, a.1).overlaps(&Range::new(b.0, b.1))
|
Range::new(a.0, a.1).overlaps(&Range::new(b.0, b.1))
|
||||||
}
|
}
|
||||||
|
@ -1022,6 +1165,160 @@ mod test {
|
||||||
assert!(overlaps((1, 1), (1, 1)));
|
assert!(overlaps((1, 1), (1, 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_selection_overlaps() {
|
||||||
|
fn overlaps(a: &[(usize, usize)], b: &[(usize, usize)]) -> bool {
|
||||||
|
let a = Selection::new(
|
||||||
|
a.iter()
|
||||||
|
.map(|(anchor, head)| Range::new(*anchor, *head))
|
||||||
|
.collect(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
let b = Selection::new(
|
||||||
|
b.iter()
|
||||||
|
.map(|(anchor, head)| Range::new(*anchor, *head))
|
||||||
|
.collect(),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
a.overlaps(&b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two non-zero-width ranges, no overlap.
|
||||||
|
assert!(!overlaps(&[(0, 3)], &[(3, 6)]));
|
||||||
|
assert!(!overlaps(&[(0, 3)], &[(6, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 0)], &[(3, 6)]));
|
||||||
|
assert!(!overlaps(&[(3, 0)], &[(6, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 6)], &[(0, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 6)], &[(3, 0)]));
|
||||||
|
assert!(!overlaps(&[(6, 3)], &[(0, 3)]));
|
||||||
|
assert!(!overlaps(&[(6, 3)], &[(3, 0)]));
|
||||||
|
|
||||||
|
// more ranges in one or the other, no overlap
|
||||||
|
assert!(!overlaps(&[(6, 3), (9, 6)], &[(3, 0)]));
|
||||||
|
assert!(!overlaps(&[(6, 3), (6, 9)], &[(3, 0)]));
|
||||||
|
assert!(!overlaps(&[(3, 6), (9, 6)], &[(3, 0)]));
|
||||||
|
assert!(!overlaps(&[(3, 6), (6, 9)], &[(3, 0)]));
|
||||||
|
assert!(!overlaps(&[(6, 3), (9, 6)], &[(0, 3)]));
|
||||||
|
assert!(!overlaps(&[(6, 3), (6, 9)], &[(0, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 6), (9, 6)], &[(0, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 6), (6, 9)], &[(0, 3)]));
|
||||||
|
|
||||||
|
assert!(!overlaps(&[(6, 3)], &[(3, 0), (9, 6)]));
|
||||||
|
assert!(!overlaps(&[(6, 3)], &[(3, 0), (6, 9)]));
|
||||||
|
assert!(!overlaps(&[(3, 6)], &[(3, 0), (9, 6)]));
|
||||||
|
assert!(!overlaps(&[(3, 6)], &[(3, 0), (6, 9)]));
|
||||||
|
assert!(!overlaps(&[(6, 3)], &[(0, 3), (9, 6)]));
|
||||||
|
assert!(!overlaps(&[(6, 3)], &[(0, 3), (6, 9)]));
|
||||||
|
assert!(!overlaps(&[(3, 6)], &[(0, 3), (9, 6)]));
|
||||||
|
assert!(!overlaps(&[(3, 6)], &[(0, 3), (6, 9)]));
|
||||||
|
|
||||||
|
// Two non-zero-width ranges, overlap.
|
||||||
|
assert!(overlaps(&[(0, 4)], &[(3, 6)]));
|
||||||
|
assert!(overlaps(&[(0, 4)], &[(6, 3)]));
|
||||||
|
assert!(overlaps(&[(4, 0)], &[(3, 6)]));
|
||||||
|
assert!(overlaps(&[(4, 0)], &[(6, 3)]));
|
||||||
|
assert!(overlaps(&[(3, 6)], &[(0, 4)]));
|
||||||
|
assert!(overlaps(&[(3, 6)], &[(4, 0)]));
|
||||||
|
assert!(overlaps(&[(6, 3)], &[(0, 4)]));
|
||||||
|
assert!(overlaps(&[(6, 3)], &[(4, 0)]));
|
||||||
|
|
||||||
|
// Two non-zero-width ranges, overlap, extra from one or the other
|
||||||
|
assert!(overlaps(&[(0, 4), (7, 10)], &[(3, 6)]));
|
||||||
|
assert!(overlaps(&[(0, 4), (7, 10)], &[(6, 3)]));
|
||||||
|
assert!(overlaps(&[(4, 0), (7, 10)], &[(3, 6)]));
|
||||||
|
assert!(overlaps(&[(4, 0), (7, 10)], &[(6, 3)]));
|
||||||
|
assert!(overlaps(&[(3, 6), (7, 10)], &[(0, 4)]));
|
||||||
|
assert!(overlaps(&[(3, 6), (7, 10)], &[(4, 0)]));
|
||||||
|
assert!(overlaps(&[(6, 3), (7, 10)], &[(0, 4)]));
|
||||||
|
assert!(overlaps(&[(6, 3), (7, 10)], &[(4, 0)]));
|
||||||
|
assert!(overlaps(&[(0, 4), (10, 7)], &[(3, 6)]));
|
||||||
|
assert!(overlaps(&[(0, 4), (10, 7)], &[(6, 3)]));
|
||||||
|
assert!(overlaps(&[(4, 0), (10, 7)], &[(3, 6)]));
|
||||||
|
assert!(overlaps(&[(4, 0), (10, 7)], &[(6, 3)]));
|
||||||
|
assert!(overlaps(&[(3, 6), (10, 7)], &[(0, 4)]));
|
||||||
|
assert!(overlaps(&[(3, 6), (10, 7)], &[(4, 0)]));
|
||||||
|
assert!(overlaps(&[(6, 3), (10, 7)], &[(0, 4)]));
|
||||||
|
assert!(overlaps(&[(6, 3), (10, 7)], &[(4, 0)]));
|
||||||
|
|
||||||
|
assert!(overlaps(&[(0, 4)], &[(3, 6), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(0, 4)], &[(6, 3), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(4, 0)], &[(3, 6), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(4, 0)], &[(6, 3), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(3, 6)], &[(0, 4), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(3, 6)], &[(4, 0), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(6, 3)], &[(0, 4), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(6, 3)], &[(4, 0), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(0, 4)], &[(3, 6), (10, 7)]));
|
||||||
|
assert!(overlaps(&[(0, 4)], &[(6, 3), (10, 7)]));
|
||||||
|
assert!(overlaps(&[(4, 0)], &[(3, 6), (10, 7)]));
|
||||||
|
assert!(overlaps(&[(4, 0)], &[(6, 3), (10, 7)]));
|
||||||
|
assert!(overlaps(&[(3, 6)], &[(0, 4), (10, 7)]));
|
||||||
|
assert!(overlaps(&[(3, 6)], &[(4, 0), (10, 7)]));
|
||||||
|
assert!(overlaps(&[(6, 3)], &[(0, 4), (10, 7)]));
|
||||||
|
assert!(overlaps(&[(6, 3)], &[(4, 0), (10, 7)]));
|
||||||
|
|
||||||
|
// Zero-width and non-zero-width range, no overlap.
|
||||||
|
assert!(!overlaps(&[(0, 3)], &[(3, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 0)], &[(3, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 3)], &[(0, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 3)], &[(3, 0)]));
|
||||||
|
|
||||||
|
assert!(!overlaps(&[(0, 3), (7, 10)], &[(3, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 0), (7, 10)], &[(3, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 3), (7, 10)], &[(0, 3)]));
|
||||||
|
assert!(!overlaps(&[(3, 3), (7, 10)], &[(3, 0)]));
|
||||||
|
|
||||||
|
assert!(!overlaps(&[(0, 3)], &[(3, 3), (7, 10)]));
|
||||||
|
assert!(!overlaps(&[(3, 0)], &[(3, 3), (7, 10)]));
|
||||||
|
assert!(!overlaps(&[(3, 3)], &[(0, 3), (7, 10)]));
|
||||||
|
assert!(!overlaps(&[(3, 3)], &[(3, 0), (7, 10)]));
|
||||||
|
|
||||||
|
// Zero-width and non-zero-width range, overlap.
|
||||||
|
assert!(overlaps(&[(1, 4)], &[(1, 1)]));
|
||||||
|
assert!(overlaps(&[(4, 1)], &[(1, 1)]));
|
||||||
|
assert!(overlaps(&[(1, 1)], &[(1, 4)]));
|
||||||
|
assert!(overlaps(&[(1, 1)], &[(4, 1)]));
|
||||||
|
|
||||||
|
assert!(overlaps(&[(1, 4)], &[(3, 3)]));
|
||||||
|
assert!(overlaps(&[(4, 1)], &[(3, 3)]));
|
||||||
|
assert!(overlaps(&[(3, 3)], &[(1, 4)]));
|
||||||
|
assert!(overlaps(&[(3, 3)], &[(4, 1)]));
|
||||||
|
|
||||||
|
assert!(overlaps(&[(1, 4), (7, 10)], &[(1, 1)]));
|
||||||
|
assert!(overlaps(&[(4, 1), (7, 10)], &[(1, 1)]));
|
||||||
|
assert!(overlaps(&[(1, 1), (7, 10)], &[(1, 4)]));
|
||||||
|
assert!(overlaps(&[(1, 1), (7, 10)], &[(4, 1)]));
|
||||||
|
|
||||||
|
assert!(overlaps(&[(1, 4), (7, 10)], &[(3, 3)]));
|
||||||
|
assert!(overlaps(&[(4, 1), (7, 10)], &[(3, 3)]));
|
||||||
|
assert!(overlaps(&[(3, 3), (7, 10)], &[(1, 4)]));
|
||||||
|
assert!(overlaps(&[(3, 3), (7, 10)], &[(4, 1)]));
|
||||||
|
|
||||||
|
assert!(overlaps(&[(1, 4)], &[(1, 1), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(4, 1)], &[(1, 1), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(1, 1)], &[(1, 4), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(1, 1)], &[(4, 1), (7, 10)]));
|
||||||
|
|
||||||
|
assert!(overlaps(&[(1, 4)], &[(3, 3), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(4, 1)], &[(3, 3), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(3, 3)], &[(1, 4), (7, 10)]));
|
||||||
|
assert!(overlaps(&[(3, 3)], &[(4, 1), (7, 10)]));
|
||||||
|
|
||||||
|
// Two zero-width ranges, no overlap.
|
||||||
|
assert!(!overlaps(&[(0, 0)], &[(1, 1)]));
|
||||||
|
assert!(!overlaps(&[(1, 1)], &[(0, 0)]));
|
||||||
|
|
||||||
|
assert!(!overlaps(&[(0, 0), (2, 2)], &[(1, 1)]));
|
||||||
|
assert!(!overlaps(&[(0, 0), (2, 2)], &[(1, 1)]));
|
||||||
|
assert!(!overlaps(&[(1, 1)], &[(0, 0), (2, 2)]));
|
||||||
|
assert!(!overlaps(&[(1, 1)], &[(0, 0), (2, 2)]));
|
||||||
|
|
||||||
|
// Two zero-width ranges, overlap.
|
||||||
|
assert!(overlaps(&[(1, 1)], &[(1, 1)]));
|
||||||
|
assert!(overlaps(&[(1, 1), (2, 2)], &[(1, 1)]));
|
||||||
|
assert!(overlaps(&[(1, 1)], &[(1, 1), (2, 2)]));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_grapheme_aligned() {
|
fn test_grapheme_aligned() {
|
||||||
let r = Rope::from_str("\r\nHi\r\n");
|
let r = Rope::from_str("\r\nHi\r\n");
|
||||||
|
@ -1378,9 +1675,15 @@ mod test {
|
||||||
// multiple matches
|
// multiple matches
|
||||||
assert!(contains(vec!((1, 1), (2, 2)), vec!((1, 1), (2, 2))));
|
assert!(contains(vec!((1, 1), (2, 2)), vec!((1, 1), (2, 2))));
|
||||||
|
|
||||||
// smaller set can't contain bigger
|
// extra items out of range
|
||||||
assert!(!contains(vec!((1, 1)), vec!((1, 1), (2, 2))));
|
assert!(!contains(vec!((1, 1)), vec!((1, 1), (2, 2))));
|
||||||
|
|
||||||
|
// one big range can contain multiple smaller ranges
|
||||||
|
assert!(contains(
|
||||||
|
vec!((1, 10)),
|
||||||
|
vec!((1, 1), (2, 2), (3, 3), (3, 5), (7, 10))
|
||||||
|
));
|
||||||
|
|
||||||
assert!(contains(
|
assert!(contains(
|
||||||
vec!((1, 1), (2, 4), (5, 6), (7, 9), (10, 13)),
|
vec!((1, 1), (2, 4), (5, 6), (7, 9), (10, 13)),
|
||||||
vec!((3, 4), (7, 9))
|
vec!((3, 4), (7, 9))
|
||||||
|
@ -1393,4 +1696,143 @@ mod test {
|
||||||
vec!((1, 2), (3, 4), (7, 9))
|
vec!((1, 2), (3, 4), (7, 9))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_selection_without() {
|
||||||
|
let without = |one, two, expected: Option<_>| {
|
||||||
|
println!("one: {:?}", one);
|
||||||
|
println!("two: {:?}", two);
|
||||||
|
println!("expected: {:?}", expected);
|
||||||
|
|
||||||
|
let (one_text, one_sel) = test::print(one);
|
||||||
|
let (two_text, two_sel) = test::print(two);
|
||||||
|
assert_eq!(one_text, two_text); // sanity
|
||||||
|
let actual_sel = one_sel.without(&two_sel);
|
||||||
|
|
||||||
|
let expected_sel = expected.map(|exp| {
|
||||||
|
let (expected_text, expected_sel) = test::print(exp);
|
||||||
|
assert_eq!(two_text, expected_text);
|
||||||
|
expected_sel
|
||||||
|
});
|
||||||
|
let actual = actual_sel
|
||||||
|
.as_ref()
|
||||||
|
.map(|sel| test::plain(two_text.to_string(), sel));
|
||||||
|
|
||||||
|
println!("actual: {:?}\n\n", actual);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
expected_sel,
|
||||||
|
actual_sel,
|
||||||
|
"expected: {:?}, got: {:?}",
|
||||||
|
expected_sel
|
||||||
|
.as_ref()
|
||||||
|
.map(|sel| test::plain(two_text.to_string(), sel)),
|
||||||
|
actual,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
without(
|
||||||
|
"#[foo bar baz|]#",
|
||||||
|
"foo #[bar|]# baz",
|
||||||
|
Some("#[foo |]#bar#( baz|)#"),
|
||||||
|
);
|
||||||
|
|
||||||
|
without("#[foo bar baz|]#", "#[foo bar baz|]#", None);
|
||||||
|
without("#[foo bar|]# baz", "#[foo bar baz|]#", None);
|
||||||
|
without("foo #[bar|]# baz", "#[foo bar baz|]#", None);
|
||||||
|
|
||||||
|
// direction is preserved
|
||||||
|
without(
|
||||||
|
"#[|foo bar baz]#",
|
||||||
|
"foo #[|bar]# baz",
|
||||||
|
Some("#[|foo ]#bar#(| baz)#"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// preference for direction is given to the left
|
||||||
|
without(
|
||||||
|
"#[|foo bar baz]#",
|
||||||
|
"foo #[bar|]# baz",
|
||||||
|
Some("#[|foo ]#bar#(| baz)#"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// disjoint ranges on the right are ignored
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# baz",
|
||||||
|
"foo bar #[baz|]#",
|
||||||
|
Some("#[foo bar|]# baz"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# baz",
|
||||||
|
"foo bar#[ baz|]#",
|
||||||
|
Some("#[foo bar|]# baz"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"#(foo|)# #[bar|]# baz",
|
||||||
|
"foo#[ b|]#ar ba#(z|)#",
|
||||||
|
Some("#(foo|)# b#[ar|]# baz"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ranges contained by those one on the right are removed
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# #(b|)#az",
|
||||||
|
"foo bar#[ b|]#a#(z|)#",
|
||||||
|
Some("#[foo bar|]# baz"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"#[foo|]# bar #(baz|)#",
|
||||||
|
"foo bar#[ b|]#a#(z|)#",
|
||||||
|
Some("#[foo|]# bar b#(a|)#z"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# #(b|)#az",
|
||||||
|
"foo bar #[b|]#a#(z|)#",
|
||||||
|
Some("#[foo bar|]# baz"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# #(b|)#a#(z|)#",
|
||||||
|
"foo bar #[b|]#a#(z|)#",
|
||||||
|
Some("#[foo bar|]# baz"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# #(b|)#a#(z|)#",
|
||||||
|
"foo bar #[b|]#a#(z|)#",
|
||||||
|
Some("#[foo bar|]# baz"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// more than one range intersected by a single range on the right
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# #(baz|)#",
|
||||||
|
"foo b#[ar ba|]#z",
|
||||||
|
Some("#[foo b|]#ar ba#(z|)#"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// partial overlap
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# baz",
|
||||||
|
"foo #[bar baz|]#",
|
||||||
|
Some("#[foo |]#bar baz"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# baz",
|
||||||
|
"foo#[ bar baz|]#",
|
||||||
|
Some("#[foo|]# bar baz"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"#[foo bar|]# baz",
|
||||||
|
"foo ba#[r baz|]#",
|
||||||
|
Some("#[foo ba|]#r baz"),
|
||||||
|
);
|
||||||
|
without(
|
||||||
|
"foo ba#[r baz|]#",
|
||||||
|
"#[foo bar|]# baz",
|
||||||
|
Some("foo bar#[ baz|]#"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// primary selection is moved - preference given to the left of a split
|
||||||
|
without(
|
||||||
|
"#(|foo)# #(|bar baz)# #[|quux]#",
|
||||||
|
"f#(o|)#o ba#[r b|]#az q#(uu|)#x",
|
||||||
|
Some("#(|f)#o#(|o)# #(|ba)#r b#(|az)# #[|q]#uu#(|x)#"),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -526,6 +526,7 @@ impl MappableCommand {
|
||||||
select_prev_sibling, "Select previous sibling the in syntax tree",
|
select_prev_sibling, "Select previous sibling the in syntax tree",
|
||||||
select_all_siblings, "Select all siblings of the current node",
|
select_all_siblings, "Select all siblings of the current node",
|
||||||
select_all_children, "Select all children of the current node",
|
select_all_children, "Select all children of the current node",
|
||||||
|
expand_selection_around, "Expand selection to parent syntax node, but exclude the selection you started with",
|
||||||
jump_forward, "Jump forward on jumplist",
|
jump_forward, "Jump forward on jumplist",
|
||||||
jump_backward, "Jump backward on jumplist",
|
jump_backward, "Jump backward on jumplist",
|
||||||
save_selection, "Save current selection to jumplist",
|
save_selection, "Save current selection to jumplist",
|
||||||
|
@ -3936,6 +3937,7 @@ fn goto_first_diag(cx: &mut Context) {
|
||||||
Some(diag) => Selection::single(diag.range.start, diag.range.end),
|
Some(diag) => Selection::single(diag.range.start, diag.range.end),
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
doc.set_selection(view.id, selection);
|
doc.set_selection(view.id, selection);
|
||||||
view.diagnostics_handler
|
view.diagnostics_handler
|
||||||
.immediately_show_diagnostic(doc, view.id);
|
.immediately_show_diagnostic(doc, view.id);
|
||||||
|
@ -3947,6 +3949,7 @@ fn goto_last_diag(cx: &mut Context) {
|
||||||
Some(diag) => Selection::single(diag.range.start, diag.range.end),
|
Some(diag) => Selection::single(diag.range.start, diag.range.end),
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
doc.set_selection(view.id, selection);
|
doc.set_selection(view.id, selection);
|
||||||
view.diagnostics_handler
|
view.diagnostics_handler
|
||||||
.immediately_show_diagnostic(doc, view.id);
|
.immediately_show_diagnostic(doc, view.id);
|
||||||
|
@ -4003,6 +4006,7 @@ fn goto_prev_diag(cx: &mut Context) {
|
||||||
view.diagnostics_handler
|
view.diagnostics_handler
|
||||||
.immediately_show_diagnostic(doc, view.id);
|
.immediately_show_diagnostic(doc, view.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.editor.apply_motion(motion)
|
cx.editor.apply_motion(motion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5374,6 +5378,10 @@ fn reverse_selection_contents(cx: &mut Context) {
|
||||||
|
|
||||||
// tree sitter node selection
|
// tree sitter node selection
|
||||||
|
|
||||||
|
const EXPAND_KEY: &str = "expand";
|
||||||
|
const EXPAND_AROUND_BASE_KEY: &str = "expand_around_base";
|
||||||
|
const PARENTS_KEY: &str = "parents";
|
||||||
|
|
||||||
fn expand_selection(cx: &mut Context) {
|
fn expand_selection(cx: &mut Context) {
|
||||||
let motion = |editor: &mut Editor| {
|
let motion = |editor: &mut Editor| {
|
||||||
let (view, doc) = current!(editor);
|
let (view, doc) = current!(editor);
|
||||||
|
@ -5381,42 +5389,154 @@ fn expand_selection(cx: &mut Context) {
|
||||||
if let Some(syntax) = doc.syntax() {
|
if let Some(syntax) = doc.syntax() {
|
||||||
let text = doc.text().slice(..);
|
let text = doc.text().slice(..);
|
||||||
|
|
||||||
let current_selection = doc.selection(view.id);
|
let current_selection = doc.selection(view.id).clone();
|
||||||
let selection = object::expand_selection(syntax, text, current_selection.clone());
|
let selection = object::expand_selection(syntax, text, current_selection.clone());
|
||||||
|
|
||||||
// check if selection is different from the last one
|
// check if selection is different from the last one
|
||||||
if *current_selection != selection {
|
if current_selection != selection {
|
||||||
// save current selection so it can be restored using shrink_selection
|
let prev_selections = doc
|
||||||
view.object_selections.push(current_selection.clone());
|
.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(EXPAND_KEY)
|
||||||
|
.or_default();
|
||||||
|
|
||||||
doc.set_selection(view.id, selection);
|
// save current selection so it can be restored using shrink_selection
|
||||||
|
prev_selections.push(current_selection);
|
||||||
|
doc.set_selection_clear(view.id, selection, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.editor.apply_motion(motion);
|
cx.editor.apply_motion(motion);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shrink_selection(cx: &mut Context) {
|
fn shrink_selection(cx: &mut Context) {
|
||||||
let motion = |editor: &mut Editor| {
|
let motion = |editor: &mut Editor| {
|
||||||
let (view, doc) = current!(editor);
|
let (view, doc) = current!(editor);
|
||||||
let current_selection = doc.selection(view.id);
|
let current_selection = doc.selection(view.id).clone();
|
||||||
|
let prev_expansions = doc
|
||||||
|
.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(EXPAND_KEY)
|
||||||
|
.or_default();
|
||||||
|
|
||||||
// try to restore previous selection
|
// try to restore previous selection
|
||||||
if let Some(prev_selection) = view.object_selections.pop() {
|
if let Some(prev_selection) = prev_expansions.pop() {
|
||||||
if current_selection.contains(&prev_selection) {
|
// allow shrinking the selection only if current selection contains the previous object selection
|
||||||
doc.set_selection(view.id, prev_selection);
|
doc.set_selection_clear(view.id, prev_selection, false);
|
||||||
|
|
||||||
|
// Do a corresponding pop of the parents from `expand_selection_around`
|
||||||
|
doc.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(PARENTS_KEY)
|
||||||
|
.and_modify(|parents| {
|
||||||
|
parents.pop();
|
||||||
|
});
|
||||||
|
|
||||||
|
// need to do this again because borrowing
|
||||||
|
let prev_expansions = doc
|
||||||
|
.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(EXPAND_KEY)
|
||||||
|
.or_default();
|
||||||
|
|
||||||
|
// if we've emptied out the previous expansions, then clear out the
|
||||||
|
// base history as well so it doesn't get used again erroneously
|
||||||
|
if prev_expansions.is_empty() {
|
||||||
|
doc.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(EXPAND_AROUND_BASE_KEY)
|
||||||
|
.and_modify(|base| {
|
||||||
|
base.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
// clear existing selection as they can't be shrunk to anyway
|
|
||||||
view.object_selections.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if not previous selection, shrink to first child
|
// if not previous selection, shrink to first child
|
||||||
if let Some(syntax) = doc.syntax() {
|
if let Some(syntax) = doc.syntax() {
|
||||||
let text = doc.text().slice(..);
|
let text = doc.text().slice(..);
|
||||||
let selection = object::shrink_selection(syntax, text, current_selection.clone());
|
let selection = object::shrink_selection(syntax, text, current_selection);
|
||||||
doc.set_selection(view.id, selection);
|
doc.set_selection_clear(view.id, selection, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
cx.editor.apply_motion(motion);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_selection_around(cx: &mut Context) {
|
||||||
|
let motion = |editor: &mut Editor| {
|
||||||
|
let (view, doc) = current!(editor);
|
||||||
|
|
||||||
|
if doc.syntax().is_some() {
|
||||||
|
// [NOTE] we do this pop and push dance because if we don't take
|
||||||
|
// ownership of the objects, then we require multiple
|
||||||
|
// mutable references to the view's object selections
|
||||||
|
let mut parents_selection = doc
|
||||||
|
.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(PARENTS_KEY)
|
||||||
|
.or_default()
|
||||||
|
.pop();
|
||||||
|
|
||||||
|
let mut base_selection = doc
|
||||||
|
.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(EXPAND_AROUND_BASE_KEY)
|
||||||
|
.or_default()
|
||||||
|
.pop();
|
||||||
|
|
||||||
|
let current_selection = doc.selection(view.id).clone();
|
||||||
|
|
||||||
|
if parents_selection.is_none() || base_selection.is_none() {
|
||||||
|
parents_selection = Some(current_selection.clone());
|
||||||
|
base_selection = Some(current_selection.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = doc.text().slice(..);
|
||||||
|
let syntax = doc.syntax().unwrap();
|
||||||
|
|
||||||
|
let outside_selection =
|
||||||
|
object::expand_selection(syntax, text, parents_selection.clone().unwrap());
|
||||||
|
|
||||||
|
let target_selection = match outside_selection
|
||||||
|
.clone()
|
||||||
|
.without(&base_selection.clone().unwrap())
|
||||||
|
{
|
||||||
|
Some(sel) => sel,
|
||||||
|
None => outside_selection.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// check if selection is different from the last one
|
||||||
|
if target_selection != current_selection {
|
||||||
|
// save current selection so it can be restored using shrink_selection
|
||||||
|
doc.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(EXPAND_KEY)
|
||||||
|
.or_default()
|
||||||
|
.push(current_selection);
|
||||||
|
|
||||||
|
doc.set_selection_clear(view.id, target_selection, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let parents = doc
|
||||||
|
.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(PARENTS_KEY)
|
||||||
|
.or_default();
|
||||||
|
|
||||||
|
parents.push(parents_selection.unwrap());
|
||||||
|
parents.push(outside_selection);
|
||||||
|
|
||||||
|
doc.view_data_mut(view.id)
|
||||||
|
.object_selections
|
||||||
|
.entry(EXPAND_AROUND_BASE_KEY)
|
||||||
|
.or_default()
|
||||||
|
.push(base_selection.unwrap());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
cx.editor.apply_motion(motion);
|
cx.editor.apply_motion(motion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5434,6 +5554,7 @@ where
|
||||||
doc.set_selection(view.id, selection);
|
doc.set_selection(view.id, selection);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.editor.apply_motion(motion);
|
cx.editor.apply_motion(motion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5535,8 +5656,6 @@ fn match_brackets(cx: &mut Context) {
|
||||||
doc.set_selection(view.id, selection);
|
doc.set_selection(view.id, selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
fn jump_forward(cx: &mut Context) {
|
fn jump_forward(cx: &mut Context) {
|
||||||
let count = cx.count();
|
let count = cx.count();
|
||||||
let config = cx.editor.config();
|
let config = cx.editor.config();
|
||||||
|
|
|
@ -87,6 +87,7 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
|
||||||
";" => collapse_selection,
|
";" => collapse_selection,
|
||||||
"A-;" => flip_selections,
|
"A-;" => flip_selections,
|
||||||
"A-o" | "A-up" => expand_selection,
|
"A-o" | "A-up" => expand_selection,
|
||||||
|
"A-O" => expand_selection_around,
|
||||||
"A-i" | "A-down" => shrink_selection,
|
"A-i" | "A-down" => shrink_selection,
|
||||||
"A-I" | "A-S-down" => select_all_children,
|
"A-I" | "A-S-down" => select_all_children,
|
||||||
"A-p" | "A-left" => select_prev_sibling,
|
"A-p" | "A-left" => select_prev_sibling,
|
||||||
|
|
|
@ -948,3 +948,198 @@ async fn match_bracket() -> anyhow::Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn expand_shrink_selection() -> anyhow::Result<()> {
|
||||||
|
let tests = vec![
|
||||||
|
// single range
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
Some(#[thing|]#)
|
||||||
|
"##},
|
||||||
|
"<A-o><A-o>",
|
||||||
|
indoc! {r##"
|
||||||
|
#[Some(thing)|]#
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
// multi range
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
Some(#[thing|]#)
|
||||||
|
Some(#(other_thing|)#)
|
||||||
|
"##},
|
||||||
|
"<A-o>",
|
||||||
|
indoc! {r##"
|
||||||
|
Some#[(thing)|]#
|
||||||
|
Some#((other_thing)|)#
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
// multi range collision merges
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
"<A-o><A-o><A-o>",
|
||||||
|
indoc! {r##"
|
||||||
|
#[(
|
||||||
|
Some(thing),
|
||||||
|
Some(other_thing),
|
||||||
|
)|]#
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
// multi range collision merges, then shrinks back to original
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
"<A-o><A-o><A-o><A-i>",
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
#[Some(thing)|]#,
|
||||||
|
#(Some(other_thing)|)#,
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
"<A-o><A-o><A-o><A-i><A-i>",
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some#[(thing)|]#,
|
||||||
|
Some#((other_thing)|)#,
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
"<A-o><A-o><A-o><A-i><A-i><A-i>",
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
// shrink with no expansion history defaults to first child
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
#[(
|
||||||
|
Some(thing),
|
||||||
|
Some(other_thing),
|
||||||
|
)|]#
|
||||||
|
"##},
|
||||||
|
"<A-i>",
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
#[Some(thing)|]#,
|
||||||
|
Some(other_thing),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
// any movement cancels selection history and falls back to first child
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
|
||||||
|
"##},
|
||||||
|
"<A-o><A-o><A-o>jkvkkk<A-i>",
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
#[|Some(thing)]#,
|
||||||
|
Some(other_thing),
|
||||||
|
)
|
||||||
|
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for test in tests {
|
||||||
|
test_with_config(AppBuilder::new().with_file("foo.rs", None), test).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn expand_selection_around() -> anyhow::Result<()> {
|
||||||
|
let tests = vec![
|
||||||
|
// single cursor stays single cursor, first goes to end of current
|
||||||
|
// node, then parent
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
Some(#[thing|]#)
|
||||||
|
"##},
|
||||||
|
"<A-O><A-O>",
|
||||||
|
indoc! {r##"
|
||||||
|
#[Some(|]#thing#()|)#
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
// shrinking restores previous selection
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
Some(#[thing|]#)
|
||||||
|
"##},
|
||||||
|
"<A-O><A-O><A-i><A-i>",
|
||||||
|
indoc! {r##"
|
||||||
|
Some(#[thing|]#)
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
// multi range collision merges expand as normal, except with the
|
||||||
|
// original selection removed from the result
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
"<A-O><A-O><A-O>",
|
||||||
|
indoc! {r##"
|
||||||
|
#[(
|
||||||
|
Some(|]#thing#(),
|
||||||
|
Some(|)#other_thing#(),
|
||||||
|
)|)#
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
"<A-O><A-O><A-O><A-i><A-i><A-i>",
|
||||||
|
indoc! {r##"
|
||||||
|
(
|
||||||
|
Some(#[thing|]#),
|
||||||
|
Some(#(other_thing|)#),
|
||||||
|
)
|
||||||
|
"##},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for test in tests {
|
||||||
|
test_with_config(AppBuilder::new().with_file("foo.rs", None), test).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -1320,15 +1320,27 @@ impl Document {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Select text within the [`Document`].
|
/// Select text within the [`Document`], optionally clearing the previous selection state.
|
||||||
pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
|
pub fn set_selection_clear(&mut self, view_id: ViewId, selection: Selection, clear_prev: bool) {
|
||||||
// TODO: use a transaction?
|
// TODO: use a transaction?
|
||||||
self.selections
|
self.selections
|
||||||
.insert(view_id, selection.ensure_invariants(self.text().slice(..)));
|
.insert(view_id, selection.ensure_invariants(self.text().slice(..)));
|
||||||
|
|
||||||
helix_event::dispatch(SelectionDidChange {
|
helix_event::dispatch(SelectionDidChange {
|
||||||
doc: self,
|
doc: self,
|
||||||
view: view_id,
|
view: view_id,
|
||||||
})
|
});
|
||||||
|
|
||||||
|
if clear_prev {
|
||||||
|
self.view_data
|
||||||
|
.entry(view_id)
|
||||||
|
.and_modify(|view_data| view_data.object_selections.clear());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Select text within the [`Document`].
|
||||||
|
pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
|
||||||
|
self.set_selection_clear(view_id, selection, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the origin selection of the text in a document, i.e. where
|
/// Find the origin selection of the text in a document, i.e. where
|
||||||
|
@ -1520,6 +1532,12 @@ impl Document {
|
||||||
apply_inlay_hint_changes(padding_after_inlay_hints);
|
apply_inlay_hint_changes(padding_after_inlay_hints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clear out all associated view object selections, as they are no
|
||||||
|
// longer valid
|
||||||
|
self.view_data
|
||||||
|
.values_mut()
|
||||||
|
.for_each(|view_data| view_data.object_selections.clear());
|
||||||
|
|
||||||
helix_event::dispatch(DocumentDidChange {
|
helix_event::dispatch(DocumentDidChange {
|
||||||
doc: self,
|
doc: self,
|
||||||
view: view_id,
|
view: view_id,
|
||||||
|
@ -1962,13 +1980,13 @@ impl Document {
|
||||||
&self.selections
|
&self.selections
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_data(&self, view_id: ViewId) -> &ViewData {
|
pub fn view_data(&self, view_id: ViewId) -> &ViewData {
|
||||||
self.view_data
|
self.view_data
|
||||||
.get(&view_id)
|
.get(&view_id)
|
||||||
.expect("This should only be called after ensure_view_init")
|
.expect("This should only be called after ensure_view_init")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_data_mut(&mut self, view_id: ViewId) -> &mut ViewData {
|
pub fn view_data_mut(&mut self, view_id: ViewId) -> &mut ViewData {
|
||||||
self.view_data.entry(view_id).or_default()
|
self.view_data.entry(view_id).or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2286,9 +2304,13 @@ impl Document {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stores data needed for views that are tied to this specific Document.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct ViewData {
|
pub struct ViewData {
|
||||||
view_position: ViewPosition,
|
view_position: ViewPosition,
|
||||||
|
|
||||||
|
/// used to store previous selections of tree-sitter objects
|
||||||
|
pub object_selections: HashMap<&'static str, Vec<Selection>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -2339,6 +2361,7 @@ mod test {
|
||||||
Arc::new(ArcSwap::from_pointee(syntax::Loader::default())),
|
Arc::new(ArcSwap::from_pointee(syntax::Loader::default())),
|
||||||
);
|
);
|
||||||
let view = ViewId::default();
|
let view = ViewId::default();
|
||||||
|
doc.ensure_view_init(view);
|
||||||
doc.set_selection(view, Selection::single(0, 0));
|
doc.set_selection(view, Selection::single(0, 0));
|
||||||
|
|
||||||
let transaction =
|
let transaction =
|
||||||
|
@ -2377,7 +2400,9 @@ mod test {
|
||||||
Arc::new(ArcSwap::new(Arc::new(Config::default()))),
|
Arc::new(ArcSwap::new(Arc::new(Config::default()))),
|
||||||
Arc::new(ArcSwap::from_pointee(syntax::Loader::default())),
|
Arc::new(ArcSwap::from_pointee(syntax::Loader::default())),
|
||||||
);
|
);
|
||||||
|
|
||||||
let view = ViewId::default();
|
let view = ViewId::default();
|
||||||
|
doc.ensure_view_init(view);
|
||||||
doc.set_selection(view, Selection::single(5, 5));
|
doc.set_selection(view, Selection::single(5, 5));
|
||||||
|
|
||||||
// insert
|
// insert
|
||||||
|
|
|
@ -137,8 +137,6 @@ pub struct View {
|
||||||
// uses two docs because we want to be able to swap between the
|
// uses two docs because we want to be able to swap between the
|
||||||
// two last modified docs which we need to manually keep track of
|
// two last modified docs which we need to manually keep track of
|
||||||
pub last_modified_docs: [Option<DocumentId>; 2],
|
pub last_modified_docs: [Option<DocumentId>; 2],
|
||||||
/// used to store previous selections of tree-sitter objects
|
|
||||||
pub object_selections: Vec<Selection>,
|
|
||||||
/// all gutter-related configuration settings, used primarily for gutter rendering
|
/// all gutter-related configuration settings, used primarily for gutter rendering
|
||||||
pub gutters: GutterConfig,
|
pub gutters: GutterConfig,
|
||||||
/// A mapping between documents and the last history revision the view was updated at.
|
/// A mapping between documents and the last history revision the view was updated at.
|
||||||
|
@ -175,7 +173,6 @@ impl View {
|
||||||
jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel
|
jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel
|
||||||
docs_access_history: Vec::new(),
|
docs_access_history: Vec::new(),
|
||||||
last_modified_docs: [None, None],
|
last_modified_docs: [None, None],
|
||||||
object_selections: Vec::new(),
|
|
||||||
gutters,
|
gutters,
|
||||||
doc_revisions: HashMap::new(),
|
doc_revisions: HashMap::new(),
|
||||||
diagnostics_handler: DiagnosticsHandler::new(),
|
diagnostics_handler: DiagnosticsHandler::new(),
|
||||||
|
|
Loading…
Reference in New Issue