Files
oak-gpui/crates/editor/src/display_map/crease_map.rs
T
6fca1d2b0b Eliminate GPUI View, ViewContext, and WindowContext types (#22632)
There's still a bit more work to do on this, but this PR is compiling
(with warnings) after eliminating the key types. When the tasks below
are complete, this will be the new narrative for GPUI:

- `Entity<T>` - This replaces `View<T>`/`Model<T>`. It represents a unit
of state, and if `T` implements `Render`, then `Entity<T>` implements
`Element`.
- `&mut App` This replaces `AppContext` and represents the app.
- `&mut Context<T>` This replaces `ModelContext` and derefs to `App`. It
is provided by the framework when updating an entity.
- `&mut Window` Broken out of `&mut WindowContext` which no longer
exists. Every method that once took `&mut WindowContext` now takes `&mut
Window, &mut App` and every method that took `&mut ViewContext<T>` now
takes `&mut Window, &mut Context<T>`

Not pictured here are the two other failed attempts. It's been quite a
month!

Tasks:

- [x] Remove `View`, `ViewContext`, `WindowContext` and thread through
`Window`
- [x] [@cole-miller @mikayla-maki] Redraw window when entities change
- [x] [@cole-miller @mikayla-maki] Get examples and Zed running
- [x] [@cole-miller @mikayla-maki] Fix Zed rendering
- [x] [@mikayla-maki] Fix todo! macros and comments
- [x] Fix a bug where the editor would not be redrawn because of view
caching
- [x] remove publicness window.notify() and replace with
`AppContext::notify`
- [x] remove `observe_new_window_models`, replace with
`observe_new_models` with an optional window
- [x] Fix a bug where the project panel would not be redrawn because of
the wrong refresh() call being used
- [x] Fix the tests
- [x] Fix warnings by eliminating `Window` params or using `_`
- [x] Fix conflicts
- [x] Simplify generic code where possible
- [x] Rename types
- [ ] Update docs

### issues post merge

- [x] Issues switching between normal and insert mode
- [x] Assistant re-rendering failure
- [x] Vim test failures
- [x] Mac build issue



Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Joseph <joseph@zed.dev>
Co-authored-by: max <max@zed.dev>
Co-authored-by: Michael Sloan <michael@zed.dev>
Co-authored-by: Mikayla Maki <mikaylamaki@Mikaylas-MacBook-Pro.local>
Co-authored-by: Mikayla <mikayla.c.maki@gmail.com>
Co-authored-by: joão <joao@zed.dev>
2025-01-26 03:02:45 +00:00

492 lines
16 KiB
Rust

use collections::HashMap;
use gpui::{AnyElement, IntoElement};
use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToPoint};
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, fmt::Debug, ops::Range, sync::Arc};
use sum_tree::{Bias, SeekTarget, SumTree};
use text::Point;
use ui::{App, IconName, SharedString, Window};
use crate::{BlockStyle, FoldPlaceholder, RenderBlock};
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct CreaseId(usize);
pub struct CreaseMap {
snapshot: CreaseSnapshot,
next_id: CreaseId,
id_to_range: HashMap<CreaseId, Range<Anchor>>,
}
impl CreaseMap {
pub fn new(snapshot: &MultiBufferSnapshot) -> Self {
CreaseMap {
snapshot: CreaseSnapshot::new(snapshot),
next_id: CreaseId::default(),
id_to_range: HashMap::default(),
}
}
}
#[derive(Clone)]
pub struct CreaseSnapshot {
creases: SumTree<CreaseItem>,
}
impl CreaseSnapshot {
pub fn new(snapshot: &MultiBufferSnapshot) -> Self {
CreaseSnapshot {
creases: SumTree::new(snapshot),
}
}
/// Returns the first Crease starting on the specified buffer row.
pub fn query_row<'a>(
&'a self,
row: MultiBufferRow,
snapshot: &'a MultiBufferSnapshot,
) -> Option<&'a Crease<Anchor>> {
let start = snapshot.anchor_before(Point::new(row.0, 0));
let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
cursor.seek(&start, Bias::Left, snapshot);
while let Some(item) = cursor.item() {
match Ord::cmp(&item.crease.range().start.to_point(snapshot).row, &row.0) {
Ordering::Less => cursor.next(snapshot),
Ordering::Equal => {
if item.crease.range().start.is_valid(snapshot) {
return Some(&item.crease);
} else {
cursor.next(snapshot);
}
}
Ordering::Greater => break,
}
}
None
}
pub fn creases_in_range<'a>(
&'a self,
range: Range<MultiBufferRow>,
snapshot: &'a MultiBufferSnapshot,
) -> impl 'a + Iterator<Item = &'a Crease<Anchor>> {
let start = snapshot.anchor_before(Point::new(range.start.0, 0));
let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
cursor.seek(&start, Bias::Left, snapshot);
std::iter::from_fn(move || {
while let Some(item) = cursor.item() {
cursor.next(snapshot);
let crease_range = item.crease.range();
let crease_start = crease_range.start.to_point(snapshot);
let crease_end = crease_range.end.to_point(snapshot);
if crease_end.row > range.end.0 {
continue;
}
if crease_start.row >= range.start.0 && crease_end.row < range.end.0 {
return Some(&item.crease);
}
}
None
})
}
pub fn crease_items_with_offsets(
&self,
snapshot: &MultiBufferSnapshot,
) -> Vec<(CreaseId, Range<Point>)> {
let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
let mut results = Vec::new();
cursor.next(snapshot);
while let Some(item) = cursor.item() {
let crease_range = item.crease.range();
let start_point = crease_range.start.to_point(snapshot);
let end_point = crease_range.end.to_point(snapshot);
results.push((item.id, start_point..end_point));
cursor.next(snapshot);
}
results
}
}
type RenderToggleFn = Arc<
dyn Send
+ Sync
+ Fn(
MultiBufferRow,
bool,
Arc<dyn Send + Sync + Fn(bool, &mut Window, &mut App)>,
&mut Window,
&mut App,
) -> AnyElement,
>;
type RenderTrailerFn =
Arc<dyn Send + Sync + Fn(MultiBufferRow, bool, &mut Window, &mut App) -> AnyElement>;
#[derive(Clone)]
pub enum Crease<T> {
Inline {
range: Range<T>,
placeholder: FoldPlaceholder,
render_toggle: Option<RenderToggleFn>,
render_trailer: Option<RenderTrailerFn>,
metadata: Option<CreaseMetadata>,
},
Block {
range: Range<T>,
block_height: u32,
block_style: BlockStyle,
render_block: RenderBlock,
block_priority: usize,
render_toggle: Option<RenderToggleFn>,
},
}
/// Metadata about a [`Crease`], that is used for serialization.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct CreaseMetadata {
pub icon: IconName,
pub label: SharedString,
}
impl<T> Crease<T> {
pub fn simple(range: Range<T>, placeholder: FoldPlaceholder) -> Self {
Crease::Inline {
range,
placeholder,
render_toggle: None,
render_trailer: None,
metadata: None,
}
}
pub fn block(range: Range<T>, height: u32, style: BlockStyle, render: RenderBlock) -> Self {
Self::Block {
range,
block_height: height,
block_style: style,
render_block: render,
block_priority: 0,
render_toggle: None,
}
}
pub fn inline<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
range: Range<T>,
placeholder: FoldPlaceholder,
render_toggle: RenderToggle,
render_trailer: RenderTrailer,
) -> Self
where
RenderToggle: 'static
+ Send
+ Sync
+ Fn(
MultiBufferRow,
bool,
Arc<dyn Send + Sync + Fn(bool, &mut Window, &mut App)>,
&mut Window,
&mut App,
) -> ToggleElement
+ 'static,
ToggleElement: IntoElement,
RenderTrailer: 'static
+ Send
+ Sync
+ Fn(MultiBufferRow, bool, &mut Window, &mut App) -> TrailerElement
+ 'static,
TrailerElement: IntoElement,
{
Crease::Inline {
range,
placeholder,
render_toggle: Some(Arc::new(move |row, folded, toggle, window, cx| {
render_toggle(row, folded, toggle, window, cx).into_any_element()
})),
render_trailer: Some(Arc::new(move |row, folded, window, cx| {
render_trailer(row, folded, window, cx).into_any_element()
})),
metadata: None,
}
}
pub fn with_metadata(self, metadata: CreaseMetadata) -> Self {
match self {
Crease::Inline {
range,
placeholder,
render_toggle,
render_trailer,
..
} => Crease::Inline {
range,
placeholder,
render_toggle,
render_trailer,
metadata: Some(metadata),
},
Crease::Block { .. } => self,
}
}
pub fn range(&self) -> &Range<T> {
match self {
Crease::Inline { range, .. } => range,
Crease::Block { range, .. } => range,
}
}
}
impl<T> std::fmt::Debug for Crease<T>
where
T: Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Crease::Inline {
range, metadata, ..
} => f
.debug_struct("Crease::Inline")
.field("range", range)
.field("metadata", metadata)
.finish_non_exhaustive(),
Crease::Block {
range,
block_height,
..
} => f
.debug_struct("Crease::Block")
.field("range", range)
.field("height", block_height)
.finish_non_exhaustive(),
}
}
}
#[derive(Clone, Debug)]
struct CreaseItem {
id: CreaseId,
crease: Crease<Anchor>,
}
impl CreaseMap {
pub fn snapshot(&self) -> CreaseSnapshot {
self.snapshot.clone()
}
pub fn insert(
&mut self,
creases: impl IntoIterator<Item = Crease<Anchor>>,
snapshot: &MultiBufferSnapshot,
) -> Vec<CreaseId> {
let mut new_ids = Vec::new();
self.snapshot.creases = {
let mut new_creases = SumTree::new(snapshot);
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>(snapshot);
for crease in creases {
let crease_range = crease.range().clone();
new_creases.append(cursor.slice(&crease_range, Bias::Left, snapshot), snapshot);
let id = self.next_id;
self.next_id.0 += 1;
self.id_to_range.insert(id, crease_range);
new_creases.push(CreaseItem { crease, id }, snapshot);
new_ids.push(id);
}
new_creases.append(cursor.suffix(snapshot), snapshot);
new_creases
};
new_ids
}
pub fn remove(
&mut self,
ids: impl IntoIterator<Item = CreaseId>,
snapshot: &MultiBufferSnapshot,
) {
let mut removals = Vec::new();
for id in ids {
if let Some(range) = self.id_to_range.remove(&id) {
removals.push((id, range.clone()));
}
}
removals.sort_unstable_by(|(a_id, a_range), (b_id, b_range)| {
AnchorRangeExt::cmp(a_range, b_range, snapshot).then(b_id.cmp(a_id))
});
self.snapshot.creases = {
let mut new_creases = SumTree::new(snapshot);
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>(snapshot);
for (id, range) in removals {
new_creases.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
while let Some(item) = cursor.item() {
cursor.next(snapshot);
if item.id == id {
break;
} else {
new_creases.push(item.clone(), snapshot);
}
}
}
new_creases.append(cursor.suffix(snapshot), snapshot);
new_creases
};
}
}
#[derive(Debug, Clone)]
pub struct ItemSummary {
range: Range<Anchor>,
}
impl Default for ItemSummary {
fn default() -> Self {
Self {
range: Anchor::min()..Anchor::min(),
}
}
}
impl sum_tree::Summary for ItemSummary {
type Context = MultiBufferSnapshot;
fn zero(_cx: &Self::Context) -> Self {
Default::default()
}
fn add_summary(&mut self, other: &Self, _snapshot: &MultiBufferSnapshot) {
self.range = other.range.clone();
}
}
impl sum_tree::Item for CreaseItem {
type Summary = ItemSummary;
fn summary(&self, _cx: &MultiBufferSnapshot) -> Self::Summary {
ItemSummary {
range: self.crease.range().clone(),
}
}
}
/// Implements `SeekTarget` for `Range<Anchor>` to enable seeking within a `SumTree` of `CreaseItem`s.
impl SeekTarget<'_, ItemSummary, ItemSummary> for Range<Anchor> {
fn cmp(&self, cursor_location: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
AnchorRangeExt::cmp(self, &cursor_location.range, snapshot)
}
}
impl SeekTarget<'_, ItemSummary, ItemSummary> for Anchor {
fn cmp(&self, other: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
self.cmp(&other.range.start, snapshot)
}
}
#[cfg(test)]
mod test {
use super::*;
use gpui::{div, App};
use multi_buffer::MultiBuffer;
#[gpui::test]
fn test_insert_and_remove_creases(cx: &mut App) {
let text = "line1\nline2\nline3\nline4\nline5";
let buffer = MultiBuffer::build_simple(text, cx);
let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
let mut crease_map = CreaseMap::new(&buffer.read(cx).read(cx));
// Insert creases
let creases = [
Crease::inline(
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _window, _cx| div(),
|_row, _folded, _window, _cx| div(),
),
Crease::inline(
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _window, _cx| div(),
|_row, _folded, _window, _cx| div(),
),
];
let crease_ids = crease_map.insert(creases, &snapshot);
assert_eq!(crease_ids.len(), 2);
// Verify creases are inserted
let crease_snapshot = crease_map.snapshot();
assert!(crease_snapshot
.query_row(MultiBufferRow(1), &snapshot)
.is_some());
assert!(crease_snapshot
.query_row(MultiBufferRow(3), &snapshot)
.is_some());
// Remove creases
crease_map.remove(crease_ids, &snapshot);
// Verify creases are removed
let crease_snapshot = crease_map.snapshot();
assert!(crease_snapshot
.query_row(MultiBufferRow(1), &snapshot)
.is_none());
assert!(crease_snapshot
.query_row(MultiBufferRow(3), &snapshot)
.is_none());
}
#[gpui::test]
fn test_creases_in_range(cx: &mut App) {
let text = "line1\nline2\nline3\nline4\nline5\nline6\nline7";
let buffer = MultiBuffer::build_simple(text, cx);
let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
let mut crease_map = CreaseMap::new(&snapshot);
let creases = [
Crease::inline(
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _window, _cx| div(),
|_row, _folded, _window, _cx| div(),
),
Crease::inline(
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _window, _cx| div(),
|_row, _folded, _window, _cx| div(),
),
Crease::inline(
snapshot.anchor_before(Point::new(5, 0))..snapshot.anchor_after(Point::new(5, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _window, _cx| div(),
|_row, _folded, _window, _cx| div(),
),
];
crease_map.insert(creases, &snapshot);
let crease_snapshot = crease_map.snapshot();
let range = MultiBufferRow(0)..MultiBufferRow(7);
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
assert_eq!(creases.len(), 3);
let range = MultiBufferRow(2)..MultiBufferRow(5);
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
assert_eq!(creases.len(), 1);
assert_eq!(creases[0].range().start.to_point(&snapshot).row, 3);
let range = MultiBufferRow(0)..MultiBufferRow(2);
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
assert_eq!(creases.len(), 1);
assert_eq!(creases[0].range().start.to_point(&snapshot).row, 1);
let range = MultiBufferRow(6)..MultiBufferRow(7);
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
assert_eq!(creases.len(), 0);
}
}