Merge branch 'main' into select-on-rename

This commit is contained in:
Nathan Sobo 2022-03-11 15:30:07 -07:00
commit 951fd1ab36
28 changed files with 1063 additions and 257 deletions

View file

@ -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);

View file

@ -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()));
}

View file

@ -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

View file

@ -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]

View file

@ -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);

View file

@ -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() {