Introduce multi-cursor inline transformations (#13368)
https://github.com/zed-industries/zed/assets/482957/591def34-e5c8-4402-9c6b-372cbca720c3 Release Notes: - N/A --------- Co-authored-by: Richard Feldman <oss@rtfeldman.com>
This commit is contained in:
parent
c58a8f1a04
commit
cb0b8b4c4b
16 changed files with 1335 additions and 698 deletions
|
@ -169,7 +169,7 @@ impl DisplayMap {
|
|||
let (wrap_snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx));
|
||||
let block_snapshot = self.block_map.read(wrap_snapshot.clone(), edits);
|
||||
let block_snapshot = self.block_map.read(wrap_snapshot.clone(), edits).snapshot;
|
||||
|
||||
DisplaySnapshot {
|
||||
buffer_snapshot: self.buffer.read(cx).snapshot(cx),
|
||||
|
@ -348,6 +348,25 @@ impl DisplayMap {
|
|||
block_map.remove(ids);
|
||||
}
|
||||
|
||||
pub fn row_for_block(
|
||||
&mut self,
|
||||
block_id: BlockId,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Option<DisplayRow> {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
let block_map = self.block_map.read(snapshot, edits);
|
||||
let block_row = block_map.row_for_block(block_id)?;
|
||||
Some(DisplayRow(block_row.0))
|
||||
}
|
||||
|
||||
pub fn highlight_text(
|
||||
&mut self,
|
||||
type_id: TypeId,
|
||||
|
|
|
@ -37,6 +37,11 @@ pub struct BlockMap {
|
|||
excerpt_footer_height: u8,
|
||||
}
|
||||
|
||||
pub struct BlockMapReader<'a> {
|
||||
blocks: &'a Vec<Arc<Block>>,
|
||||
pub snapshot: BlockSnapshot,
|
||||
}
|
||||
|
||||
pub struct BlockMapWriter<'a>(&'a mut BlockMap);
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -246,12 +251,15 @@ impl BlockMap {
|
|||
map
|
||||
}
|
||||
|
||||
pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockSnapshot {
|
||||
pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapReader {
|
||||
self.sync(&wrap_snapshot, edits);
|
||||
*self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
|
||||
BlockSnapshot {
|
||||
wrap_snapshot,
|
||||
transforms: self.transforms.borrow().clone(),
|
||||
BlockMapReader {
|
||||
blocks: &self.blocks,
|
||||
snapshot: BlockSnapshot {
|
||||
wrap_snapshot,
|
||||
transforms: self.transforms.borrow().clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -606,6 +614,62 @@ impl std::ops::DerefMut for BlockPoint {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for BlockMapReader<'a> {
|
||||
type Target = BlockSnapshot;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.snapshot
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DerefMut for BlockMapReader<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.snapshot
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BlockMapReader<'a> {
|
||||
pub fn row_for_block(&self, block_id: BlockId) -> Option<BlockRow> {
|
||||
let block = self.blocks.iter().find(|block| block.id == block_id)?;
|
||||
let buffer_row = block
|
||||
.position
|
||||
.to_point(self.wrap_snapshot.buffer_snapshot())
|
||||
.row;
|
||||
let wrap_row = self
|
||||
.wrap_snapshot
|
||||
.make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
|
||||
.row();
|
||||
let start_wrap_row = WrapRow(
|
||||
self.wrap_snapshot
|
||||
.prev_row_boundary(WrapPoint::new(wrap_row, 0)),
|
||||
);
|
||||
let end_wrap_row = WrapRow(
|
||||
self.wrap_snapshot
|
||||
.next_row_boundary(WrapPoint::new(wrap_row, 0))
|
||||
.unwrap_or(self.wrap_snapshot.max_point().row() + 1),
|
||||
);
|
||||
|
||||
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
|
||||
cursor.seek(&start_wrap_row, Bias::Left, &());
|
||||
while let Some(transform) = cursor.item() {
|
||||
if cursor.start().0 > end_wrap_row {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(BlockType::Custom(id)) =
|
||||
transform.block.as_ref().map(|block| block.block_type())
|
||||
{
|
||||
if id == block_id {
|
||||
return Some(cursor.start().1);
|
||||
}
|
||||
}
|
||||
cursor.next(&());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BlockMapWriter<'a> {
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
|
@ -1784,6 +1848,15 @@ mod tests {
|
|||
expected_block_positions
|
||||
);
|
||||
|
||||
for (block_row, block) in expected_block_positions {
|
||||
if let BlockType::Custom(block_id) = block.block_type() {
|
||||
assert_eq!(
|
||||
blocks_snapshot.row_for_block(block_id),
|
||||
Some(BlockRow(block_row))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut expected_longest_rows = Vec::new();
|
||||
let mut longest_line_len = -1_isize;
|
||||
for (row, line) in expected_lines.iter().enumerate() {
|
||||
|
|
|
@ -457,6 +457,9 @@ pub struct Editor {
|
|||
pub display_map: Model<DisplayMap>,
|
||||
pub selections: SelectionsCollection,
|
||||
pub scroll_manager: ScrollManager,
|
||||
/// When inline assist editors are linked, they all render cursors because
|
||||
/// typing enters text into each of them, even the ones that aren't focused.
|
||||
pub(crate) show_cursor_when_unfocused: bool,
|
||||
columnar_selection_tail: Option<Anchor>,
|
||||
add_selections_state: Option<AddSelectionsState>,
|
||||
select_next_state: Option<SelectNextState>,
|
||||
|
@ -1635,7 +1638,7 @@ impl Editor {
|
|||
clone
|
||||
}
|
||||
|
||||
fn new(
|
||||
pub fn new(
|
||||
mode: EditorMode,
|
||||
buffer: Model<MultiBuffer>,
|
||||
project: Option<Model<Project>>,
|
||||
|
@ -1752,6 +1755,7 @@ impl Editor {
|
|||
|
||||
let mut this = Self {
|
||||
focus_handle,
|
||||
show_cursor_when_unfocused: false,
|
||||
last_focused_descendant: None,
|
||||
buffer: buffer.clone(),
|
||||
display_map: display_map.clone(),
|
||||
|
@ -2220,7 +2224,7 @@ impl Editor {
|
|||
// Copy selections to primary selection buffer
|
||||
#[cfg(target_os = "linux")]
|
||||
if local {
|
||||
let selections = &self.selections.disjoint;
|
||||
let selections = self.selections.all::<usize>(cx);
|
||||
let buffer_handle = self.buffer.read(cx).read(cx);
|
||||
|
||||
let mut text = String::new();
|
||||
|
@ -9964,6 +9968,15 @@ impl Editor {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn row_for_block(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<DisplayRow> {
|
||||
self.display_map
|
||||
.update(cx, |map, cx| map.row_for_block(block_id, cx))
|
||||
}
|
||||
|
||||
pub fn insert_creases(
|
||||
&mut self,
|
||||
creases: impl IntoIterator<Item = Crease>,
|
||||
|
@ -10902,6 +10915,11 @@ impl Editor {
|
|||
&& self.focus_handle.is_focused(cx)
|
||||
}
|
||||
|
||||
pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut ViewContext<Self>) {
|
||||
self.show_cursor_when_unfocused = is_enabled;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn on_buffer_changed(&mut self, _: Model<MultiBuffer>, cx: &mut ViewContext<Self>) {
|
||||
cx.notify();
|
||||
}
|
||||
|
@ -11722,7 +11740,7 @@ impl EditorSnapshot {
|
|||
.map(|(_, collaborator)| (collaborator.replica_id, collaborator))
|
||||
.collect::<HashMap<_, _>>();
|
||||
self.buffer_snapshot
|
||||
.remote_selections_in_range(range)
|
||||
.selections_in_range(range, false)
|
||||
.filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
|
||||
let collaborator = collaborators_by_replica_id.get(&replica_id)?;
|
||||
let participant_index = participant_indices.get(&collaborator.user_id).copied();
|
||||
|
|
|
@ -859,6 +859,28 @@ impl EditorElement {
|
|||
}
|
||||
|
||||
selections.extend(remote_selections.into_values());
|
||||
} else if !editor.is_focused(cx) && editor.show_cursor_when_unfocused {
|
||||
let player = if editor.read_only(cx) {
|
||||
cx.theme().players().read_only()
|
||||
} else {
|
||||
self.style.local_player
|
||||
};
|
||||
let layouts = snapshot
|
||||
.buffer_snapshot
|
||||
.selections_in_range(&(start_anchor..end_anchor), true)
|
||||
.map(move |(_, line_mode, cursor_shape, selection)| {
|
||||
SelectionLayout::new(
|
||||
selection,
|
||||
line_mode,
|
||||
cursor_shape,
|
||||
&snapshot.display_snapshot,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
selections.push((player, layouts));
|
||||
}
|
||||
(selections, active_rows, newest_selection_head)
|
||||
}
|
||||
|
@ -3631,12 +3653,12 @@ impl EditorElement {
|
|||
let forbid_vertical_scroll = editor.scroll_manager.forbid_vertical_scroll();
|
||||
if forbid_vertical_scroll {
|
||||
scroll_position.y = current_scroll_position.y;
|
||||
if scroll_position == current_scroll_position {
|
||||
return;
|
||||
}
|
||||
}
|
||||
editor.scroll(scroll_position, axis, cx);
|
||||
cx.stop_propagation();
|
||||
|
||||
if scroll_position != current_scroll_position {
|
||||
editor.scroll(scroll_position, axis, cx);
|
||||
cx.stop_propagation();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -4621,13 +4643,29 @@ impl Element for EditorElement {
|
|||
let content_origin =
|
||||
text_hitbox.origin + point(gutter_dimensions.margin, Pixels::ZERO);
|
||||
|
||||
let height_in_lines = bounds.size.height / line_height;
|
||||
let max_scroll_top = if matches!(snapshot.mode, EditorMode::AutoHeight { .. }) {
|
||||
(snapshot.max_point().row().as_f32() - height_in_lines + 1.).max(0.)
|
||||
} else {
|
||||
let settings = EditorSettings::get_global(cx);
|
||||
let max_row = snapshot.max_point().row().as_f32();
|
||||
match settings.scroll_beyond_last_line {
|
||||
ScrollBeyondLastLine::OnePage => max_row,
|
||||
ScrollBeyondLastLine::Off => (max_row - height_in_lines + 1.0).max(0.0),
|
||||
ScrollBeyondLastLine::VerticalScrollMargin => {
|
||||
(max_row - height_in_lines + 1.0 + settings.vertical_scroll_margin)
|
||||
.max(0.0)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut autoscroll_containing_element = false;
|
||||
let mut autoscroll_horizontally = false;
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
autoscroll_containing_element =
|
||||
editor.autoscroll_requested() || editor.has_pending_selection();
|
||||
autoscroll_horizontally =
|
||||
editor.autoscroll_vertically(bounds, line_height, cx);
|
||||
editor.autoscroll_vertically(bounds, line_height, max_scroll_top, cx);
|
||||
snapshot = editor.snapshot(cx);
|
||||
});
|
||||
|
||||
|
@ -4635,7 +4673,6 @@ impl Element for EditorElement {
|
|||
// The scroll position is a fractional point, the whole number of which represents
|
||||
// the top of the window in terms of display rows.
|
||||
let start_row = DisplayRow(scroll_position.y as u32);
|
||||
let height_in_lines = bounds.size.height / line_height;
|
||||
let max_row = snapshot.max_point().row();
|
||||
let end_row = cmp::min(
|
||||
(scroll_position.y + height_in_lines).ceil() as u32,
|
||||
|
@ -4817,22 +4854,9 @@ impl Element for EditorElement {
|
|||
cx,
|
||||
);
|
||||
|
||||
let settings = EditorSettings::get_global(cx);
|
||||
let scroll_max_row = max_row.as_f32();
|
||||
let scroll_max_row = match settings.scroll_beyond_last_line {
|
||||
ScrollBeyondLastLine::OnePage => scroll_max_row,
|
||||
ScrollBeyondLastLine::Off => {
|
||||
(scroll_max_row - height_in_lines + 1.0).max(0.0)
|
||||
}
|
||||
ScrollBeyondLastLine::VerticalScrollMargin => (scroll_max_row
|
||||
- height_in_lines
|
||||
+ 1.0
|
||||
+ settings.vertical_scroll_margin)
|
||||
.max(0.0),
|
||||
};
|
||||
let scroll_max = point(
|
||||
((scroll_width - text_hitbox.size.width) / em_width).max(0.0),
|
||||
scroll_max_row,
|
||||
max_scroll_top,
|
||||
);
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
|
|
|
@ -1201,20 +1201,22 @@ impl SearchableItem for Editor {
|
|||
for (excerpt_id, search_buffer, search_range) in
|
||||
buffer.excerpts_in_ranges(search_within_ranges)
|
||||
{
|
||||
ranges.extend(
|
||||
query
|
||||
.search(&search_buffer, Some(search_range.clone()))
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|match_range| {
|
||||
let start = search_buffer
|
||||
.anchor_after(search_range.start + match_range.start);
|
||||
let end = search_buffer
|
||||
.anchor_before(search_range.start + match_range.end);
|
||||
buffer.anchor_in_excerpt(excerpt_id, start).unwrap()
|
||||
..buffer.anchor_in_excerpt(excerpt_id, end).unwrap()
|
||||
}),
|
||||
);
|
||||
if !search_range.is_empty() {
|
||||
ranges.extend(
|
||||
query
|
||||
.search(&search_buffer, Some(search_range.clone()))
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|match_range| {
|
||||
let start = search_buffer
|
||||
.anchor_after(search_range.start + match_range.start);
|
||||
let end = search_buffer
|
||||
.anchor_before(search_range.start + match_range.end);
|
||||
buffer.anchor_in_excerpt(excerpt_id, start).unwrap()
|
||||
..buffer.anchor_in_excerpt(excerpt_id, end).unwrap()
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ impl Editor {
|
|||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
line_height: Pixels,
|
||||
max_scroll_top: f32,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> bool {
|
||||
let viewport_height = bounds.size.height;
|
||||
|
@ -84,11 +85,6 @@ impl Editor {
|
|||
}
|
||||
}
|
||||
}
|
||||
let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
|
||||
(display_map.max_point().row().as_f32() - visible_lines + 1.).max(0.)
|
||||
} else {
|
||||
display_map.max_point().row().as_f32()
|
||||
};
|
||||
if scroll_position.y > max_scroll_top {
|
||||
scroll_position.y = max_scroll_top;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue