Merge branch 'main' into select-on-rename
This commit is contained in:
commit
951fd1ab36
28 changed files with 1063 additions and 257 deletions
|
@ -6,7 +6,7 @@ mod wrap_map;
|
|||
use crate::{Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
|
||||
use block_map::{BlockMap, BlockPoint};
|
||||
use collections::{HashMap, HashSet};
|
||||
use fold_map::{FoldMap, ToFoldPoint as _};
|
||||
use fold_map::FoldMap;
|
||||
use gpui::{
|
||||
fonts::{FontId, HighlightStyle},
|
||||
Entity, ModelContext, ModelHandle,
|
||||
|
@ -223,7 +223,7 @@ impl DisplaySnapshot {
|
|||
|
||||
pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
|
||||
loop {
|
||||
let mut fold_point = point.to_fold_point(&self.folds_snapshot, Bias::Left);
|
||||
let mut fold_point = self.folds_snapshot.to_fold_point(point, Bias::Left);
|
||||
*fold_point.column_mut() = 0;
|
||||
point = fold_point.to_buffer_point(&self.folds_snapshot);
|
||||
|
||||
|
@ -239,7 +239,7 @@ impl DisplaySnapshot {
|
|||
|
||||
pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
|
||||
loop {
|
||||
let mut fold_point = point.to_fold_point(&self.folds_snapshot, Bias::Right);
|
||||
let mut fold_point = self.folds_snapshot.to_fold_point(point, Bias::Right);
|
||||
*fold_point.column_mut() = self.folds_snapshot.line_len(fold_point.row());
|
||||
point = fold_point.to_buffer_point(&self.folds_snapshot);
|
||||
|
||||
|
@ -254,7 +254,7 @@ impl DisplaySnapshot {
|
|||
}
|
||||
|
||||
fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint {
|
||||
let fold_point = point.to_fold_point(&self.folds_snapshot, bias);
|
||||
let fold_point = self.folds_snapshot.to_fold_point(point, bias);
|
||||
let tab_point = self.tabs_snapshot.to_tab_point(fold_point);
|
||||
let wrap_point = self.wraps_snapshot.from_tab_point(tab_point);
|
||||
let block_point = self.blocks_snapshot.to_block_point(wrap_point);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use super::TextHighlights;
|
||||
use crate::{
|
||||
multi_buffer::MultiBufferRows, Anchor, AnchorRangeExt, MultiBufferChunks, MultiBufferSnapshot,
|
||||
ToOffset,
|
||||
|
@ -16,12 +17,6 @@ use std::{
|
|||
};
|
||||
use sum_tree::{Bias, Cursor, FilterCursor, SumTree};
|
||||
|
||||
use super::TextHighlights;
|
||||
|
||||
pub trait ToFoldPoint {
|
||||
fn to_fold_point(&self, snapshot: &FoldSnapshot, bias: Bias) -> FoldPoint;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||
pub struct FoldPoint(pub super::Point);
|
||||
|
||||
|
@ -81,26 +76,6 @@ impl FoldPoint {
|
|||
}
|
||||
}
|
||||
|
||||
impl ToFoldPoint for Point {
|
||||
fn to_fold_point(&self, snapshot: &FoldSnapshot, bias: Bias) -> FoldPoint {
|
||||
let mut cursor = snapshot.transforms.cursor::<(Point, FoldPoint)>();
|
||||
cursor.seek(self, Bias::Right, &());
|
||||
if cursor.item().map_or(false, |t| t.is_fold()) {
|
||||
if bias == Bias::Left || *self == cursor.start().0 {
|
||||
cursor.start().1
|
||||
} else {
|
||||
cursor.end(&()).1
|
||||
}
|
||||
} else {
|
||||
let overshoot = *self - cursor.start().0;
|
||||
FoldPoint(cmp::min(
|
||||
cursor.start().1 .0 + overshoot,
|
||||
cursor.end(&()).1 .0,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += &summary.output.lines;
|
||||
|
@ -566,6 +541,24 @@ impl FoldSnapshot {
|
|||
summary
|
||||
}
|
||||
|
||||
pub fn to_fold_point(&self, point: Point, bias: Bias) -> FoldPoint {
|
||||
let mut cursor = self.transforms.cursor::<(Point, FoldPoint)>();
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
if cursor.item().map_or(false, |t| t.is_fold()) {
|
||||
if bias == Bias::Left || point == cursor.start().0 {
|
||||
cursor.start().1
|
||||
} else {
|
||||
cursor.end(&()).1
|
||||
}
|
||||
} else {
|
||||
let overshoot = point - cursor.start().0;
|
||||
FoldPoint(cmp::min(
|
||||
cursor.start().1 .0 + overshoot,
|
||||
cursor.end(&()).1 .0,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> FoldOffset {
|
||||
FoldOffset(self.transforms.summary().output.bytes)
|
||||
}
|
||||
|
@ -1511,7 +1504,7 @@ mod tests {
|
|||
let buffer_point = fold_point.to_buffer_point(&snapshot);
|
||||
let buffer_offset = buffer_point.to_offset(&buffer_snapshot);
|
||||
assert_eq!(
|
||||
buffer_point.to_fold_point(&snapshot, Right),
|
||||
snapshot.to_fold_point(buffer_point, Right),
|
||||
fold_point,
|
||||
"{:?} -> fold point",
|
||||
buffer_point,
|
||||
|
@ -1583,10 +1576,8 @@ mod tests {
|
|||
}
|
||||
|
||||
for fold_range in map.merged_fold_ranges() {
|
||||
let fold_point = fold_range
|
||||
.start
|
||||
.to_point(&buffer_snapshot)
|
||||
.to_fold_point(&snapshot, Right);
|
||||
let fold_point =
|
||||
snapshot.to_fold_point(fold_range.start.to_point(&buffer_snapshot), Right);
|
||||
assert!(snapshot.is_line_folded(fold_point.row()));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::{
|
||||
fold_map::{self, FoldEdit, FoldPoint, FoldSnapshot, ToFoldPoint},
|
||||
fold_map::{self, FoldEdit, FoldPoint, FoldSnapshot},
|
||||
TextHighlights,
|
||||
};
|
||||
use crate::MultiBufferSnapshot;
|
||||
|
@ -212,10 +212,6 @@ impl TabSnapshot {
|
|||
TabPoint::new(input.row(), expanded as u32)
|
||||
}
|
||||
|
||||
pub fn from_point(&self, point: Point, bias: Bias) -> TabPoint {
|
||||
self.to_tab_point(point.to_fold_point(&self.fold_snapshot, bias))
|
||||
}
|
||||
|
||||
pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, usize, usize) {
|
||||
let chars = self.fold_snapshot.chars_at(FoldPoint::new(output.row(), 0));
|
||||
let expanded = output.column() as usize;
|
||||
|
@ -228,6 +224,10 @@ impl TabSnapshot {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn from_point(&self, point: Point, bias: Bias) -> TabPoint {
|
||||
self.to_tab_point(self.fold_snapshot.to_fold_point(point, bias))
|
||||
}
|
||||
|
||||
pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point {
|
||||
self.to_fold_point(point, bias)
|
||||
.0
|
||||
|
|
|
@ -453,6 +453,7 @@ pub struct Editor {
|
|||
document_highlights_task: Option<Task<()>>,
|
||||
pending_rename: Option<RenameState>,
|
||||
searchable: bool,
|
||||
cursor_shape: CursorShape,
|
||||
}
|
||||
|
||||
pub struct EditorSnapshot {
|
||||
|
@ -934,6 +935,7 @@ impl Editor {
|
|||
pending_rename: Default::default(),
|
||||
searchable: true,
|
||||
override_text_style: None,
|
||||
cursor_shape: Default::default(),
|
||||
};
|
||||
this.end_selection(cx);
|
||||
this
|
||||
|
@ -1030,6 +1032,11 @@ impl Editor {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
|
||||
self.cursor_shape = cursor_shape;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
compute_scroll_position(&display_map, self.scroll_position, &self.scroll_top_anchor)
|
||||
|
@ -2647,11 +2654,26 @@ impl Editor {
|
|||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
for selection in &mut selections {
|
||||
if selection.is_empty() {
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let cursor = movement::left(&display_map, head)
|
||||
.unwrap()
|
||||
.to_point(&display_map);
|
||||
selection.set_head(cursor);
|
||||
let old_head = selection.head();
|
||||
let mut new_head =
|
||||
movement::left(&display_map, old_head.to_display_point(&display_map))
|
||||
.unwrap()
|
||||
.to_point(&display_map);
|
||||
if let Some((buffer, line_buffer_range)) = display_map
|
||||
.buffer_snapshot
|
||||
.buffer_line_for_row(old_head.row)
|
||||
{
|
||||
let indent_column = buffer.indent_column_for_line(line_buffer_range.start.row);
|
||||
if old_head.column <= indent_column && old_head.column > 0 {
|
||||
let indent = buffer.indent_size();
|
||||
new_head = cmp::min(
|
||||
new_head,
|
||||
Point::new(old_head.row, ((old_head.column - 1) / indent) * indent),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
selection.set_head(new_head);
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
}
|
||||
|
@ -5599,7 +5621,7 @@ impl View for Editor {
|
|||
self.display_map.update(cx, |map, cx| {
|
||||
map.set_font(style.text.font_id, style.text.font_size, cx)
|
||||
});
|
||||
EditorElement::new(self.handle.clone(), style.clone()).boxed()
|
||||
EditorElement::new(self.handle.clone(), style.clone(), self.cursor_shape).boxed()
|
||||
}
|
||||
|
||||
fn ui_name() -> &'static str {
|
||||
|
@ -7202,14 +7224,13 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_backspace(cx: &mut gpui::MutableAppContext) {
|
||||
let buffer =
|
||||
MultiBuffer::build_simple("one two three\nfour five six\nseven eight nine\nten\n", cx);
|
||||
let settings = Settings::test(&cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||
build_editor(buffer.clone(), settings, cx)
|
||||
build_editor(MultiBuffer::build_simple("", cx), settings, cx)
|
||||
});
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
view.set_text("one two three\nfour five six\nseven eight nine\nten\n", cx);
|
||||
view.select_display_ranges(
|
||||
&[
|
||||
// an empty selection - the preceding character is deleted
|
||||
|
@ -7222,12 +7243,28 @@ mod tests {
|
|||
cx,
|
||||
);
|
||||
view.backspace(&Backspace, cx);
|
||||
});
|
||||
assert_eq!(view.text(cx), "oe two three\nfou five six\nseven ten\n");
|
||||
|
||||
assert_eq!(
|
||||
buffer.read(cx).read(cx).text(),
|
||||
"oe two three\nfou five six\nseven ten\n"
|
||||
);
|
||||
view.set_text(" one\n two\n three\n four", cx);
|
||||
view.select_display_ranges(
|
||||
&[
|
||||
// cursors at the the end of leading indent - last indent is deleted
|
||||
DisplayPoint::new(0, 4)..DisplayPoint::new(0, 4),
|
||||
DisplayPoint::new(1, 8)..DisplayPoint::new(1, 8),
|
||||
// cursors inside leading indent - overlapping indent deletions are coalesced
|
||||
DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
|
||||
DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
|
||||
DisplayPoint::new(2, 6)..DisplayPoint::new(2, 6),
|
||||
// cursor at the beginning of a line - preceding newline is deleted
|
||||
DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
|
||||
// selection inside leading indent - only the selected character is deleted
|
||||
DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3),
|
||||
],
|
||||
cx,
|
||||
);
|
||||
view.backspace(&Backspace, cx);
|
||||
assert_eq!(view.text(cx), "one\n two\n three four");
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
|
|
@ -16,7 +16,7 @@ use gpui::{
|
|||
PathBuilder,
|
||||
},
|
||||
json::{self, ToJson},
|
||||
text_layout::{self, RunStyle, TextLayoutCache},
|
||||
text_layout::{self, Line, RunStyle, TextLayoutCache},
|
||||
AppContext, Axis, Border, Element, ElementBox, Event, EventContext, LayoutContext,
|
||||
MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
|
||||
};
|
||||
|
@ -32,11 +32,20 @@ use std::{
|
|||
pub struct EditorElement {
|
||||
view: WeakViewHandle<Editor>,
|
||||
style: EditorStyle,
|
||||
cursor_shape: CursorShape,
|
||||
}
|
||||
|
||||
impl EditorElement {
|
||||
pub fn new(view: WeakViewHandle<Editor>, style: EditorStyle) -> Self {
|
||||
Self { view, style }
|
||||
pub fn new(
|
||||
view: WeakViewHandle<Editor>,
|
||||
style: EditorStyle,
|
||||
cursor_shape: CursorShape,
|
||||
) -> Self {
|
||||
Self {
|
||||
view,
|
||||
style,
|
||||
cursor_shape,
|
||||
}
|
||||
}
|
||||
|
||||
fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor {
|
||||
|
@ -338,7 +347,7 @@ impl EditorElement {
|
|||
|
||||
let mut cursors = SmallVec::<[Cursor; 32]>::new();
|
||||
for (replica_id, selections) in &layout.selections {
|
||||
let style = style.replica_selection_style(*replica_id);
|
||||
let selection_style = style.replica_selection_style(*replica_id);
|
||||
let corner_radius = 0.15 * layout.line_height;
|
||||
|
||||
for selection in selections {
|
||||
|
@ -346,7 +355,7 @@ impl EditorElement {
|
|||
selection.start..selection.end,
|
||||
start_row,
|
||||
end_row,
|
||||
style.selection,
|
||||
selection_style.selection,
|
||||
corner_radius,
|
||||
corner_radius * 2.,
|
||||
layout,
|
||||
|
@ -362,13 +371,50 @@ impl EditorElement {
|
|||
if (start_row..end_row).contains(&cursor_position.row()) {
|
||||
let cursor_row_layout =
|
||||
&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 cursor_column = cursor_position.column() as usize;
|
||||
|
||||
let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
|
||||
let mut block_width =
|
||||
cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
|
||||
if block_width == 0.0 {
|
||||
block_width = layout.em_width;
|
||||
}
|
||||
|
||||
let block_text =
|
||||
if matches!(self.cursor_shape, CursorShape::Block) {
|
||||
layout.snapshot.chars_at(cursor_position).next().and_then(
|
||||
|character| {
|
||||
let font_id =
|
||||
cursor_row_layout.font_for_index(cursor_column)?;
|
||||
let text = character.to_string();
|
||||
|
||||
Some(cx.text_layout_cache.layout_str(
|
||||
&text,
|
||||
cursor_row_layout.font_size(),
|
||||
&[(
|
||||
text.len(),
|
||||
RunStyle {
|
||||
font_id,
|
||||
color: style.background,
|
||||
underline: None,
|
||||
},
|
||||
)],
|
||||
))
|
||||
},
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let x = cursor_character_x - scroll_left;
|
||||
let y = cursor_position.row() as f32 * layout.line_height - scroll_top;
|
||||
cursors.push(Cursor {
|
||||
color: style.cursor,
|
||||
color: selection_style.cursor,
|
||||
block_width,
|
||||
origin: content_origin + vec2f(x, y),
|
||||
line_height: layout.line_height,
|
||||
shape: self.cursor_shape,
|
||||
block_text,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1168,6 +1214,7 @@ fn layout_line(
|
|||
while !line.is_char_boundary(len) {
|
||||
len -= 1;
|
||||
}
|
||||
|
||||
line.truncate(len);
|
||||
}
|
||||
|
||||
|
@ -1219,20 +1266,51 @@ impl PaintState {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum CursorShape {
|
||||
Bar,
|
||||
Block,
|
||||
Underscore,
|
||||
}
|
||||
|
||||
impl Default for CursorShape {
|
||||
fn default() -> Self {
|
||||
CursorShape::Bar
|
||||
}
|
||||
}
|
||||
|
||||
struct Cursor {
|
||||
origin: Vector2F,
|
||||
block_width: f32,
|
||||
line_height: f32,
|
||||
color: Color,
|
||||
shape: CursorShape,
|
||||
block_text: Option<Line>,
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
fn paint(&self, cx: &mut PaintContext) {
|
||||
let bounds = match self.shape {
|
||||
CursorShape::Bar => RectF::new(self.origin, vec2f(2.0, self.line_height)),
|
||||
CursorShape::Block => {
|
||||
RectF::new(self.origin, vec2f(self.block_width, self.line_height))
|
||||
}
|
||||
CursorShape::Underscore => RectF::new(
|
||||
self.origin + Vector2F::new(0.0, self.line_height - 2.0),
|
||||
vec2f(self.block_width, 2.0),
|
||||
),
|
||||
};
|
||||
|
||||
cx.scene.push_quad(Quad {
|
||||
bounds: RectF::new(self.origin, vec2f(2.0, self.line_height)),
|
||||
bounds,
|
||||
background: Some(self.color),
|
||||
border: Border::new(0., Color::black()),
|
||||
corner_radius: 0.,
|
||||
});
|
||||
|
||||
if let Some(block_text) = &self.block_text {
|
||||
block_text.paint(self.origin, bounds, self.line_height, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1396,7 +1474,7 @@ mod tests {
|
|||
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
||||
Editor::new(EditorMode::Full, buffer, None, settings.1, None, cx)
|
||||
});
|
||||
let element = EditorElement::new(editor.downgrade(), editor.read(cx).style(cx));
|
||||
let element = EditorElement::new(editor.downgrade(), editor.read(cx).style(cx), CursorShape::Bar);
|
||||
|
||||
let layouts = editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
|
|
|
@ -1655,7 +1655,7 @@ impl MultiBufferSnapshot {
|
|||
}
|
||||
}
|
||||
|
||||
fn buffer_line_for_row(&self, row: u32) -> Option<(&BufferSnapshot, Range<Point>)> {
|
||||
pub fn buffer_line_for_row(&self, row: u32) -> Option<(&BufferSnapshot, Range<Point>)> {
|
||||
let mut cursor = self.excerpts.cursor::<Point>();
|
||||
cursor.seek(&Point::new(row, 0), Bias::Right, &());
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue