Merge pull request #256 from zed-industries/select-next
Implement select next (cmd-d) and replace selection with next (cmd-k cmd-d)
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1521,6 +1521,7 @@ dependencies = [
|
||||
name = "editor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
"buffer",
|
||||
"clock",
|
||||
|
||||
@@ -29,7 +29,9 @@ pub struct AnchorSet(pub(crate) AnchorMap<()>);
|
||||
#[derive(Clone)]
|
||||
pub struct AnchorRangeMap<T> {
|
||||
pub(crate) version: clock::Global,
|
||||
pub(crate) entries: Vec<(Range<(FullOffset, Bias)>, T)>,
|
||||
pub(crate) entries: Vec<(Range<FullOffset>, T)>,
|
||||
pub(crate) start_bias: Bias,
|
||||
pub(crate) end_bias: Bias,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -140,8 +142,12 @@ impl<T> AnchorMap<T> {
|
||||
{
|
||||
let content = content.into();
|
||||
content
|
||||
.summaries_for_anchors(self)
|
||||
.map(move |(sum, value)| (sum, value))
|
||||
.summaries_for_anchors(
|
||||
self.version.clone(),
|
||||
self.bias,
|
||||
self.entries.iter().map(|e| &e.0),
|
||||
)
|
||||
.zip(self.entries.iter().map(|e| &e.1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,9 +180,16 @@ impl<T> AnchorRangeMap<T> {
|
||||
|
||||
pub fn from_full_offset_ranges(
|
||||
version: clock::Global,
|
||||
entries: Vec<(Range<(FullOffset, Bias)>, T)>,
|
||||
start_bias: Bias,
|
||||
end_bias: Bias,
|
||||
entries: Vec<(Range<FullOffset>, T)>,
|
||||
) -> Self {
|
||||
Self { version, entries }
|
||||
Self {
|
||||
version,
|
||||
start_bias,
|
||||
end_bias,
|
||||
entries,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ranges<'a, D>(
|
||||
@@ -187,13 +200,53 @@ impl<T> AnchorRangeMap<T> {
|
||||
D: 'a + TextDimension<'a>,
|
||||
{
|
||||
let content = content.into();
|
||||
content.summaries_for_anchor_ranges(self)
|
||||
content
|
||||
.summaries_for_anchor_ranges(
|
||||
self.version.clone(),
|
||||
self.start_bias,
|
||||
self.end_bias,
|
||||
self.entries.iter().map(|e| &e.0),
|
||||
)
|
||||
.zip(self.entries.iter().map(|e| &e.1))
|
||||
}
|
||||
|
||||
pub fn full_offset_ranges(&self) -> impl Iterator<Item = (Range<FullOffset>, &T)> {
|
||||
self.entries
|
||||
.iter()
|
||||
.map(|(range, value)| (range.start.0..range.end.0, value))
|
||||
pub fn intersecting_ranges<'a, D, I>(
|
||||
&'a self,
|
||||
range: Range<(I, Bias)>,
|
||||
content: impl Into<Content<'a>> + 'a,
|
||||
) -> impl Iterator<Item = (Range<D>, &'a T)> + 'a
|
||||
where
|
||||
D: 'a + TextDimension<'a>,
|
||||
I: ToOffset,
|
||||
{
|
||||
let content = content.into();
|
||||
let range = content.anchor_at(range.start.0, range.start.1)
|
||||
..content.anchor_at(range.end.0, range.end.1);
|
||||
|
||||
let mut probe_anchor = Anchor {
|
||||
full_offset: Default::default(),
|
||||
bias: self.start_bias,
|
||||
version: self.version.clone(),
|
||||
};
|
||||
let start_ix = self.entries.binary_search_by(|probe| {
|
||||
probe_anchor.full_offset = probe.0.end;
|
||||
probe_anchor.cmp(&range.start, &content).unwrap()
|
||||
});
|
||||
|
||||
match start_ix {
|
||||
Ok(start_ix) | Err(start_ix) => content
|
||||
.summaries_for_anchor_ranges(
|
||||
self.version.clone(),
|
||||
self.start_bias,
|
||||
self.end_bias,
|
||||
self.entries[start_ix..].iter().map(|e| &e.0),
|
||||
)
|
||||
.zip(self.entries.iter().map(|e| &e.1)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn full_offset_ranges(&self) -> impl Iterator<Item = &(Range<FullOffset>, T)> {
|
||||
self.entries.iter()
|
||||
}
|
||||
|
||||
pub fn min_by_key<'a, C, D, F, K>(
|
||||
@@ -232,25 +285,19 @@ impl<T> AnchorRangeMap<T> {
|
||||
.map(|(range, value)| (self.resolve_range(range, &content), value))
|
||||
}
|
||||
|
||||
fn resolve_range<'a, D>(
|
||||
&self,
|
||||
range: &Range<(FullOffset, Bias)>,
|
||||
content: &Content<'a>,
|
||||
) -> Range<D>
|
||||
fn resolve_range<'a, D>(&self, range: &Range<FullOffset>, content: &Content<'a>) -> Range<D>
|
||||
where
|
||||
D: 'a + TextDimension<'a>,
|
||||
{
|
||||
let (start, start_bias) = range.start;
|
||||
let mut anchor = Anchor {
|
||||
full_offset: start,
|
||||
bias: start_bias,
|
||||
full_offset: range.start,
|
||||
bias: self.start_bias,
|
||||
version: self.version.clone(),
|
||||
};
|
||||
let start = content.summary_for_anchor(&anchor);
|
||||
|
||||
let (end, end_bias) = range.end;
|
||||
anchor.full_offset = end;
|
||||
anchor.bias = end_bias;
|
||||
anchor.full_offset = range.end;
|
||||
anchor.bias = self.end_bias;
|
||||
let end = content.summary_for_anchor(&anchor);
|
||||
|
||||
start..end
|
||||
|
||||
@@ -606,9 +606,8 @@ impl Buffer {
|
||||
self.text_for_range(range).flat_map(str::chars)
|
||||
}
|
||||
|
||||
pub fn bytes_at<T: ToOffset>(&self, position: T) -> impl Iterator<Item = u8> + '_ {
|
||||
let offset = position.to_offset(self);
|
||||
self.visible_text.bytes_at(offset)
|
||||
pub fn bytes_in_range<T: ToOffset>(&self, range: Range<T>) -> rope::Bytes {
|
||||
self.content().bytes_in_range(range)
|
||||
}
|
||||
|
||||
pub fn contains_str_at<T>(&self, position: T, needle: &str) -> bool
|
||||
@@ -618,7 +617,9 @@ impl Buffer {
|
||||
let position = position.to_offset(self);
|
||||
position == self.clip_offset(position, Bias::Left)
|
||||
&& self
|
||||
.bytes_at(position)
|
||||
.bytes_in_range(position..self.len())
|
||||
.flatten()
|
||||
.copied()
|
||||
.take(needle.len())
|
||||
.eq(needle.bytes())
|
||||
}
|
||||
@@ -1581,6 +1582,10 @@ impl Snapshot {
|
||||
self.visible_text.max_point()
|
||||
}
|
||||
|
||||
pub fn bytes_in_range<T: ToOffset>(&self, range: Range<T>) -> rope::Bytes {
|
||||
self.content().bytes_in_range(range)
|
||||
}
|
||||
|
||||
pub fn text_for_range<T: ToOffset>(&self, range: Range<T>) -> Chunks {
|
||||
self.content().text_for_range(range)
|
||||
}
|
||||
@@ -1716,6 +1721,12 @@ impl<'a> Content<'a> {
|
||||
self.visible_text.reversed_chars_at(offset)
|
||||
}
|
||||
|
||||
pub fn bytes_in_range<T: ToOffset>(&self, range: Range<T>) -> rope::Bytes<'a> {
|
||||
let start = range.start.to_offset(self);
|
||||
let end = range.end.to_offset(self);
|
||||
self.visible_text.bytes_in_range(start..end)
|
||||
}
|
||||
|
||||
pub fn text_for_range<T: ToOffset>(&self, range: Range<T>) -> Chunks<'a> {
|
||||
let start = range.start.to_offset(self);
|
||||
let end = range.end.to_offset(self);
|
||||
@@ -1775,66 +1786,67 @@ impl<'a> Content<'a> {
|
||||
self.visible_text.cursor(range.start).summary(range.end)
|
||||
}
|
||||
|
||||
fn summaries_for_anchors<D, T>(&self, map: &'a AnchorMap<T>) -> impl Iterator<Item = (D, &'a T)>
|
||||
fn summaries_for_anchors<D, I>(
|
||||
&self,
|
||||
version: clock::Global,
|
||||
bias: Bias,
|
||||
ranges: I,
|
||||
) -> impl 'a + Iterator<Item = D>
|
||||
where
|
||||
D: TextDimension<'a>,
|
||||
D: 'a + TextDimension<'a>,
|
||||
I: 'a + IntoIterator<Item = &'a FullOffset>,
|
||||
{
|
||||
let cx = Some(map.version.clone());
|
||||
let cx = Some(version.clone());
|
||||
let mut summary = D::default();
|
||||
let mut rope_cursor = self.visible_text.cursor(0);
|
||||
let mut cursor = self.fragments.cursor::<(VersionedFullOffset, usize)>();
|
||||
map.entries.iter().map(move |(offset, value)| {
|
||||
cursor.seek_forward(&VersionedFullOffset::Offset(*offset), map.bias, &cx);
|
||||
ranges.into_iter().map(move |offset| {
|
||||
cursor.seek_forward(&VersionedFullOffset::Offset(*offset), bias, &cx);
|
||||
let overshoot = if cursor.item().map_or(false, |fragment| fragment.visible) {
|
||||
*offset - cursor.start().0.full_offset()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
summary.add_assign(&rope_cursor.summary(cursor.start().1 + overshoot));
|
||||
(summary.clone(), value)
|
||||
summary.clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn summaries_for_anchor_ranges<D, T>(
|
||||
fn summaries_for_anchor_ranges<D, I>(
|
||||
&self,
|
||||
map: &'a AnchorRangeMap<T>,
|
||||
) -> impl Iterator<Item = (Range<D>, &'a T)>
|
||||
version: clock::Global,
|
||||
start_bias: Bias,
|
||||
end_bias: Bias,
|
||||
ranges: I,
|
||||
) -> impl 'a + Iterator<Item = Range<D>>
|
||||
where
|
||||
D: TextDimension<'a>,
|
||||
D: 'a + TextDimension<'a>,
|
||||
I: 'a + IntoIterator<Item = &'a Range<FullOffset>>,
|
||||
{
|
||||
let cx = Some(map.version.clone());
|
||||
let cx = Some(version);
|
||||
let mut summary = D::default();
|
||||
let mut rope_cursor = self.visible_text.cursor(0);
|
||||
let mut cursor = self.fragments.cursor::<(VersionedFullOffset, usize)>();
|
||||
map.entries.iter().map(move |(range, value)| {
|
||||
let Range {
|
||||
start: (start_offset, start_bias),
|
||||
end: (end_offset, end_bias),
|
||||
} = range;
|
||||
|
||||
cursor.seek_forward(
|
||||
&VersionedFullOffset::Offset(*start_offset),
|
||||
*start_bias,
|
||||
&cx,
|
||||
);
|
||||
ranges.into_iter().map(move |range| {
|
||||
cursor.seek_forward(&VersionedFullOffset::Offset(range.start), start_bias, &cx);
|
||||
let overshoot = if cursor.item().map_or(false, |fragment| fragment.visible) {
|
||||
*start_offset - cursor.start().0.full_offset()
|
||||
range.start - cursor.start().0.full_offset()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
summary.add_assign(&rope_cursor.summary::<D>(cursor.start().1 + overshoot));
|
||||
let start_summary = summary.clone();
|
||||
|
||||
cursor.seek_forward(&VersionedFullOffset::Offset(*end_offset), *end_bias, &cx);
|
||||
cursor.seek_forward(&VersionedFullOffset::Offset(range.end), end_bias, &cx);
|
||||
let overshoot = if cursor.item().map_or(false, |fragment| fragment.visible) {
|
||||
*end_offset - cursor.start().0.full_offset()
|
||||
range.end - cursor.start().0.full_offset()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
summary.add_assign(&rope_cursor.summary::<D>(cursor.start().1 + overshoot));
|
||||
let end_summary = summary.clone();
|
||||
|
||||
(start_summary..end_summary, value)
|
||||
start_summary..end_summary
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1890,14 +1902,16 @@ impl<'a> Content<'a> {
|
||||
let full_start_offset = FullOffset(cursor.start().deleted + start_offset);
|
||||
cursor.seek_forward(&end_offset, end_bias, &None);
|
||||
let full_end_offset = FullOffset(cursor.start().deleted + end_offset);
|
||||
(
|
||||
(full_start_offset, start_bias)..(full_end_offset, end_bias),
|
||||
value,
|
||||
)
|
||||
(full_start_offset..full_end_offset, value)
|
||||
})
|
||||
.collect();
|
||||
|
||||
AnchorRangeMap { version, entries }
|
||||
AnchorRangeMap {
|
||||
version,
|
||||
start_bias,
|
||||
end_bias,
|
||||
entries,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn anchor_set<E>(&self, bias: Bias, entries: E) -> AnchorSet
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::PointUtf16;
|
||||
use super::Point;
|
||||
use arrayvec::ArrayString;
|
||||
use smallvec::SmallVec;
|
||||
use std::{cmp, fmt, mem, ops::Range, str};
|
||||
use std::{cmp, fmt, io, mem, ops::Range, str};
|
||||
use sum_tree::{Bias, Dimension, SumTree};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -137,8 +137,8 @@ impl Rope {
|
||||
.flat_map(|chunk| chunk.chars().rev())
|
||||
}
|
||||
|
||||
pub fn bytes_at(&self, start: usize) -> impl Iterator<Item = u8> + '_ {
|
||||
self.chunks_in_range(start..self.len()).flat_map(str::bytes)
|
||||
pub fn bytes_in_range(&self, range: Range<usize>) -> Bytes {
|
||||
Bytes::new(self, range)
|
||||
}
|
||||
|
||||
pub fn chunks<'a>(&'a self) -> Chunks<'a> {
|
||||
@@ -444,6 +444,59 @@ impl<'a> Iterator for Chunks<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Bytes<'a> {
|
||||
chunks: sum_tree::Cursor<'a, Chunk, usize>,
|
||||
range: Range<usize>,
|
||||
}
|
||||
|
||||
impl<'a> Bytes<'a> {
|
||||
pub fn new(rope: &'a Rope, range: Range<usize>) -> Self {
|
||||
let mut chunks = rope.chunks.cursor();
|
||||
chunks.seek(&range.start, Bias::Right, &());
|
||||
Self { chunks, range }
|
||||
}
|
||||
|
||||
pub fn peek(&self) -> Option<&'a [u8]> {
|
||||
let chunk = self.chunks.item()?;
|
||||
let chunk_start = *self.chunks.start();
|
||||
if self.range.end <= chunk_start {
|
||||
return None;
|
||||
}
|
||||
|
||||
let start = self.range.start.saturating_sub(chunk_start);
|
||||
let end = self.range.end - chunk_start;
|
||||
Some(&chunk.0.as_bytes()[start..chunk.0.len().min(end)])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Bytes<'a> {
|
||||
type Item = &'a [u8];
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let result = self.peek();
|
||||
if result.is_some() {
|
||||
self.chunks.next(&());
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> io::Read for Bytes<'a> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
if let Some(chunk) = self.peek() {
|
||||
let len = cmp::min(buf.len(), chunk.len());
|
||||
buf[..len].copy_from_slice(&chunk[..len]);
|
||||
self.range.start += len;
|
||||
if len == chunk.len() {
|
||||
self.chunks.next(&());
|
||||
}
|
||||
Ok(len)
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct Chunk(ArrayString<{ 2 * CHUNK_BASE }>);
|
||||
|
||||
@@ -767,7 +820,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::random_char_iter::RandomCharIter;
|
||||
use rand::prelude::*;
|
||||
use std::{cmp::Ordering, env};
|
||||
use std::{cmp::Ordering, env, io::Read};
|
||||
use Bias::{Left, Right};
|
||||
|
||||
#[test]
|
||||
@@ -843,10 +896,16 @@ mod tests {
|
||||
for _ in 0..5 {
|
||||
let end_ix = clip_offset(&expected, rng.gen_range(0..=expected.len()), Right);
|
||||
let start_ix = clip_offset(&expected, rng.gen_range(0..=end_ix), Left);
|
||||
assert_eq!(
|
||||
actual.chunks_in_range(start_ix..end_ix).collect::<String>(),
|
||||
&expected[start_ix..end_ix]
|
||||
);
|
||||
|
||||
let actual_text = actual.chunks_in_range(start_ix..end_ix).collect::<String>();
|
||||
assert_eq!(actual_text, &expected[start_ix..end_ix]);
|
||||
|
||||
let mut actual_text = String::new();
|
||||
actual
|
||||
.bytes_in_range(start_ix..end_ix)
|
||||
.read_to_string(&mut actual_text)
|
||||
.unwrap();
|
||||
assert_eq!(actual_text, &expected[start_ix..end_ix]);
|
||||
|
||||
assert_eq!(
|
||||
actual
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use sum_tree::Bias;
|
||||
|
||||
use crate::rope::TextDimension;
|
||||
|
||||
use super::{AnchorRangeMap, Buffer, Content, Point, ToOffset, ToPoint};
|
||||
@@ -117,6 +119,27 @@ impl SelectionSet {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn intersecting_selections<'a, D, I, C>(
|
||||
&'a self,
|
||||
range: Range<(I, Bias)>,
|
||||
content: C,
|
||||
) -> impl 'a + Iterator<Item = Selection<D>>
|
||||
where
|
||||
D: 'a + TextDimension<'a>,
|
||||
I: 'a + ToOffset,
|
||||
C: 'a + Into<Content<'a>>,
|
||||
{
|
||||
self.selections
|
||||
.intersecting_ranges(range, content)
|
||||
.map(|(range, state)| Selection {
|
||||
id: state.id,
|
||||
start: range.start,
|
||||
end: range.end,
|
||||
reversed: state.reversed,
|
||||
goal: state.goal,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn oldest_selection<'a, D, C>(&'a self, content: C) -> Option<Selection<D>>
|
||||
where
|
||||
D: 'a + TextDimension<'a>,
|
||||
|
||||
@@ -20,6 +20,7 @@ sum_tree = { path = "../sum_tree" }
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
aho-corasick = "0.7"
|
||||
anyhow = "1.0"
|
||||
lazy_static = "1.4"
|
||||
log = "0.4"
|
||||
|
||||
@@ -418,6 +418,12 @@ impl DisplayPoint {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDisplayPoint for usize {
|
||||
fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint {
|
||||
map.point_to_display_point(self.to_point(&map.buffer_snapshot), Bias::Left)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDisplayPoint for Point {
|
||||
fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint {
|
||||
map.point_to_display_point(*self, Bias::Left)
|
||||
|
||||
@@ -380,13 +380,12 @@ impl EditorElement {
|
||||
|
||||
for selection in selections {
|
||||
if selection.start != selection.end {
|
||||
let range_start = cmp::min(selection.start, selection.end);
|
||||
let range_end = cmp::max(selection.start, selection.end);
|
||||
let row_range = if range_end.column() == 0 {
|
||||
cmp::max(range_start.row(), start_row)..cmp::min(range_end.row(), end_row)
|
||||
let row_range = if selection.end.column() == 0 {
|
||||
cmp::max(selection.start.row(), start_row)
|
||||
..cmp::min(selection.end.row(), end_row)
|
||||
} else {
|
||||
cmp::max(range_start.row(), start_row)
|
||||
..cmp::min(range_end.row() + 1, end_row)
|
||||
cmp::max(selection.start.row(), start_row)
|
||||
..cmp::min(selection.end.row() + 1, end_row)
|
||||
};
|
||||
|
||||
let selection = Selection {
|
||||
@@ -399,16 +398,18 @@ impl EditorElement {
|
||||
.map(|row| {
|
||||
let line_layout = &layout.line_layouts[(row - start_row) as usize];
|
||||
SelectionLine {
|
||||
start_x: if row == range_start.row() {
|
||||
start_x: if row == selection.start.row() {
|
||||
content_origin.x()
|
||||
+ line_layout.x_for_index(range_start.column() as usize)
|
||||
+ line_layout
|
||||
.x_for_index(selection.start.column() as usize)
|
||||
- scroll_left
|
||||
} else {
|
||||
content_origin.x() - scroll_left
|
||||
},
|
||||
end_x: if row == range_end.row() {
|
||||
end_x: if row == selection.end.row() {
|
||||
content_origin.x()
|
||||
+ line_layout.x_for_index(range_end.column() as usize)
|
||||
+ line_layout
|
||||
.x_for_index(selection.end.column() as usize)
|
||||
- scroll_left
|
||||
} else {
|
||||
content_origin.x()
|
||||
@@ -425,13 +426,13 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
if view.show_local_cursors() || *replica_id != local_replica_id {
|
||||
let cursor_position = selection.end;
|
||||
let cursor_position = selection.head();
|
||||
if (start_row..end_row).contains(&cursor_position.row()) {
|
||||
let cursor_row_layout =
|
||||
&layout.line_layouts[(selection.end.row() - start_row) as usize];
|
||||
let x = cursor_row_layout.x_for_index(selection.end.column() as usize)
|
||||
&layout.line_layouts[(cursor_position.row() - start_row) as usize];
|
||||
let x = cursor_row_layout.x_for_index(cursor_position.column() as usize)
|
||||
- scroll_left;
|
||||
let y = selection.end.row() as f32 * layout.line_height - scroll_top;
|
||||
let y = cursor_position.row() as f32 * layout.line_height - scroll_top;
|
||||
cursors.push(Cursor {
|
||||
color: style.cursor,
|
||||
origin: content_origin + vec2f(x, y),
|
||||
@@ -747,26 +748,18 @@ impl Element for EditorElement {
|
||||
self.update_view(cx.app, |view, cx| {
|
||||
highlighted_row = view.highlighted_row();
|
||||
for selection_set_id in view.active_selection_sets(cx).collect::<Vec<_>>() {
|
||||
let mut set = Vec::new();
|
||||
for selection in view.selections_in_range(
|
||||
selection_set_id,
|
||||
DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0),
|
||||
cx,
|
||||
) {
|
||||
set.push(selection.clone());
|
||||
let replica_selections = view
|
||||
.intersecting_selections(
|
||||
selection_set_id,
|
||||
DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0),
|
||||
cx,
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
for selection in &replica_selections {
|
||||
if selection_set_id == view.selection_set_id {
|
||||
let is_empty = selection.start == selection.end;
|
||||
let mut selection_start;
|
||||
let mut selection_end;
|
||||
if selection.start < selection.end {
|
||||
selection_start = selection.start;
|
||||
selection_end = selection.end;
|
||||
} else {
|
||||
selection_start = selection.end;
|
||||
selection_end = selection.start;
|
||||
};
|
||||
selection_start = snapshot.prev_row_boundary(selection_start).0;
|
||||
selection_end = snapshot.next_row_boundary(selection_end).0;
|
||||
let selection_start = snapshot.prev_row_boundary(selection.start).0;
|
||||
let selection_end = snapshot.next_row_boundary(selection.end).0;
|
||||
for row in cmp::max(selection_start.row(), start_row)
|
||||
..=cmp::min(selection_end.row(), end_row)
|
||||
{
|
||||
@@ -777,7 +770,7 @@ impl Element for EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
selections.insert(selection_set_id.replica_id, set);
|
||||
selections.insert(selection_set_id.replica_id, replica_selections);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -939,7 +932,7 @@ pub struct LayoutState {
|
||||
line_height: f32,
|
||||
em_width: f32,
|
||||
em_advance: f32,
|
||||
selections: HashMap<ReplicaId, Vec<Range<DisplayPoint>>>,
|
||||
selections: HashMap<ReplicaId, Vec<buffer::Selection<DisplayPoint>>>,
|
||||
overscroll: Vector2F,
|
||||
text_offset: Vector2F,
|
||||
max_visible_line_width: f32,
|
||||
|
||||
@@ -6,6 +6,7 @@ pub mod movement;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
use aho_corasick::AhoCorasick;
|
||||
use buffer::rope::TextDimension;
|
||||
use clock::ReplicaId;
|
||||
use display_map::*;
|
||||
@@ -87,6 +88,7 @@ action!(SelectLine);
|
||||
action!(SplitSelectionIntoLines);
|
||||
action!(AddSelectionAbove);
|
||||
action!(AddSelectionBelow);
|
||||
action!(SelectNext, bool);
|
||||
action!(ToggleComments);
|
||||
action!(SelectLargerSyntaxNode);
|
||||
action!(SelectSmallerSyntaxNode);
|
||||
@@ -191,6 +193,8 @@ pub fn init(cx: &mut MutableAppContext, entry_openers: &mut Vec<Box<dyn EntryOpe
|
||||
Binding::new("cmd-ctrl-p", AddSelectionAbove, Some("Editor")),
|
||||
Binding::new("cmd-alt-down", AddSelectionBelow, Some("Editor")),
|
||||
Binding::new("cmd-ctrl-n", AddSelectionBelow, Some("Editor")),
|
||||
Binding::new("cmd-d", SelectNext(false), Some("Editor")),
|
||||
Binding::new("cmd-k cmd-d", SelectNext(true), Some("Editor")),
|
||||
Binding::new("cmd-/", ToggleComments, Some("Editor")),
|
||||
Binding::new("alt-up", SelectLargerSyntaxNode, Some("Editor")),
|
||||
Binding::new("ctrl-w", SelectLargerSyntaxNode, Some("Editor")),
|
||||
@@ -254,6 +258,7 @@ pub fn init(cx: &mut MutableAppContext, entry_openers: &mut Vec<Box<dyn EntryOpe
|
||||
cx.add_action(Editor::split_selection_into_lines);
|
||||
cx.add_action(Editor::add_selection_above);
|
||||
cx.add_action(Editor::add_selection_below);
|
||||
cx.add_action(Editor::select_next);
|
||||
cx.add_action(Editor::toggle_comments);
|
||||
cx.add_action(Editor::select_larger_syntax_node);
|
||||
cx.add_action(Editor::select_smaller_syntax_node);
|
||||
@@ -311,9 +316,11 @@ enum SelectMode {
|
||||
All,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum Autoscroll {
|
||||
Closest,
|
||||
Fit,
|
||||
Center,
|
||||
Newest,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
@@ -338,6 +345,7 @@ pub struct Editor {
|
||||
columnar_selection_tail: Option<Anchor>,
|
||||
next_selection_id: usize,
|
||||
add_selections_state: Option<AddSelectionsState>,
|
||||
select_next_state: Option<SelectNextState>,
|
||||
autoclose_stack: Vec<BracketPairState>,
|
||||
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
|
||||
active_diagnostics: Option<ActiveDiagnosticGroup>,
|
||||
@@ -373,6 +381,12 @@ struct AddSelectionsState {
|
||||
stack: Vec<usize>,
|
||||
}
|
||||
|
||||
struct SelectNextState {
|
||||
query: AhoCorasick,
|
||||
wordwise: bool,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BracketPairState {
|
||||
ranges: AnchorRangeSet,
|
||||
@@ -473,6 +487,7 @@ impl Editor {
|
||||
columnar_selection_tail: None,
|
||||
next_selection_id,
|
||||
add_selections_state: None,
|
||||
select_next_state: None,
|
||||
autoclose_stack: Default::default(),
|
||||
select_larger_syntax_node_stack: Vec::new(),
|
||||
active_diagnostics: None,
|
||||
@@ -595,20 +610,29 @@ impl Editor {
|
||||
return false;
|
||||
};
|
||||
|
||||
let mut selections = self.selections::<Point>(cx).peekable();
|
||||
let first_cursor_top = selections
|
||||
.peek()
|
||||
.unwrap()
|
||||
.head()
|
||||
.to_display_point(&display_map)
|
||||
.row() as f32;
|
||||
let last_cursor_bottom = selections
|
||||
.last()
|
||||
.unwrap()
|
||||
.head()
|
||||
.to_display_point(&display_map)
|
||||
.row() as f32
|
||||
+ 1.0;
|
||||
let first_cursor_top;
|
||||
let last_cursor_bottom;
|
||||
if autoscroll == Autoscroll::Newest {
|
||||
let newest_selection = self.newest_selection::<Point>(cx);
|
||||
first_cursor_top = newest_selection.head().to_display_point(&display_map).row() as f32;
|
||||
last_cursor_bottom = first_cursor_top + 1.;
|
||||
} else {
|
||||
let mut selections = self.selections::<Point>(cx).peekable();
|
||||
first_cursor_top = selections
|
||||
.peek()
|
||||
.unwrap()
|
||||
.head()
|
||||
.to_display_point(&display_map)
|
||||
.row() as f32;
|
||||
last_cursor_bottom = selections
|
||||
.last()
|
||||
.unwrap()
|
||||
.head()
|
||||
.to_display_point(&display_map)
|
||||
.row() as f32
|
||||
+ 1.0;
|
||||
}
|
||||
|
||||
let margin = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
|
||||
0.
|
||||
} else {
|
||||
@@ -619,7 +643,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
match autoscroll {
|
||||
Autoscroll::Closest => {
|
||||
Autoscroll::Fit | Autoscroll::Newest => {
|
||||
let margin = margin.min(3.0);
|
||||
let target_top = (first_cursor_top - margin).max(0.0);
|
||||
let target_bottom = last_cursor_bottom + margin;
|
||||
@@ -658,14 +682,17 @@ impl Editor {
|
||||
let mut target_right = 0.0_f32;
|
||||
for selection in selections {
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let start_column = head.column().saturating_sub(3);
|
||||
let end_column = cmp::min(display_map.line_len(head.row()), head.column() + 3);
|
||||
target_left = target_left
|
||||
.min(layouts[(head.row() - start_row) as usize].x_for_index(start_column as usize));
|
||||
target_right = target_right.max(
|
||||
layouts[(head.row() - start_row) as usize].x_for_index(end_column as usize)
|
||||
+ max_glyph_width,
|
||||
);
|
||||
if head.row() >= start_row && head.row() < start_row + layouts.len() as u32 {
|
||||
let start_column = head.column().saturating_sub(3);
|
||||
let end_column = cmp::min(display_map.line_len(head.row()), head.column() + 3);
|
||||
target_left = target_left.min(
|
||||
layouts[(head.row() - start_row) as usize].x_for_index(start_column as usize),
|
||||
);
|
||||
target_right = target_right.max(
|
||||
layouts[(head.row() - start_row) as usize].x_for_index(end_column as usize)
|
||||
+ max_glyph_width,
|
||||
);
|
||||
}
|
||||
}
|
||||
target_right = target_right.min(scroll_width);
|
||||
|
||||
@@ -1000,7 +1027,7 @@ impl Editor {
|
||||
goal: selection.goal,
|
||||
};
|
||||
if self.selections::<Point>(cx).next().is_none() {
|
||||
self.update_selections(vec![selection], Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(vec![selection], Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
} else {
|
||||
let mut oldest_selection = self.oldest_selection::<usize>(cx);
|
||||
@@ -1008,7 +1035,7 @@ impl Editor {
|
||||
oldest_selection.start = oldest_selection.head().clone();
|
||||
oldest_selection.end = oldest_selection.head().clone();
|
||||
}
|
||||
self.update_selections(vec![oldest_selection], Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(vec![oldest_selection], Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1201,7 +1228,7 @@ impl Editor {
|
||||
))
|
||||
});
|
||||
|
||||
self.update_selections(new_selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(new_selections, Some(Autoscroll::Fit), cx);
|
||||
self.end_transaction(cx);
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -1241,7 +1268,7 @@ impl Editor {
|
||||
.collect();
|
||||
});
|
||||
|
||||
self.update_selections(new_selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(new_selections, Some(Autoscroll::Fit), cx);
|
||||
self.end_transaction(cx);
|
||||
}
|
||||
|
||||
@@ -1341,7 +1368,7 @@ impl Editor {
|
||||
})
|
||||
.collect();
|
||||
self.autoclose_stack.pop();
|
||||
self.update_selections(new_selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(new_selections, Some(Autoscroll::Fit), cx);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@@ -1369,7 +1396,7 @@ impl Editor {
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
self.insert("", cx);
|
||||
self.end_transaction(cx);
|
||||
}
|
||||
@@ -1388,7 +1415,7 @@ impl Editor {
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
self.insert(&"", cx);
|
||||
self.end_transaction(cx);
|
||||
}
|
||||
@@ -1459,7 +1486,7 @@ impl Editor {
|
||||
}
|
||||
});
|
||||
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
self.end_transaction(cx);
|
||||
}
|
||||
|
||||
@@ -1505,7 +1532,7 @@ impl Editor {
|
||||
|
||||
self.update_selections(
|
||||
self.selections::<usize>(cx).collect(),
|
||||
Some(Autoscroll::Closest),
|
||||
Some(Autoscroll::Fit),
|
||||
cx,
|
||||
);
|
||||
self.end_transaction(cx);
|
||||
@@ -1576,7 +1603,7 @@ impl Editor {
|
||||
.collect();
|
||||
self.buffer
|
||||
.update(cx, |buffer, cx| buffer.edit(edit_ranges, "", cx));
|
||||
self.update_selections(new_selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(new_selections, Some(Autoscroll::Fit), cx);
|
||||
self.end_transaction(cx);
|
||||
}
|
||||
|
||||
@@ -1634,7 +1661,7 @@ impl Editor {
|
||||
}
|
||||
});
|
||||
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
self.end_transaction(cx);
|
||||
}
|
||||
|
||||
@@ -1723,7 +1750,7 @@ impl Editor {
|
||||
}
|
||||
});
|
||||
self.fold_ranges(new_folds, cx);
|
||||
self.select_ranges(new_selection_ranges, Some(Autoscroll::Closest), cx);
|
||||
self.select_ranges(new_selection_ranges, Some(Autoscroll::Fit), cx);
|
||||
|
||||
self.end_transaction(cx);
|
||||
}
|
||||
@@ -1810,7 +1837,7 @@ impl Editor {
|
||||
}
|
||||
});
|
||||
self.fold_ranges(new_folds, cx);
|
||||
self.select_ranges(new_selection_ranges, Some(Autoscroll::Closest), cx);
|
||||
self.select_ranges(new_selection_ranges, Some(Autoscroll::Fit), cx);
|
||||
|
||||
self.end_transaction(cx);
|
||||
}
|
||||
@@ -1840,7 +1867,7 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
self.insert("", cx);
|
||||
self.end_transaction(cx);
|
||||
|
||||
@@ -1925,7 +1952,7 @@ impl Editor {
|
||||
selection.end = selection.start;
|
||||
});
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
} else {
|
||||
self.insert(clipboard_text, cx);
|
||||
}
|
||||
@@ -1934,12 +1961,12 @@ impl Editor {
|
||||
|
||||
pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) {
|
||||
self.buffer.update(cx, |buffer, cx| buffer.undo(cx));
|
||||
self.request_autoscroll(Autoscroll::Closest, cx);
|
||||
self.request_autoscroll(Autoscroll::Fit, cx);
|
||||
}
|
||||
|
||||
pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext<Self>) {
|
||||
self.buffer.update(cx, |buffer, cx| buffer.redo(cx));
|
||||
self.request_autoscroll(Autoscroll::Closest, cx);
|
||||
self.request_autoscroll(Autoscroll::Fit, cx);
|
||||
}
|
||||
|
||||
pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext<Self>) {
|
||||
@@ -1961,7 +1988,7 @@ impl Editor {
|
||||
selection.reversed = false;
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
|
||||
@@ -1975,7 +2002,7 @@ impl Editor {
|
||||
selection.set_head(cursor);
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext<Self>) {
|
||||
@@ -1997,7 +2024,7 @@ impl Editor {
|
||||
selection.reversed = false;
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
|
||||
@@ -2011,7 +2038,7 @@ impl Editor {
|
||||
selection.set_head(cursor);
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
|
||||
@@ -2036,7 +2063,7 @@ impl Editor {
|
||||
selection.goal = goal;
|
||||
selection.reversed = false;
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
|
||||
@@ -2049,7 +2076,7 @@ impl Editor {
|
||||
selection.set_head(cursor);
|
||||
selection.goal = goal;
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
|
||||
@@ -2074,7 +2101,7 @@ impl Editor {
|
||||
selection.goal = goal;
|
||||
selection.reversed = false;
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) {
|
||||
@@ -2087,7 +2114,7 @@ impl Editor {
|
||||
selection.set_head(cursor);
|
||||
selection.goal = goal;
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn move_to_previous_word_boundary(
|
||||
@@ -2105,7 +2132,7 @@ impl Editor {
|
||||
selection.reversed = false;
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn select_to_previous_word_boundary(
|
||||
@@ -2121,7 +2148,7 @@ impl Editor {
|
||||
selection.set_head(cursor);
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn delete_to_previous_word_boundary(
|
||||
@@ -2141,7 +2168,7 @@ impl Editor {
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
self.insert("", cx);
|
||||
self.end_transaction(cx);
|
||||
}
|
||||
@@ -2161,7 +2188,7 @@ impl Editor {
|
||||
selection.reversed = false;
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn select_to_next_word_boundary(
|
||||
@@ -2177,7 +2204,7 @@ impl Editor {
|
||||
selection.set_head(cursor);
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn delete_to_next_word_boundary(
|
||||
@@ -2197,7 +2224,7 @@ impl Editor {
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
self.insert("", cx);
|
||||
self.end_transaction(cx);
|
||||
}
|
||||
@@ -2218,7 +2245,7 @@ impl Editor {
|
||||
selection.reversed = false;
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn select_to_beginning_of_line(
|
||||
@@ -2234,7 +2261,7 @@ impl Editor {
|
||||
selection.set_head(new_head.to_point(&display_map));
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn delete_to_beginning_of_line(
|
||||
@@ -2262,7 +2289,7 @@ impl Editor {
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn select_to_end_of_line(&mut self, _: &SelectToEndOfLine, cx: &mut ViewContext<Self>) {
|
||||
@@ -2274,7 +2301,7 @@ impl Editor {
|
||||
selection.set_head(new_head.to_point(&display_map));
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext<Self>) {
|
||||
@@ -2299,13 +2326,13 @@ impl Editor {
|
||||
reversed: false,
|
||||
goal: SelectionGoal::None,
|
||||
};
|
||||
self.update_selections(vec![selection], Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(vec![selection], Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext<Self>) {
|
||||
let mut selection = self.selections::<Point>(cx).last().unwrap().clone();
|
||||
selection.set_head(Point::zero());
|
||||
self.update_selections(vec![selection], Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(vec![selection], Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext<Self>) {
|
||||
@@ -2318,13 +2345,13 @@ impl Editor {
|
||||
reversed: false,
|
||||
goal: SelectionGoal::None,
|
||||
};
|
||||
self.update_selections(vec![selection], Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(vec![selection], Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext<Self>) {
|
||||
let mut selection = self.selections::<usize>(cx).last().unwrap().clone();
|
||||
selection.set_head(self.buffer.read(cx).len());
|
||||
self.update_selections(vec![selection], Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(vec![selection], Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
|
||||
@@ -2349,7 +2376,7 @@ impl Editor {
|
||||
selection.end = cmp::min(max_point, Point::new(rows.end, 0));
|
||||
selection.reversed = false;
|
||||
}
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn split_selection_into_lines(
|
||||
@@ -2383,7 +2410,7 @@ impl Editor {
|
||||
to_unfold.push(selection.start..selection.end);
|
||||
}
|
||||
self.unfold_ranges(to_unfold, cx);
|
||||
self.update_selections(new_selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(new_selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn add_selection_above(&mut self, _: &AddSelectionAbove, cx: &mut ViewContext<Self>) {
|
||||
@@ -2481,12 +2508,110 @@ impl Editor {
|
||||
state.stack.pop();
|
||||
}
|
||||
|
||||
self.update_selections(new_selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(new_selections, Some(Autoscroll::Fit), cx);
|
||||
if state.stack.len() > 1 {
|
||||
self.add_selections_state = Some(state);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_next(&mut self, action: &SelectNext, cx: &mut ViewContext<Self>) {
|
||||
let replace_newest = action.0;
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = &display_map.buffer_snapshot;
|
||||
let mut selections = self.selections::<usize>(cx).collect::<Vec<_>>();
|
||||
if let Some(mut select_next_state) = self.select_next_state.take() {
|
||||
let query = &select_next_state.query;
|
||||
if !select_next_state.done {
|
||||
let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
|
||||
let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
|
||||
let mut next_selected_range = None;
|
||||
|
||||
let bytes_after_last_selection =
|
||||
buffer.bytes_in_range(last_selection.end..buffer.len());
|
||||
let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
|
||||
let query_matches = query
|
||||
.stream_find_iter(bytes_after_last_selection)
|
||||
.map(|result| (last_selection.end, result))
|
||||
.chain(
|
||||
query
|
||||
.stream_find_iter(bytes_before_first_selection)
|
||||
.map(|result| (0, result)),
|
||||
);
|
||||
for (start_offset, query_match) in query_matches {
|
||||
let query_match = query_match.unwrap(); // can only fail due to I/O
|
||||
let offset_range =
|
||||
start_offset + query_match.start()..start_offset + query_match.end();
|
||||
let display_range = offset_range.start.to_display_point(&display_map)
|
||||
..offset_range.end.to_display_point(&display_map);
|
||||
|
||||
if !select_next_state.wordwise
|
||||
|| (!movement::is_inside_word(&display_map, display_range.start)
|
||||
&& !movement::is_inside_word(&display_map, display_range.end))
|
||||
{
|
||||
next_selected_range = Some(offset_range);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(next_selected_range) = next_selected_range {
|
||||
if replace_newest {
|
||||
if let Some(newest_id) =
|
||||
selections.iter().max_by_key(|s| s.id).map(|s| s.id)
|
||||
{
|
||||
selections.retain(|s| s.id != newest_id);
|
||||
}
|
||||
}
|
||||
selections.push(Selection {
|
||||
id: post_inc(&mut self.next_selection_id),
|
||||
start: next_selected_range.start,
|
||||
end: next_selected_range.end,
|
||||
reversed: false,
|
||||
goal: SelectionGoal::None,
|
||||
});
|
||||
selections.sort_unstable_by_key(|s| s.start);
|
||||
self.update_selections(selections, Some(Autoscroll::Newest), cx);
|
||||
} else {
|
||||
select_next_state.done = true;
|
||||
}
|
||||
}
|
||||
|
||||
self.select_next_state = Some(select_next_state);
|
||||
} else if selections.len() == 1 {
|
||||
let selection = selections.last_mut().unwrap();
|
||||
if selection.start == selection.end {
|
||||
let word_range = movement::surrounding_word(
|
||||
&display_map,
|
||||
selection.start.to_display_point(&display_map),
|
||||
);
|
||||
selection.start = word_range.start.to_offset(&display_map, Bias::Left);
|
||||
selection.end = word_range.end.to_offset(&display_map, Bias::Left);
|
||||
selection.goal = SelectionGoal::None;
|
||||
selection.reversed = false;
|
||||
|
||||
let query = buffer
|
||||
.text_for_range(selection.start..selection.end)
|
||||
.collect::<String>();
|
||||
let select_state = SelectNextState {
|
||||
query: AhoCorasick::new_auto_configured(&[query]),
|
||||
wordwise: true,
|
||||
done: false,
|
||||
};
|
||||
self.update_selections(selections, Some(Autoscroll::Newest), cx);
|
||||
self.select_next_state = Some(select_state);
|
||||
} else {
|
||||
let query = buffer
|
||||
.text_for_range(selection.start..selection.end)
|
||||
.collect::<String>();
|
||||
self.select_next_state = Some(SelectNextState {
|
||||
query: AhoCorasick::new_auto_configured(&[query]),
|
||||
wordwise: false,
|
||||
done: false,
|
||||
});
|
||||
self.select_next(action, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_comments(&mut self, _: &ToggleComments, cx: &mut ViewContext<Self>) {
|
||||
// Get the line comment prefix. Split its trailing whitespace into a separate string,
|
||||
// as that portion won't be used for detecting if a line is a comment.
|
||||
@@ -2529,7 +2654,10 @@ impl Editor {
|
||||
}
|
||||
|
||||
let start = Point::new(row, buffer.indent_column_for_line(row));
|
||||
let mut line_bytes = buffer.bytes_at(start);
|
||||
let mut line_bytes = buffer
|
||||
.bytes_in_range(start..buffer.max_point())
|
||||
.flatten()
|
||||
.copied();
|
||||
|
||||
// If this line currently begins with the line comment prefix, then record
|
||||
// the range containing the prefix.
|
||||
@@ -2575,7 +2703,7 @@ impl Editor {
|
||||
|
||||
self.update_selections(
|
||||
self.selections::<usize>(cx).collect(),
|
||||
Some(Autoscroll::Closest),
|
||||
Some(Autoscroll::Fit),
|
||||
cx,
|
||||
);
|
||||
self.end_transaction(cx);
|
||||
@@ -2622,7 +2750,7 @@ impl Editor {
|
||||
if selected_larger_node {
|
||||
stack.push(old_selections);
|
||||
new_selections.sort_unstable_by_key(|selection| selection.start);
|
||||
self.update_selections(new_selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(new_selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
self.select_larger_syntax_node_stack = stack;
|
||||
}
|
||||
@@ -2634,7 +2762,7 @@ impl Editor {
|
||||
) {
|
||||
let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
|
||||
if let Some(selections) = stack.pop() {
|
||||
self.update_selections(selections.to_vec(), Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections.to_vec(), Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
self.select_larger_syntax_node_stack = stack;
|
||||
}
|
||||
@@ -2663,7 +2791,7 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
self.update_selections(selections, Some(Autoscroll::Closest), cx);
|
||||
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
|
||||
pub fn show_next_diagnostic(&mut self, _: &ShowNextDiagnostic, cx: &mut ViewContext<Self>) {
|
||||
@@ -2898,32 +3026,27 @@ impl Editor {
|
||||
.map(|(set_id, _)| *set_id)
|
||||
}
|
||||
|
||||
pub fn selections_in_range<'a>(
|
||||
pub fn intersecting_selections<'a>(
|
||||
&'a self,
|
||||
set_id: SelectionSetId,
|
||||
range: Range<DisplayPoint>,
|
||||
cx: &'a mut MutableAppContext,
|
||||
) -> impl 'a + Iterator<Item = Range<DisplayPoint>> {
|
||||
) -> impl 'a + Iterator<Item = Selection<DisplayPoint>> {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = self.buffer.read(cx);
|
||||
let selections = self
|
||||
.buffer
|
||||
.read(cx)
|
||||
.selection_set(set_id)
|
||||
.unwrap()
|
||||
.selections::<Point, _>(buffer)
|
||||
.collect::<Vec<_>>();
|
||||
let start = range.start.to_point(&display_map);
|
||||
let start_index = self.selection_insertion_index(&selections, start);
|
||||
|
||||
let pending_selection = if set_id == self.selection_set_id {
|
||||
self.pending_selection.as_ref().and_then(|pending| {
|
||||
let mut selection_start = pending.selection.start.to_display_point(&display_map);
|
||||
let mut selection_end = pending.selection.end.to_display_point(&display_map);
|
||||
if pending.selection.reversed {
|
||||
mem::swap(&mut selection_start, &mut selection_end);
|
||||
}
|
||||
let selection_start = pending.selection.start.to_display_point(&display_map);
|
||||
let selection_end = pending.selection.end.to_display_point(&display_map);
|
||||
if selection_start <= range.end || selection_end <= range.end {
|
||||
Some(selection_start..selection_end)
|
||||
Some(Selection {
|
||||
id: pending.selection.id,
|
||||
start: selection_start,
|
||||
end: selection_end,
|
||||
reversed: pending.selection.reversed,
|
||||
goal: pending.selection.goal,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -2931,25 +3054,25 @@ impl Editor {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
selections
|
||||
.into_iter()
|
||||
.skip(start_index)
|
||||
.map(move |s| s.display_range(&display_map))
|
||||
.take_while(move |r| r.start <= range.end || r.end <= range.end)
|
||||
.chain(pending_selection)
|
||||
}
|
||||
|
||||
fn selection_insertion_index(&self, selections: &[Selection<Point>], start: Point) -> usize {
|
||||
match selections.binary_search_by_key(&start, |probe| probe.start) {
|
||||
Ok(index) => index,
|
||||
Err(index) => {
|
||||
if index > 0 && selections[index - 1].end > start {
|
||||
index - 1
|
||||
} else {
|
||||
index
|
||||
}
|
||||
}
|
||||
}
|
||||
let range = (range.start.to_offset(&display_map, Bias::Left), Bias::Left)
|
||||
..(range.end.to_offset(&display_map, Bias::Left), Bias::Right);
|
||||
let selections = self
|
||||
.buffer
|
||||
.read(cx)
|
||||
.selection_set(set_id)
|
||||
.unwrap()
|
||||
.intersecting_selections::<Point, _, _>(range, buffer);
|
||||
|
||||
selections
|
||||
.map(move |s| Selection {
|
||||
id: s.id,
|
||||
start: s.start.to_display_point(&display_map),
|
||||
end: s.end.to_display_point(&display_map),
|
||||
reversed: s.reversed,
|
||||
goal: s.goal,
|
||||
})
|
||||
.chain(pending_selection)
|
||||
}
|
||||
|
||||
pub fn selections<'a, D>(&self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Selection<D>>
|
||||
@@ -3061,6 +3184,7 @@ impl Editor {
|
||||
|
||||
self.pending_selection = None;
|
||||
self.add_selections_state = None;
|
||||
self.select_next_state = None;
|
||||
self.select_larger_syntax_node_stack.clear();
|
||||
while let Some(autoclose_pair_state) = self.autoclose_stack.last() {
|
||||
let all_selections_inside_autoclose_ranges =
|
||||
@@ -3219,7 +3343,7 @@ impl Editor {
|
||||
fn fold_ranges<T: ToOffset>(&mut self, ranges: Vec<Range<T>>, cx: &mut ViewContext<Self>) {
|
||||
if !ranges.is_empty() {
|
||||
self.display_map.update(cx, |map, cx| map.fold(ranges, cx));
|
||||
self.request_autoscroll(Autoscroll::Closest, cx);
|
||||
self.request_autoscroll(Autoscroll::Fit, cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
@@ -3228,7 +3352,7 @@ impl Editor {
|
||||
if !ranges.is_empty() {
|
||||
self.display_map
|
||||
.update(cx, |map, cx| map.unfold(ranges, cx));
|
||||
self.request_autoscroll(Autoscroll::Closest, cx);
|
||||
self.request_autoscroll(Autoscroll::Fit, cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
@@ -5564,12 +5688,20 @@ mod tests {
|
||||
|
||||
impl Editor {
|
||||
fn selection_ranges(&self, cx: &mut MutableAppContext) -> Vec<Range<DisplayPoint>> {
|
||||
self.selections_in_range(
|
||||
self.intersecting_selections(
|
||||
self.selection_set_id,
|
||||
DisplayPoint::zero()..self.max_point(cx),
|
||||
cx,
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
if s.reversed {
|
||||
s.end..s.start
|
||||
} else {
|
||||
s.start..s.end
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -194,8 +194,8 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<Operation> {
|
||||
.selections
|
||||
.iter()
|
||||
.map(|selection| {
|
||||
let range = (FullOffset(selection.start as usize), Bias::Left)
|
||||
..(FullOffset(selection.end as usize), Bias::Right);
|
||||
let range = FullOffset(selection.start as usize)
|
||||
..FullOffset(selection.end as usize);
|
||||
let state = SelectionState {
|
||||
id: selection.id as usize,
|
||||
reversed: selection.reversed,
|
||||
@@ -204,7 +204,12 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<Operation> {
|
||||
(range, state)
|
||||
})
|
||||
.collect();
|
||||
let selections = AnchorRangeMap::from_full_offset_ranges(version, entries);
|
||||
let selections = AnchorRangeMap::from_full_offset_ranges(
|
||||
version,
|
||||
Bias::Left,
|
||||
Bias::Left,
|
||||
entries,
|
||||
);
|
||||
|
||||
Operation::Buffer(buffer::Operation::UpdateSelections {
|
||||
set_id: clock::Lamport {
|
||||
@@ -276,11 +281,13 @@ pub fn deserialize_selection_set(set: proto::SelectionSet) -> SelectionSet {
|
||||
active: set.is_active,
|
||||
selections: Arc::new(AnchorRangeMap::from_full_offset_ranges(
|
||||
set.version.into(),
|
||||
Bias::Left,
|
||||
Bias::Left,
|
||||
set.selections
|
||||
.into_iter()
|
||||
.map(|selection| {
|
||||
let range = (FullOffset(selection.start as usize), Bias::Left)
|
||||
..(FullOffset(selection.end as usize), Bias::Left);
|
||||
let range =
|
||||
FullOffset(selection.start as usize)..FullOffset(selection.end as usize);
|
||||
let state = SelectionState {
|
||||
id: selection.id as usize,
|
||||
reversed: selection.reversed,
|
||||
|
||||
Reference in New Issue
Block a user