diff --git a/Cargo.lock b/Cargo.lock index ea80c92376..d5d0493936 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1866,7 +1866,6 @@ dependencies = [ "async-tar", "clock", "collections", - "context_menu", "fs", "futures 0.3.28", "gpui2", @@ -2622,6 +2621,60 @@ dependencies = [ "workspace", ] +[[package]] +name = "editor2" +version = "0.1.0" +dependencies = [ + "aho-corasick", + "anyhow", + "client2", + "clock", + "collections", + "convert_case 0.6.0", + "copilot2", + "ctor", + "db2", + "drag_and_drop", + "env_logger 0.9.3", + "futures 0.3.28", + "fuzzy2", + "git", + "gpui2", + "indoc", + "itertools 0.10.5", + "language2", + "lazy_static", + "log", + "lsp2", + "multi_buffer2", + "ordered-float 2.10.0", + "parking_lot 0.11.2", + "postage", + "project2", + "rand 0.8.5", + "rich_text2", + "rpc2", + "schemars", + "serde", + "serde_derive", + "serde_json", + "settings2", + "smallvec", + "smol", + "snippet", + "sqlez", + "sum_tree", + "text2", + "theme2", + "tree-sitter", + "tree-sitter-html", + "tree-sitter-rust", + "tree-sitter-typescript", + "unindent", + "util", + "workspace2", +] + [[package]] name = "either" version = "1.9.0" @@ -3524,7 +3577,6 @@ dependencies = [ "foreign-types", "futures 0.3.28", "gpui2_macros", - "gpui_macros", "image", "itertools 0.10.5", "lazy_static", @@ -4374,6 +4426,7 @@ dependencies = [ "lsp2", "parking_lot 0.11.2", "postage", + "pulldown-cmark", "rand 0.8.5", "regex", "rpc2", @@ -11109,6 +11162,7 @@ dependencies = [ "copilot2", "ctor", "db2", + "editor2", "env_logger 0.9.3", "feature_flags2", "fs2", diff --git a/crates/copilot2/Cargo.toml b/crates/copilot2/Cargo.toml index 2021194607..2ce432d9fc 100644 --- a/crates/copilot2/Cargo.toml +++ b/crates/copilot2/Cargo.toml @@ -20,7 +20,7 @@ test-support = [ [dependencies] collections = { path = "../collections" } -context_menu = { path = "../context_menu" } +# context_menu = { path = "../context_menu" } gpui = { package = "gpui2", path = "../gpui2" } language = { package = "language2", path = "../language2" } settings = { package = "settings2", path = "../settings2" } diff --git a/crates/copilot2/src/copilot2.rs b/crates/copilot2/src/copilot2.rs index 6b1190a5bf..ac52bf156d 100644 --- a/crates/copilot2/src/copilot2.rs +++ b/crates/copilot2/src/copilot2.rs @@ -42,6 +42,11 @@ use util::{ // copilot, // [Suggest, NextSuggestion, PreviousSuggestion, Reinstall] // ); +// +pub struct Suggest; +pub struct NextSuggestion; +pub struct PreviousSuggestion; +pub struct Reinstall; pub fn init( new_server_id: LanguageServerId, diff --git a/crates/editor2/Cargo.toml b/crates/editor2/Cargo.toml new file mode 100644 index 0000000000..121c03ec00 --- /dev/null +++ b/crates/editor2/Cargo.toml @@ -0,0 +1,93 @@ +[package] +name = "editor2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/editor.rs" +doctest = false + +[features] +test-support = [ + "copilot/test-support", + "text/test-support", + "language/test-support", + "gpui/test-support", + "multi_buffer/test-support", + "project/test-support", + "util/test-support", + "workspace/test-support", + "tree-sitter-rust", + "tree-sitter-typescript" +] + +[dependencies] +client = { package = "client2", path = "../client2" } +clock = { path = "../clock" } +copilot = { package="copilot2", path = "../copilot2" } +db = { package="db2", path = "../db2" } +drag_and_drop = { path = "../drag_and_drop" } +collections = { path = "../collections" } +# context_menu = { path = "../context_menu" } +fuzzy = { package = "fuzzy2", path = "../fuzzy2" } +git = { path = "../git" } +gpui = { package = "gpui2", path = "../gpui2" } +language = { package = "language2", path = "../language2" } +lsp = { package = "lsp2", path = "../lsp2" } +multi_buffer = { package = "multi_buffer2", path = "../multi_buffer2" } +project = { package = "project2", path = "../project2" } +rpc = { package = "rpc2", path = "../rpc2" } +rich_text = { package = "rich_text2", path = "../rich_text2" } +settings = { package="settings2", path = "../settings2" } +snippet = { path = "../snippet" } +sum_tree = { path = "../sum_tree" } +text = { package="text2", path = "../text2" } +theme = { package="theme2", path = "../theme2" } +util = { path = "../util" } +sqlez = { path = "../sqlez" } +workspace = { package = "workspace2", path = "../workspace2" } + +aho-corasick = "1.1" +anyhow.workspace = true +convert_case = "0.6.0" +futures.workspace = true +indoc = "1.0.4" +itertools = "0.10" +lazy_static.workspace = true +log.workspace = true +ordered-float.workspace = true +parking_lot.workspace = true +postage.workspace = true +rand.workspace = true +schemars.workspace = true +serde.workspace = true +serde_json.workspace = true +serde_derive.workspace = true +smallvec.workspace = true +smol.workspace = true + +tree-sitter-rust = { workspace = true, optional = true } +tree-sitter-html = { workspace = true, optional = true } +tree-sitter-typescript = { workspace = true, optional = true } + +[dev-dependencies] +copilot = { package="copilot2", path = "../copilot2", features = ["test-support"] } +text = { package="text2", path = "../text2", features = ["test-support"] } +language = { package="language2", path = "../language2", features = ["test-support"] } +lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] } +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } +util = { path = "../util", features = ["test-support"] } +project = { package = "project2", path = "../project2", features = ["test-support"] } +settings = { package = "settings2", path = "../settings2", features = ["test-support"] } +workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] } +multi_buffer = { package = "multi_buffer2", path = "../multi_buffer2", features = ["test-support"] } + +ctor.workspace = true +env_logger.workspace = true +rand.workspace = true +unindent.workspace = true +tree-sitter.workspace = true +tree-sitter-rust.workspace = true +tree-sitter-html.workspace = true +tree-sitter-typescript.workspace = true diff --git a/crates/editor2/src/blink_manager.rs b/crates/editor2/src/blink_manager.rs new file mode 100644 index 0000000000..d25e30f649 --- /dev/null +++ b/crates/editor2/src/blink_manager.rs @@ -0,0 +1,102 @@ +use crate::EditorSettings; +use gpui::ModelContext; +use settings::Settings; +use settings::SettingsStore; +use smol::Timer; +use std::time::Duration; + +pub struct BlinkManager { + blink_interval: Duration, + + blink_epoch: usize, + blinking_paused: bool, + visible: bool, + enabled: bool, +} + +impl BlinkManager { + pub fn new(blink_interval: Duration, cx: &mut ModelContext) -> Self { + // Make sure we blink the cursors if the setting is re-enabled + cx.observe_global::(move |this, cx| { + this.blink_cursors(this.blink_epoch, cx) + }) + .detach(); + + Self { + blink_interval, + + blink_epoch: 0, + blinking_paused: false, + visible: true, + enabled: false, + } + } + + fn next_blink_epoch(&mut self) -> usize { + self.blink_epoch += 1; + self.blink_epoch + } + + pub fn pause_blinking(&mut self, cx: &mut ModelContext) { + self.show_cursor(cx); + + let epoch = self.next_blink_epoch(); + let interval = self.blink_interval; + cx.spawn(|this, mut cx| async move { + Timer::after(interval).await; + this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx)) + }) + .detach(); + } + + fn resume_cursor_blinking(&mut self, epoch: usize, cx: &mut ModelContext) { + if epoch == self.blink_epoch { + self.blinking_paused = false; + self.blink_cursors(epoch, cx); + } + } + + fn blink_cursors(&mut self, epoch: usize, cx: &mut ModelContext) { + if EditorSettings::get_global(cx).cursor_blink { + if epoch == self.blink_epoch && self.enabled && !self.blinking_paused { + self.visible = !self.visible; + cx.notify(); + + let epoch = self.next_blink_epoch(); + let interval = self.blink_interval; + cx.spawn(|this, mut cx| async move { + Timer::after(interval).await; + if let Some(this) = this.upgrade() { + this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx)); + } + }) + .detach(); + } + } else { + self.show_cursor(cx); + } + } + + pub fn show_cursor(&mut self, cx: &mut ModelContext<'_, BlinkManager>) { + if !self.visible { + self.visible = true; + cx.notify(); + } + } + + pub fn enable(&mut self, cx: &mut ModelContext) { + self.enabled = true; + // Set cursors as invisible and start blinking: this causes cursors + // to be visible during the next render. + self.visible = false; + self.blink_cursors(self.blink_epoch, cx); + } + + pub fn disable(&mut self, _cx: &mut ModelContext) { + self.enabled = false; + } + + pub fn visible(&self) -> bool { + self.visible + } +} diff --git a/crates/editor2/src/display_map.rs b/crates/editor2/src/display_map.rs new file mode 100644 index 0000000000..2955a929f8 --- /dev/null +++ b/crates/editor2/src/display_map.rs @@ -0,0 +1,1900 @@ +mod block_map; +mod fold_map; +mod inlay_map; +mod tab_map; +mod wrap_map; + +use crate::{ + link_go_to_definition::InlayHighlight, movement::TextLayoutDetails, Anchor, AnchorRangeExt, + InlayId, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, +}; +pub use block_map::{BlockMap, BlockPoint}; +use collections::{BTreeMap, HashMap, HashSet}; +use fold_map::FoldMap; +use gpui::{Font, FontId, HighlightStyle, Hsla, Line, Model, ModelContext, Pixels}; +use inlay_map::InlayMap; +use language::{ + language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription, +}; +use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc}; +use sum_tree::{Bias, TreeMap}; +use tab_map::TabMap; +use wrap_map::WrapMap; + +pub use block_map::{ + BlockBufferRows as DisplayBufferRows, BlockChunks as DisplayChunks, BlockContext, + BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock, +}; + +pub use self::fold_map::FoldPoint; +pub use self::inlay_map::{Inlay, InlayOffset, InlayPoint}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum FoldStatus { + Folded, + Foldable, +} + +pub trait ToDisplayPoint { + fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint; +} + +type TextHighlights = TreeMap, Arc<(HighlightStyle, Vec>)>>; +type InlayHighlights = BTreeMap>; + +pub struct DisplayMap { + buffer: Model, + buffer_subscription: BufferSubscription, + fold_map: FoldMap, + inlay_map: InlayMap, + tab_map: TabMap, + wrap_map: Model, + block_map: BlockMap, + text_highlights: TextHighlights, + inlay_highlights: InlayHighlights, + pub clip_at_line_ends: bool, +} + +impl DisplayMap { + pub fn new( + buffer: Model, + font: Font, + font_size: Pixels, + wrap_width: Option, + buffer_header_height: u8, + excerpt_header_height: u8, + cx: &mut ModelContext, + ) -> Self { + let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); + + let tab_size = Self::tab_size(&buffer, cx); + let (inlay_map, snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx)); + let (fold_map, snapshot) = FoldMap::new(snapshot); + let (tab_map, snapshot) = TabMap::new(snapshot, tab_size); + let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx); + let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height); + cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach(); + DisplayMap { + buffer, + buffer_subscription, + fold_map, + inlay_map, + tab_map, + wrap_map, + block_map, + text_highlights: Default::default(), + inlay_highlights: Default::default(), + clip_at_line_ends: false, + } + } + + pub fn snapshot(&mut self, cx: &mut ModelContext) -> DisplaySnapshot { + let buffer_snapshot = self.buffer.read(cx).snapshot(cx); + let edits = self.buffer_subscription.consume().into_inner(); + let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits); + let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits); + let tab_size = Self::tab_size(&self.buffer, cx); + let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot.clone(), edits, tab_size); + 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); + + DisplaySnapshot { + buffer_snapshot: self.buffer.read(cx).snapshot(cx), + fold_snapshot, + inlay_snapshot, + tab_snapshot, + wrap_snapshot, + block_snapshot, + text_highlights: self.text_highlights.clone(), + inlay_highlights: self.inlay_highlights.clone(), + clip_at_line_ends: self.clip_at_line_ends, + } + } + + pub fn set_state(&mut self, other: &DisplaySnapshot, cx: &mut ModelContext) { + self.fold( + other + .folds_in_range(0..other.buffer_snapshot.len()) + .map(|fold| fold.to_offset(&other.buffer_snapshot)), + cx, + ); + } + + pub fn fold( + &mut self, + ranges: impl IntoIterator>, + cx: &mut ModelContext, + ) { + 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 (mut fold_map, snapshot, edits) = self.fold_map.write(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)); + self.block_map.read(snapshot, edits); + let (snapshot, edits) = fold_map.fold(ranges); + 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)); + self.block_map.read(snapshot, edits); + } + + pub fn unfold( + &mut self, + ranges: impl IntoIterator>, + inclusive: bool, + cx: &mut ModelContext, + ) { + 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 (mut fold_map, snapshot, edits) = self.fold_map.write(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)); + self.block_map.read(snapshot, edits); + let (snapshot, edits) = fold_map.unfold(ranges, inclusive); + 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)); + self.block_map.read(snapshot, edits); + } + + pub fn insert_blocks( + &mut self, + blocks: impl IntoIterator>, + cx: &mut ModelContext, + ) -> Vec { + 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 mut block_map = self.block_map.write(snapshot, edits); + block_map.insert(blocks) + } + + pub fn replace_blocks(&mut self, styles: HashMap) { + self.block_map.replace(styles); + } + + pub fn remove_blocks(&mut self, ids: HashSet, cx: &mut ModelContext) { + 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 mut block_map = self.block_map.write(snapshot, edits); + block_map.remove(ids); + } + + pub fn highlight_text( + &mut self, + type_id: TypeId, + ranges: Vec>, + style: HighlightStyle, + ) { + self.text_highlights + .insert(Some(type_id), Arc::new((style, ranges))); + } + + pub fn highlight_inlays( + &mut self, + type_id: TypeId, + highlights: Vec, + style: HighlightStyle, + ) { + for highlight in highlights { + self.inlay_highlights + .entry(type_id) + .or_default() + .insert(highlight.inlay, (style, highlight)); + } + } + + pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range])> { + let highlights = self.text_highlights.get(&Some(type_id))?; + Some((highlights.0, &highlights.1)) + } + pub fn clear_highlights(&mut self, type_id: TypeId) -> bool { + let mut cleared = self.text_highlights.remove(&Some(type_id)).is_some(); + cleared |= self.inlay_highlights.remove(&type_id).is_none(); + cleared + } + + pub fn set_font(&self, font: Font, font_size: Pixels, cx: &mut ModelContext) -> bool { + self.wrap_map + .update(cx, |map, cx| map.set_font_with_size(font, font_size, cx)) + } + + pub fn set_fold_ellipses_color(&mut self, color: Hsla) -> bool { + self.fold_map.set_ellipses_color(color) + } + + pub fn set_wrap_width(&self, width: Option, cx: &mut ModelContext) -> bool { + self.wrap_map + .update(cx, |map, cx| map.set_wrap_width(width, cx)) + } + + pub fn current_inlays(&self) -> impl Iterator { + self.inlay_map.current_inlays() + } + + pub fn splice_inlays( + &mut self, + to_remove: Vec, + to_insert: Vec, + cx: &mut ModelContext, + ) { + if to_remove.is_empty() && to_insert.is_empty() { + return; + } + let buffer_snapshot = self.buffer.read(cx).snapshot(cx); + let edits = self.buffer_subscription.consume().into_inner(); + let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits); + let (snapshot, edits) = self.fold_map.read(snapshot, edits); + let tab_size = Self::tab_size(&self.buffer, cx); + 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)); + self.block_map.read(snapshot, edits); + + let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert); + 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)); + self.block_map.read(snapshot, edits); + } + + fn tab_size(buffer: &Model, cx: &mut ModelContext) -> NonZeroU32 { + let language = buffer + .read(cx) + .as_singleton() + .and_then(|buffer| buffer.read(cx).language()); + language_settings(language.as_deref(), None, cx).tab_size + } + + #[cfg(test)] + pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool { + self.wrap_map.read(cx).is_rewrapping() + } +} + +#[derive(Debug, Default)] +pub struct Highlights<'a> { + pub text_highlights: Option<&'a TextHighlights>, + pub inlay_highlights: Option<&'a InlayHighlights>, + pub inlay_highlight_style: Option, + pub suggestion_highlight_style: Option, +} + +pub struct HighlightedChunk<'a> { + pub chunk: &'a str, + pub style: Option, + pub is_tab: bool, +} + +pub struct DisplaySnapshot { + pub buffer_snapshot: MultiBufferSnapshot, + pub fold_snapshot: fold_map::FoldSnapshot, + inlay_snapshot: inlay_map::InlaySnapshot, + tab_snapshot: tab_map::TabSnapshot, + wrap_snapshot: wrap_map::WrapSnapshot, + block_snapshot: block_map::BlockSnapshot, + text_highlights: TextHighlights, + inlay_highlights: InlayHighlights, + clip_at_line_ends: bool, +} + +impl DisplaySnapshot { + #[cfg(test)] + pub fn fold_count(&self) -> usize { + self.fold_snapshot.fold_count() + } + + pub fn is_empty(&self) -> bool { + self.buffer_snapshot.len() == 0 + } + + pub fn buffer_rows(&self, start_row: u32) -> DisplayBufferRows { + self.block_snapshot.buffer_rows(start_row) + } + + pub fn max_buffer_row(&self) -> u32 { + self.buffer_snapshot.max_buffer_row() + } + + pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) { + loop { + let mut inlay_point = self.inlay_snapshot.to_inlay_point(point); + let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left); + fold_point.0.column = 0; + inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); + point = self.inlay_snapshot.to_buffer_point(inlay_point); + + let mut display_point = self.point_to_display_point(point, Bias::Left); + *display_point.column_mut() = 0; + let next_point = self.display_point_to_point(display_point, Bias::Left); + if next_point == point { + return (point, display_point); + } + point = next_point; + } + } + + pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) { + loop { + let mut inlay_point = self.inlay_snapshot.to_inlay_point(point); + let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Right); + fold_point.0.column = self.fold_snapshot.line_len(fold_point.row()); + inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); + point = self.inlay_snapshot.to_buffer_point(inlay_point); + + let mut display_point = self.point_to_display_point(point, Bias::Right); + *display_point.column_mut() = self.line_len(display_point.row()); + let next_point = self.display_point_to_point(display_point, Bias::Right); + if next_point == point { + return (point, display_point); + } + point = next_point; + } + } + + // used by line_mode selections and tries to match vim behaviour + pub fn expand_to_line(&self, range: Range) -> Range { + let new_start = if range.start.row == 0 { + Point::new(0, 0) + } else if range.start.row == self.max_buffer_row() + || (range.end.column > 0 && range.end.row == self.max_buffer_row()) + { + Point::new(range.start.row - 1, self.line_len(range.start.row - 1)) + } else { + self.prev_line_boundary(range.start).0 + }; + + let new_end = if range.end.column == 0 { + range.end + } else if range.end.row < self.max_buffer_row() { + self.buffer_snapshot + .clip_point(Point::new(range.end.row + 1, 0), Bias::Left) + } else { + self.buffer_snapshot.max_point() + }; + + new_start..new_end + } + + fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint { + let inlay_point = self.inlay_snapshot.to_inlay_point(point); + let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias); + let tab_point = self.tab_snapshot.to_tab_point(fold_point); + let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point); + let block_point = self.block_snapshot.to_block_point(wrap_point); + DisplayPoint(block_point) + } + + fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point { + self.inlay_snapshot + .to_buffer_point(self.display_point_to_inlay_point(point, bias)) + } + + pub fn display_point_to_inlay_offset(&self, point: DisplayPoint, bias: Bias) -> InlayOffset { + self.inlay_snapshot + .to_offset(self.display_point_to_inlay_point(point, bias)) + } + + pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset { + self.inlay_snapshot + .to_inlay_offset(anchor.to_offset(&self.buffer_snapshot)) + } + + fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint { + let block_point = point.0; + let wrap_point = self.block_snapshot.to_wrap_point(block_point); + let tab_point = self.wrap_snapshot.to_tab_point(wrap_point); + let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0; + fold_point.to_inlay_point(&self.fold_snapshot) + } + + pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint { + let block_point = point.0; + let wrap_point = self.block_snapshot.to_wrap_point(block_point); + let tab_point = self.wrap_snapshot.to_tab_point(wrap_point); + self.tab_snapshot.to_fold_point(tab_point, bias).0 + } + + pub fn fold_point_to_display_point(&self, fold_point: FoldPoint) -> DisplayPoint { + let tab_point = self.tab_snapshot.to_tab_point(fold_point); + let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point); + let block_point = self.block_snapshot.to_block_point(wrap_point); + DisplayPoint(block_point) + } + + pub fn max_point(&self) -> DisplayPoint { + DisplayPoint(self.block_snapshot.max_point()) + } + + /// Returns text chunks starting at the given display row until the end of the file + pub fn text_chunks(&self, display_row: u32) -> impl Iterator { + self.block_snapshot + .chunks( + display_row..self.max_point().row() + 1, + false, + Highlights::default(), + ) + .map(|h| h.text) + } + + /// Returns text chunks starting at the end of the given display row in reverse until the start of the file + pub fn reverse_text_chunks(&self, display_row: u32) -> impl Iterator { + (0..=display_row).into_iter().rev().flat_map(|row| { + self.block_snapshot + .chunks(row..row + 1, false, Highlights::default()) + .map(|h| h.text) + .collect::>() + .into_iter() + .rev() + }) + } + + pub fn chunks<'a>( + &'a self, + display_rows: Range, + language_aware: bool, + inlay_highlight_style: Option, + suggestion_highlight_style: Option, + ) -> DisplayChunks<'a> { + self.block_snapshot.chunks( + display_rows, + language_aware, + Highlights { + text_highlights: Some(&self.text_highlights), + inlay_highlights: Some(&self.inlay_highlights), + inlay_highlight_style, + suggestion_highlight_style, + }, + ) + } + + // pub fn highlighted_chunks<'a>( + // &'a self, + // display_rows: Range, + // language_aware: bool, + // style: &'a EditorStyle, + // ) -> impl Iterator> { + // self.chunks( + // display_rows, + // language_aware, + // Some(style.theme.hint), + // Some(style.theme.suggestion), + // ) + // .map(|chunk| { + // let mut highlight_style = chunk + // .syntax_highlight_id + // .and_then(|id| id.style(&style.syntax)); + + // if let Some(chunk_highlight) = chunk.highlight_style { + // if let Some(highlight_style) = highlight_style.as_mut() { + // highlight_style.highlight(chunk_highlight); + // } else { + // highlight_style = Some(chunk_highlight); + // } + // } + + // let mut diagnostic_highlight = HighlightStyle::default(); + + // if chunk.is_unnecessary { + // diagnostic_highlight.fade_out = Some(style.unnecessary_code_fade); + // } + + // if let Some(severity) = chunk.diagnostic_severity { + // // Omit underlines for HINT/INFO diagnostics on 'unnecessary' code. + // if severity <= DiagnosticSeverity::WARNING || !chunk.is_unnecessary { + // todo!() + // // let diagnostic_style = super::diagnostic_style(severity, true, style); + // // diagnostic_highlight.underline = Some(UnderlineStyle { + // // color: Some(diagnostic_style.message.text.color), + // // thickness: 1.0.into(), + // // wavy: true, + // // }); + // } + // } + + // if let Some(highlight_style) = highlight_style.as_mut() { + // highlight_style.highlight(diagnostic_highlight); + // } else { + // highlight_style = Some(diagnostic_highlight); + // } + + // HighlightedChunk { + // chunk: chunk.text, + // style: highlight_style, + // is_tab: chunk.is_tab, + // } + // }) + // } + + pub fn lay_out_line_for_row( + &self, + display_row: u32, + TextLayoutDetails { + text_system, + editor_style, + }: &TextLayoutDetails, + ) -> Line { + todo!() + // let mut styles = Vec::new(); + // let mut line = String::new(); + // let mut ended_in_newline = false; + + // let range = display_row..display_row + 1; + // for chunk in self.highlighted_chunks(range, false, editor_style) { + // line.push_str(chunk.chunk); + + // let text_style = if let Some(style) = chunk.style { + // editor_style + // .text + // .clone() + // .highlight(style, text_system) + // .map(Cow::Owned) + // .unwrap_or_else(|_| Cow::Borrowed(&editor_style.text)) + // } else { + // Cow::Borrowed(&editor_style.text) + // }; + // ended_in_newline = chunk.chunk.ends_with("\n"); + + // styles.push( + // todo!(), // len: chunk.chunk.len(), + // // font_id: text_style.font_id, + // // color: text_style.color, + // // underline: text_style.underline, + // ); + // } + + // // our pixel positioning logic assumes each line ends in \n, + // // this is almost always true except for the last line which + // // may have no trailing newline. + // if !ended_in_newline && display_row == self.max_point().row() { + // line.push_str("\n"); + + // todo!(); + // // styles.push(RunStyle { + // // len: "\n".len(), + // // font_id: editor_style.text.font_id, + // // color: editor_style.text_color, + // // underline: editor_style.text.underline, + // // }); + // } + + // text_system.layout_text(&line, editor_style.text.font_size, &styles, None) + } + + pub fn x_for_point( + &self, + display_point: DisplayPoint, + text_layout_details: &TextLayoutDetails, + ) -> Pixels { + let layout_line = self.lay_out_line_for_row(display_point.row(), text_layout_details); + layout_line.x_for_index(display_point.column() as usize) + } + + pub fn column_for_x( + &self, + display_row: u32, + x_coordinate: Pixels, + text_layout_details: &TextLayoutDetails, + ) -> u32 { + let layout_line = self.lay_out_line_for_row(display_row, text_layout_details); + layout_line.closest_index_for_x(x_coordinate) as u32 + } + + pub fn chars_at( + &self, + mut point: DisplayPoint, + ) -> impl Iterator + '_ { + point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left)); + self.text_chunks(point.row()) + .flat_map(str::chars) + .skip_while({ + let mut column = 0; + move |char| { + let at_point = column >= point.column(); + column += char.len_utf8() as u32; + !at_point + } + }) + .map(move |ch| { + let result = (ch, point); + if ch == '\n' { + *point.row_mut() += 1; + *point.column_mut() = 0; + } else { + *point.column_mut() += ch.len_utf8() as u32; + } + result + }) + } + + pub fn reverse_chars_at( + &self, + mut point: DisplayPoint, + ) -> impl Iterator + '_ { + point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left)); + self.reverse_text_chunks(point.row()) + .flat_map(|chunk| chunk.chars().rev()) + .skip_while({ + let mut column = self.line_len(point.row()); + if self.max_point().row() > point.row() { + column += 1; + } + + move |char| { + let at_point = column <= point.column(); + column = column.saturating_sub(char.len_utf8() as u32); + !at_point + } + }) + .map(move |ch| { + if ch == '\n' { + *point.row_mut() -= 1; + *point.column_mut() = self.line_len(point.row()); + } else { + *point.column_mut() = point.column().saturating_sub(ch.len_utf8() as u32); + } + (ch, point) + }) + } + + pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 { + let mut count = 0; + let mut column = 0; + for (c, _) in self.chars_at(DisplayPoint::new(display_row, 0)) { + if column >= target { + break; + } + count += 1; + column += c.len_utf8() as u32; + } + count + } + + pub fn column_from_chars(&self, display_row: u32, char_count: u32) -> u32 { + let mut column = 0; + + for (count, (c, _)) in self.chars_at(DisplayPoint::new(display_row, 0)).enumerate() { + if c == '\n' || count >= char_count as usize { + break; + } + column += c.len_utf8() as u32; + } + + column + } + + pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint { + let mut clipped = self.block_snapshot.clip_point(point.0, bias); + if self.clip_at_line_ends { + clipped = self.clip_at_line_end(DisplayPoint(clipped)).0 + } + DisplayPoint(clipped) + } + + pub fn clip_at_line_end(&self, point: DisplayPoint) -> DisplayPoint { + let mut point = point.0; + if point.column == self.line_len(point.row) { + point.column = point.column.saturating_sub(1); + point = self.block_snapshot.clip_point(point, Bias::Left); + } + DisplayPoint(point) + } + + pub fn folds_in_range(&self, range: Range) -> impl Iterator> + where + T: ToOffset, + { + self.fold_snapshot.folds_in_range(range) + } + + pub fn blocks_in_range( + &self, + rows: Range, + ) -> impl Iterator { + self.block_snapshot.blocks_in_range(rows) + } + + pub fn intersects_fold(&self, offset: T) -> bool { + self.fold_snapshot.intersects_fold(offset) + } + + pub fn is_line_folded(&self, buffer_row: u32) -> bool { + self.fold_snapshot.is_line_folded(buffer_row) + } + + pub fn is_block_line(&self, display_row: u32) -> bool { + self.block_snapshot.is_block_line(display_row) + } + + pub fn soft_wrap_indent(&self, display_row: u32) -> Option { + let wrap_row = self + .block_snapshot + .to_wrap_point(BlockPoint::new(display_row, 0)) + .row(); + self.wrap_snapshot.soft_wrap_indent(wrap_row) + } + + pub fn text(&self) -> String { + self.text_chunks(0).collect() + } + + pub fn line(&self, display_row: u32) -> String { + let mut result = String::new(); + for chunk in self.text_chunks(display_row) { + if let Some(ix) = chunk.find('\n') { + result.push_str(&chunk[0..ix]); + break; + } else { + result.push_str(chunk); + } + } + result + } + + pub fn line_indent(&self, display_row: u32) -> (u32, bool) { + let mut indent = 0; + let mut is_blank = true; + for (c, _) in self.chars_at(DisplayPoint::new(display_row, 0)) { + if c == ' ' { + indent += 1; + } else { + is_blank = c == '\n'; + break; + } + } + (indent, is_blank) + } + + pub fn line_indent_for_buffer_row(&self, buffer_row: u32) -> (u32, bool) { + let (buffer, range) = self + .buffer_snapshot + .buffer_line_for_row(buffer_row) + .unwrap(); + + let mut indent_size = 0; + let mut is_blank = false; + for c in buffer.chars_at(Point::new(range.start.row, 0)) { + if c == ' ' || c == '\t' { + indent_size += 1; + } else { + if c == '\n' { + is_blank = true; + } + break; + } + } + + (indent_size, is_blank) + } + + pub fn line_len(&self, row: u32) -> u32 { + self.block_snapshot.line_len(row) + } + + pub fn longest_row(&self) -> u32 { + self.block_snapshot.longest_row() + } + + pub fn fold_for_line(self: &Self, buffer_row: u32) -> Option { + if self.is_line_folded(buffer_row) { + Some(FoldStatus::Folded) + } else if self.is_foldable(buffer_row) { + Some(FoldStatus::Foldable) + } else { + None + } + } + + pub fn is_foldable(self: &Self, buffer_row: u32) -> bool { + let max_row = self.buffer_snapshot.max_buffer_row(); + if buffer_row >= max_row { + return false; + } + + let (indent_size, is_blank) = self.line_indent_for_buffer_row(buffer_row); + if is_blank { + return false; + } + + for next_row in (buffer_row + 1)..=max_row { + let (next_indent_size, next_line_is_blank) = self.line_indent_for_buffer_row(next_row); + if next_indent_size > indent_size { + return true; + } else if !next_line_is_blank { + break; + } + } + + false + } + + pub fn foldable_range(self: &Self, buffer_row: u32) -> Option> { + let start = Point::new(buffer_row, self.buffer_snapshot.line_len(buffer_row)); + if self.is_foldable(start.row) && !self.is_line_folded(start.row) { + let (start_indent, _) = self.line_indent_for_buffer_row(buffer_row); + let max_point = self.buffer_snapshot.max_point(); + let mut end = None; + + for row in (buffer_row + 1)..=max_point.row { + let (indent, is_blank) = self.line_indent_for_buffer_row(row); + if !is_blank && indent <= start_indent { + let prev_row = row - 1; + end = Some(Point::new( + prev_row, + self.buffer_snapshot.line_len(prev_row), + )); + break; + } + } + let end = end.unwrap_or(max_point); + Some(start..end) + } else { + None + } + } + + #[cfg(any(test, feature = "test-support"))] + pub fn text_highlight_ranges( + &self, + ) -> Option>)>> { + let type_id = TypeId::of::(); + self.text_highlights.get(&Some(type_id)).cloned() + } + + #[cfg(any(test, feature = "test-support"))] + pub fn inlay_highlights( + &self, + ) -> Option<&HashMap> { + let type_id = TypeId::of::(); + self.inlay_highlights.get(&type_id) + } +} + +#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct DisplayPoint(BlockPoint); + +impl Debug for DisplayPoint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!( + "DisplayPoint({}, {})", + self.row(), + self.column() + )) + } +} + +impl DisplayPoint { + pub fn new(row: u32, column: u32) -> Self { + Self(BlockPoint(Point::new(row, column))) + } + + pub fn zero() -> Self { + Self::new(0, 0) + } + + pub fn is_zero(&self) -> bool { + self.0.is_zero() + } + + pub fn row(self) -> u32 { + self.0.row + } + + pub fn column(self) -> u32 { + self.0.column + } + + pub fn row_mut(&mut self) -> &mut u32 { + &mut self.0.row + } + + pub fn column_mut(&mut self) -> &mut u32 { + &mut self.0.column + } + + pub fn to_point(self, map: &DisplaySnapshot) -> Point { + map.display_point_to_point(self, Bias::Left) + } + + pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize { + let wrap_point = map.block_snapshot.to_wrap_point(self.0); + let tab_point = map.wrap_snapshot.to_tab_point(wrap_point); + let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0; + let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot); + map.inlay_snapshot + .to_buffer_offset(map.inlay_snapshot.to_offset(inlay_point)) + } +} + +impl ToDisplayPoint for usize { + fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint { + map.point_to_display_point(self.to_point(&map.buffer_snapshot), Bias::Left) + } +} + +impl ToDisplayPoint for OffsetUtf16 { + fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint { + self.to_offset(&map.buffer_snapshot).to_display_point(map) + } +} + +impl ToDisplayPoint for Point { + fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint { + map.point_to_display_point(*self, Bias::Left) + } +} + +impl ToDisplayPoint for Anchor { + fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint { + self.to_point(&map.buffer_snapshot).to_display_point(map) + } +} + +pub fn next_rows(display_row: u32, display_map: &DisplaySnapshot) -> impl Iterator { + let max_row = display_map.max_point().row(); + let start_row = display_row + 1; + let mut current = None; + std::iter::from_fn(move || { + if current == None { + current = Some(start_row); + } else { + current = Some(current.unwrap() + 1) + } + if current.unwrap() > max_row { + None + } else { + current + } + }) +} + +// #[cfg(test)] +// pub mod tests { +// use super::*; +// use crate::{ +// movement, +// test::{editor_test_context::EditorTestContext, marked_display_snapshot}, +// }; +// use gpui::{AppContext, Hsla}; +// use language::{ +// language_settings::{AllLanguageSettings, AllLanguageSettingsContent}, +// Buffer, Language, LanguageConfig, SelectionGoal, +// }; +// use project::Project; +// use rand::{prelude::*, Rng}; +// use settings::SettingsStore; +// use smol::stream::StreamExt; +// use std::{env, sync::Arc}; +// use theme::SyntaxTheme; +// use util::test::{marked_text_ranges, sample_text}; +// use Bias::*; + +// #[gpui::test(iterations = 100)] +// async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) { +// cx.foreground().set_block_on_ticks(0..=50); +// cx.foreground().forbid_parking(); +// let operations = env::var("OPERATIONS") +// .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) +// .unwrap_or(10); + +// let font_cache = cx.font_cache().clone(); +// let mut tab_size = rng.gen_range(1..=4); +// let buffer_start_excerpt_header_height = rng.gen_range(1..=5); +// let excerpt_header_height = rng.gen_range(1..=5); +// let family_id = font_cache +// .load_family(&["Helvetica"], &Default::default()) +// .unwrap(); +// let font_id = font_cache +// .select_font(family_id, &Default::default()) +// .unwrap(); +// let font_size = 14.0; +// let max_wrap_width = 300.0; +// let mut wrap_width = if rng.gen_bool(0.1) { +// None +// } else { +// Some(rng.gen_range(0.0..=max_wrap_width)) +// }; + +// log::info!("tab size: {}", tab_size); +// log::info!("wrap width: {:?}", wrap_width); + +// cx.update(|cx| { +// init_test(cx, |s| s.defaults.tab_size = NonZeroU32::new(tab_size)); +// }); + +// let buffer = cx.update(|cx| { +// if rng.gen() { +// let len = rng.gen_range(0..10); +// let text = util::RandomCharIter::new(&mut rng) +// .take(len) +// .collect::(); +// MultiBuffer::build_simple(&text, cx) +// } else { +// MultiBuffer::build_random(&mut rng, cx) +// } +// }); + +// let map = cx.add_model(|cx| { +// DisplayMap::new( +// buffer.clone(), +// font_id, +// font_size, +// wrap_width, +// buffer_start_excerpt_header_height, +// excerpt_header_height, +// cx, +// ) +// }); +// let mut notifications = observe(&map, cx); +// let mut fold_count = 0; +// let mut blocks = Vec::new(); + +// let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); +// log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text()); +// log::info!("fold text: {:?}", snapshot.fold_snapshot.text()); +// log::info!("tab text: {:?}", snapshot.tab_snapshot.text()); +// log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text()); +// log::info!("block text: {:?}", snapshot.block_snapshot.text()); +// log::info!("display text: {:?}", snapshot.text()); + +// for _i in 0..operations { +// match rng.gen_range(0..100) { +// 0..=19 => { +// wrap_width = if rng.gen_bool(0.2) { +// None +// } else { +// Some(rng.gen_range(0.0..=max_wrap_width)) +// }; +// log::info!("setting wrap width to {:?}", wrap_width); +// map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); +// } +// 20..=29 => { +// let mut tab_sizes = vec![1, 2, 3, 4]; +// tab_sizes.remove((tab_size - 1) as usize); +// tab_size = *tab_sizes.choose(&mut rng).unwrap(); +// log::info!("setting tab size to {:?}", tab_size); +// cx.update(|cx| { +// cx.update_global::(|store, cx| { +// store.update_user_settings::(cx, |s| { +// s.defaults.tab_size = NonZeroU32::new(tab_size); +// }); +// }); +// }); +// } +// 30..=44 => { +// map.update(cx, |map, cx| { +// if rng.gen() || blocks.is_empty() { +// let buffer = map.snapshot(cx).buffer_snapshot; +// let block_properties = (0..rng.gen_range(1..=1)) +// .map(|_| { +// let position = +// buffer.anchor_after(buffer.clip_offset( +// rng.gen_range(0..=buffer.len()), +// Bias::Left, +// )); + +// let disposition = if rng.gen() { +// BlockDisposition::Above +// } else { +// BlockDisposition::Below +// }; +// let height = rng.gen_range(1..5); +// log::info!( +// "inserting block {:?} {:?} with height {}", +// disposition, +// position.to_point(&buffer), +// height +// ); +// BlockProperties { +// style: BlockStyle::Fixed, +// position, +// height, +// disposition, +// render: Arc::new(|_| Empty::new().into_any()), +// } +// }) +// .collect::>(); +// blocks.extend(map.insert_blocks(block_properties, cx)); +// } else { +// blocks.shuffle(&mut rng); +// let remove_count = rng.gen_range(1..=4.min(blocks.len())); +// let block_ids_to_remove = (0..remove_count) +// .map(|_| blocks.remove(rng.gen_range(0..blocks.len()))) +// .collect(); +// log::info!("removing block ids {:?}", block_ids_to_remove); +// map.remove_blocks(block_ids_to_remove, cx); +// } +// }); +// } +// 45..=79 => { +// let mut ranges = Vec::new(); +// for _ in 0..rng.gen_range(1..=3) { +// buffer.read_with(cx, |buffer, cx| { +// let buffer = buffer.read(cx); +// let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right); +// let start = buffer.clip_offset(rng.gen_range(0..=end), Left); +// ranges.push(start..end); +// }); +// } + +// if rng.gen() && fold_count > 0 { +// log::info!("unfolding ranges: {:?}", ranges); +// map.update(cx, |map, cx| { +// map.unfold(ranges, true, cx); +// }); +// } else { +// log::info!("folding ranges: {:?}", ranges); +// map.update(cx, |map, cx| { +// map.fold(ranges, cx); +// }); +// } +// } +// _ => { +// buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx)); +// } +// } + +// if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) { +// notifications.next().await.unwrap(); +// } + +// let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); +// fold_count = snapshot.fold_count(); +// log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text()); +// log::info!("fold text: {:?}", snapshot.fold_snapshot.text()); +// log::info!("tab text: {:?}", snapshot.tab_snapshot.text()); +// log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text()); +// log::info!("block text: {:?}", snapshot.block_snapshot.text()); +// log::info!("display text: {:?}", snapshot.text()); + +// // Line boundaries +// let buffer = &snapshot.buffer_snapshot; +// for _ in 0..5 { +// let row = rng.gen_range(0..=buffer.max_point().row); +// let column = rng.gen_range(0..=buffer.line_len(row)); +// let point = buffer.clip_point(Point::new(row, column), Left); + +// let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point); +// let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point); + +// assert!(prev_buffer_bound <= point); +// assert!(next_buffer_bound >= point); +// assert_eq!(prev_buffer_bound.column, 0); +// assert_eq!(prev_display_bound.column(), 0); +// if next_buffer_bound < buffer.max_point() { +// assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n')); +// } + +// assert_eq!( +// prev_display_bound, +// prev_buffer_bound.to_display_point(&snapshot), +// "row boundary before {:?}. reported buffer row boundary: {:?}", +// point, +// prev_buffer_bound +// ); +// assert_eq!( +// next_display_bound, +// next_buffer_bound.to_display_point(&snapshot), +// "display row boundary after {:?}. reported buffer row boundary: {:?}", +// point, +// next_buffer_bound +// ); +// assert_eq!( +// prev_buffer_bound, +// prev_display_bound.to_point(&snapshot), +// "row boundary before {:?}. reported display row boundary: {:?}", +// point, +// prev_display_bound +// ); +// assert_eq!( +// next_buffer_bound, +// next_display_bound.to_point(&snapshot), +// "row boundary after {:?}. reported display row boundary: {:?}", +// point, +// next_display_bound +// ); +// } + +// // Movement +// let min_point = snapshot.clip_point(DisplayPoint::new(0, 0), Left); +// let max_point = snapshot.clip_point(snapshot.max_point(), Right); +// for _ in 0..5 { +// let row = rng.gen_range(0..=snapshot.max_point().row()); +// let column = rng.gen_range(0..=snapshot.line_len(row)); +// let point = snapshot.clip_point(DisplayPoint::new(row, column), Left); + +// log::info!("Moving from point {:?}", point); + +// let moved_right = movement::right(&snapshot, point); +// log::info!("Right {:?}", moved_right); +// if point < max_point { +// assert!(moved_right > point); +// if point.column() == snapshot.line_len(point.row()) +// || snapshot.soft_wrap_indent(point.row()).is_some() +// && point.column() == snapshot.line_len(point.row()) - 1 +// { +// assert!(moved_right.row() > point.row()); +// } +// } else { +// assert_eq!(moved_right, point); +// } + +// let moved_left = movement::left(&snapshot, point); +// log::info!("Left {:?}", moved_left); +// if point > min_point { +// assert!(moved_left < point); +// if point.column() == 0 { +// assert!(moved_left.row() < point.row()); +// } +// } else { +// assert_eq!(moved_left, point); +// } +// } +// } +// } + +// #[gpui::test(retries = 5)] +// async fn test_soft_wraps(cx: &mut gpui::TestAppContext) { +// cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); +// cx.update(|cx| { +// init_test(cx, |_| {}); +// }); + +// let mut cx = EditorTestContext::new(cx).await; +// let editor = cx.editor.clone(); +// let window = cx.window.clone(); + +// cx.update_window(window, |cx| { +// let text_layout_details = +// editor.read_with(cx, |editor, cx| editor.text_layout_details(cx)); + +// let font_cache = cx.font_cache().clone(); + +// let family_id = font_cache +// .load_family(&["Helvetica"], &Default::default()) +// .unwrap(); +// let font_id = font_cache +// .select_font(family_id, &Default::default()) +// .unwrap(); +// let font_size = 12.0; +// let wrap_width = Some(64.); + +// let text = "one two three four five\nsix seven eight"; +// let buffer = MultiBuffer::build_simple(text, cx); +// let map = cx.add_model(|cx| { +// DisplayMap::new(buffer.clone(), font_id, font_size, wrap_width, 1, 1, cx) +// }); + +// let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); +// assert_eq!( +// snapshot.text_chunks(0).collect::(), +// "one two \nthree four \nfive\nsix seven \neight" +// ); +// assert_eq!( +// snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Left), +// DisplayPoint::new(0, 7) +// ); +// assert_eq!( +// snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Right), +// DisplayPoint::new(1, 0) +// ); +// assert_eq!( +// movement::right(&snapshot, DisplayPoint::new(0, 7)), +// DisplayPoint::new(1, 0) +// ); +// assert_eq!( +// movement::left(&snapshot, DisplayPoint::new(1, 0)), +// DisplayPoint::new(0, 7) +// ); + +// let x = snapshot.x_for_point(DisplayPoint::new(1, 10), &text_layout_details); +// assert_eq!( +// movement::up( +// &snapshot, +// DisplayPoint::new(1, 10), +// SelectionGoal::None, +// false, +// &text_layout_details, +// ), +// ( +// DisplayPoint::new(0, 7), +// SelectionGoal::HorizontalPosition(x) +// ) +// ); +// assert_eq!( +// movement::down( +// &snapshot, +// DisplayPoint::new(0, 7), +// SelectionGoal::HorizontalPosition(x), +// false, +// &text_layout_details +// ), +// ( +// DisplayPoint::new(1, 10), +// SelectionGoal::HorizontalPosition(x) +// ) +// ); +// assert_eq!( +// movement::down( +// &snapshot, +// DisplayPoint::new(1, 10), +// SelectionGoal::HorizontalPosition(x), +// false, +// &text_layout_details +// ), +// ( +// DisplayPoint::new(2, 4), +// SelectionGoal::HorizontalPosition(x) +// ) +// ); + +// let ix = snapshot.buffer_snapshot.text().find("seven").unwrap(); +// buffer.update(cx, |buffer, cx| { +// buffer.edit([(ix..ix, "and ")], None, cx); +// }); + +// let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); +// assert_eq!( +// snapshot.text_chunks(1).collect::(), +// "three four \nfive\nsix and \nseven eight" +// ); + +// // Re-wrap on font size changes +// map.update(cx, |map, cx| map.set_font_with_size(font_id, font_size + 3., cx)); + +// let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); +// assert_eq!( +// snapshot.text_chunks(1).collect::(), +// "three \nfour five\nsix and \nseven \neight" +// ) +// }); +// } + +// #[gpui::test] +// fn test_text_chunks(cx: &mut gpui::AppContext) { +// init_test(cx, |_| {}); + +// let text = sample_text(6, 6, 'a'); +// let buffer = MultiBuffer::build_simple(&text, cx); +// let family_id = cx +// .font_cache() +// .load_family(&["Helvetica"], &Default::default()) +// .unwrap(); +// let font_id = cx +// .font_cache() +// .select_font(family_id, &Default::default()) +// .unwrap(); +// let font_size = 14.0; +// let map = +// cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx)); + +// buffer.update(cx, |buffer, cx| { +// buffer.edit( +// vec![ +// (Point::new(1, 0)..Point::new(1, 0), "\t"), +// (Point::new(1, 1)..Point::new(1, 1), "\t"), +// (Point::new(2, 1)..Point::new(2, 1), "\t"), +// ], +// None, +// cx, +// ) +// }); + +// assert_eq!( +// map.update(cx, |map, cx| map.snapshot(cx)) +// .text_chunks(1) +// .collect::() +// .lines() +// .next(), +// Some(" b bbbbb") +// ); +// assert_eq!( +// map.update(cx, |map, cx| map.snapshot(cx)) +// .text_chunks(2) +// .collect::() +// .lines() +// .next(), +// Some("c ccccc") +// ); +// } + +// #[gpui::test] +// async fn test_chunks(cx: &mut gpui::TestAppContext) { +// use unindent::Unindent as _; + +// let text = r#" +// fn outer() {} + +// mod module { +// fn inner() {} +// }"# +// .unindent(); + +// let theme = SyntaxTheme::new(vec![ +// ("mod.body".to_string(), Hsla::red().into()), +// ("fn.name".to_string(), Hsla::blue().into()), +// ]); +// let language = Arc::new( +// Language::new( +// LanguageConfig { +// name: "Test".into(), +// path_suffixes: vec![".test".to_string()], +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ) +// .with_highlights_query( +// r#" +// (mod_item name: (identifier) body: _ @mod.body) +// (function_item name: (identifier) @fn.name) +// "#, +// ) +// .unwrap(), +// ); +// language.set_theme(&theme); + +// cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap()))); + +// let buffer = cx +// .add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx)); +// buffer.condition(cx, |buf, _| !buf.is_parsing()).await; +// let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); + +// let font_cache = cx.font_cache(); +// let family_id = font_cache +// .load_family(&["Helvetica"], &Default::default()) +// .unwrap(); +// let font_id = font_cache +// .select_font(family_id, &Default::default()) +// .unwrap(); +// let font_size = 14.0; + +// let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx)); +// assert_eq!( +// cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)), +// vec![ +// ("fn ".to_string(), None), +// ("outer".to_string(), Some(Hsla::blue())), +// ("() {}\n\nmod module ".to_string(), None), +// ("{\n fn ".to_string(), Some(Hsla::red())), +// ("inner".to_string(), Some(Hsla::blue())), +// ("() {}\n}".to_string(), Some(Hsla::red())), +// ] +// ); +// assert_eq!( +// cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)), +// vec![ +// (" fn ".to_string(), Some(Hsla::red())), +// ("inner".to_string(), Some(Hsla::blue())), +// ("() {}\n}".to_string(), Some(Hsla::red())), +// ] +// ); + +// map.update(cx, |map, cx| { +// map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx) +// }); +// assert_eq!( +// cx.update(|cx| syntax_chunks(0..2, &map, &theme, cx)), +// vec![ +// ("fn ".to_string(), None), +// ("out".to_string(), Some(Hsla::blue())), +// ("⋯".to_string(), None), +// (" fn ".to_string(), Some(Hsla::red())), +// ("inner".to_string(), Some(Hsla::blue())), +// ("() {}\n}".to_string(), Some(Hsla::red())), +// ] +// ); +// } + +// #[gpui::test] +// async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) { +// use unindent::Unindent as _; + +// cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); + +// let text = r#" +// fn outer() {} + +// mod module { +// fn inner() {} +// }"# +// .unindent(); + +// let theme = SyntaxTheme::new(vec![ +// ("mod.body".to_string(), Hsla::red().into()), +// ("fn.name".to_string(), Hsla::blue().into()), +// ]); +// let language = Arc::new( +// Language::new( +// LanguageConfig { +// name: "Test".into(), +// path_suffixes: vec![".test".to_string()], +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ) +// .with_highlights_query( +// r#" +// (mod_item name: (identifier) body: _ @mod.body) +// (function_item name: (identifier) @fn.name) +// "#, +// ) +// .unwrap(), +// ); +// language.set_theme(&theme); + +// cx.update(|cx| init_test(cx, |_| {})); + +// let buffer = cx +// .add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx)); +// buffer.condition(cx, |buf, _| !buf.is_parsing()).await; +// let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); + +// let font_cache = cx.font_cache(); + +// let family_id = font_cache +// .load_family(&["Courier"], &Default::default()) +// .unwrap(); +// let font_id = font_cache +// .select_font(family_id, &Default::default()) +// .unwrap(); +// let font_size = 16.0; + +// let map = +// cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, Some(40.0), 1, 1, cx)); +// assert_eq!( +// cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)), +// [ +// ("fn \n".to_string(), None), +// ("oute\nr".to_string(), Some(Hsla::blue())), +// ("() \n{}\n\n".to_string(), None), +// ] +// ); +// assert_eq!( +// cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)), +// [("{}\n\n".to_string(), None)] +// ); + +// map.update(cx, |map, cx| { +// map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx) +// }); +// assert_eq!( +// cx.update(|cx| syntax_chunks(1..4, &map, &theme, cx)), +// [ +// ("out".to_string(), Some(Hsla::blue())), +// ("⋯\n".to_string(), None), +// (" \nfn ".to_string(), Some(Hsla::red())), +// ("i\n".to_string(), Some(Hsla::blue())) +// ] +// ); +// } + +// #[gpui::test] +// async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) { +// cx.update(|cx| init_test(cx, |_| {})); + +// let theme = SyntaxTheme::new(vec![ +// ("operator".to_string(), Hsla::red().into()), +// ("string".to_string(), Hsla::green().into()), +// ]); +// let language = Arc::new( +// Language::new( +// LanguageConfig { +// name: "Test".into(), +// path_suffixes: vec![".test".to_string()], +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ) +// .with_highlights_query( +// r#" +// ":" @operator +// (string_literal) @string +// "#, +// ) +// .unwrap(), +// ); +// language.set_theme(&theme); + +// let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»: B = "c «d»""#, false); + +// let buffer = cx +// .add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx)); +// buffer.condition(cx, |buf, _| !buf.is_parsing()).await; + +// let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); +// let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); + +// let font_cache = cx.font_cache(); +// let family_id = font_cache +// .load_family(&["Courier"], &Default::default()) +// .unwrap(); +// let font_id = font_cache +// .select_font(family_id, &Default::default()) +// .unwrap(); +// let font_size = 16.0; +// let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx)); + +// enum MyType {} + +// let style = HighlightStyle { +// color: Some(Hsla::blue()), +// ..Default::default() +// }; + +// map.update(cx, |map, _cx| { +// map.highlight_text( +// TypeId::of::(), +// highlighted_ranges +// .into_iter() +// .map(|range| { +// buffer_snapshot.anchor_before(range.start) +// ..buffer_snapshot.anchor_before(range.end) +// }) +// .collect(), +// style, +// ); +// }); + +// assert_eq!( +// cx.update(|cx| chunks(0..10, &map, &theme, cx)), +// [ +// ("const ".to_string(), None, None), +// ("a".to_string(), None, Some(Hsla::blue())), +// (":".to_string(), Some(Hsla::red()), None), +// (" B = ".to_string(), None, None), +// ("\"c ".to_string(), Some(Hsla::green()), None), +// ("d".to_string(), Some(Hsla::green()), Some(Hsla::blue())), +// ("\"".to_string(), Some(Hsla::green()), None), +// ] +// ); +// } + +// #[gpui::test] +// fn test_clip_point(cx: &mut gpui::AppContext) { +// init_test(cx, |_| {}); + +// fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::AppContext) { +// let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx); + +// match bias { +// Bias::Left => { +// if shift_right { +// *markers[1].column_mut() += 1; +// } + +// assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0]) +// } +// Bias::Right => { +// if shift_right { +// *markers[0].column_mut() += 1; +// } + +// assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1]) +// } +// }; +// } + +// use Bias::{Left, Right}; +// assert("ˇˇα", false, Left, cx); +// assert("ˇˇα", true, Left, cx); +// assert("ˇˇα", false, Right, cx); +// assert("ˇαˇ", true, Right, cx); +// assert("ˇˇ✋", false, Left, cx); +// assert("ˇˇ✋", true, Left, cx); +// assert("ˇˇ✋", false, Right, cx); +// assert("ˇ✋ˇ", true, Right, cx); +// assert("ˇˇ🍐", false, Left, cx); +// assert("ˇˇ🍐", true, Left, cx); +// assert("ˇˇ🍐", false, Right, cx); +// assert("ˇ🍐ˇ", true, Right, cx); +// assert("ˇˇ\t", false, Left, cx); +// assert("ˇˇ\t", true, Left, cx); +// assert("ˇˇ\t", false, Right, cx); +// assert("ˇ\tˇ", true, Right, cx); +// assert(" ˇˇ\t", false, Left, cx); +// assert(" ˇˇ\t", true, Left, cx); +// assert(" ˇˇ\t", false, Right, cx); +// assert(" ˇ\tˇ", true, Right, cx); +// assert(" ˇˇ\t", false, Left, cx); +// assert(" ˇˇ\t", false, Right, cx); +// } + +// #[gpui::test] +// fn test_clip_at_line_ends(cx: &mut gpui::AppContext) { +// init_test(cx, |_| {}); + +// fn assert(text: &str, cx: &mut gpui::AppContext) { +// let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx); +// unmarked_snapshot.clip_at_line_ends = true; +// assert_eq!( +// unmarked_snapshot.clip_point(markers[1], Bias::Left), +// markers[0] +// ); +// } + +// assert("ˇˇ", cx); +// assert("ˇaˇ", cx); +// assert("aˇbˇ", cx); +// assert("aˇαˇ", cx); +// } + +// #[gpui::test] +// fn test_tabs_with_multibyte_chars(cx: &mut gpui::AppContext) { +// init_test(cx, |_| {}); + +// let text = "✅\t\tα\nβ\t\n🏀β\t\tγ"; +// let buffer = MultiBuffer::build_simple(text, cx); +// let font_cache = cx.font_cache(); +// let family_id = font_cache +// .load_family(&["Helvetica"], &Default::default()) +// .unwrap(); +// let font_id = font_cache +// .select_font(family_id, &Default::default()) +// .unwrap(); +// let font_size = 14.0; + +// let map = +// cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx)); +// let map = map.update(cx, |map, cx| map.snapshot(cx)); +// assert_eq!(map.text(), "✅ α\nβ \n🏀β γ"); +// assert_eq!( +// map.text_chunks(0).collect::(), +// "✅ α\nβ \n🏀β γ" +// ); +// assert_eq!(map.text_chunks(1).collect::(), "β \n🏀β γ"); +// assert_eq!(map.text_chunks(2).collect::(), "🏀β γ"); + +// let point = Point::new(0, "✅\t\t".len() as u32); +// let display_point = DisplayPoint::new(0, "✅ ".len() as u32); +// assert_eq!(point.to_display_point(&map), display_point); +// assert_eq!(display_point.to_point(&map), point); + +// let point = Point::new(1, "β\t".len() as u32); +// let display_point = DisplayPoint::new(1, "β ".len() as u32); +// assert_eq!(point.to_display_point(&map), display_point); +// assert_eq!(display_point.to_point(&map), point,); + +// let point = Point::new(2, "🏀β\t\t".len() as u32); +// let display_point = DisplayPoint::new(2, "🏀β ".len() as u32); +// assert_eq!(point.to_display_point(&map), display_point); +// assert_eq!(display_point.to_point(&map), point,); + +// // Display points inside of expanded tabs +// assert_eq!( +// DisplayPoint::new(0, "✅ ".len() as u32).to_point(&map), +// Point::new(0, "✅\t".len() as u32), +// ); +// assert_eq!( +// DisplayPoint::new(0, "✅ ".len() as u32).to_point(&map), +// Point::new(0, "✅".len() as u32), +// ); + +// // Clipping display points inside of multi-byte characters +// assert_eq!( +// map.clip_point(DisplayPoint::new(0, "✅".len() as u32 - 1), Left), +// DisplayPoint::new(0, 0) +// ); +// assert_eq!( +// map.clip_point(DisplayPoint::new(0, "✅".len() as u32 - 1), Bias::Right), +// DisplayPoint::new(0, "✅".len() as u32) +// ); +// } + +// #[gpui::test] +// fn test_max_point(cx: &mut gpui::AppContext) { +// init_test(cx, |_| {}); + +// let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx); +// let font_cache = cx.font_cache(); +// let family_id = font_cache +// .load_family(&["Helvetica"], &Default::default()) +// .unwrap(); +// let font_id = font_cache +// .select_font(family_id, &Default::default()) +// .unwrap(); +// let font_size = 14.0; +// let map = +// cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx)); +// assert_eq!( +// map.update(cx, |map, cx| map.snapshot(cx)).max_point(), +// DisplayPoint::new(1, 11) +// ) +// } + +// fn syntax_chunks<'a>( +// rows: Range, +// map: &Model, +// theme: &'a SyntaxTheme, +// cx: &mut AppContext, +// ) -> Vec<(String, Option)> { +// chunks(rows, map, theme, cx) +// .into_iter() +// .map(|(text, color, _)| (text, color)) +// .collect() +// } + +// fn chunks<'a>( +// rows: Range, +// map: &Model, +// theme: &'a SyntaxTheme, +// cx: &mut AppContext, +// ) -> Vec<(String, Option, Option)> { +// let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); +// let mut chunks: Vec<(String, Option, Option)> = Vec::new(); +// for chunk in snapshot.chunks(rows, true, None, None) { +// let syntax_color = chunk +// .syntax_highlight_id +// .and_then(|id| id.style(theme)?.color); +// let highlight_color = chunk.highlight_style.and_then(|style| style.color); +// if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() { +// if syntax_color == *last_syntax_color && highlight_color == *last_highlight_color { +// last_chunk.push_str(chunk.text); +// continue; +// } +// } +// chunks.push((chunk.text.to_string(), syntax_color, highlight_color)); +// } +// chunks +// } + +// fn init_test(cx: &mut AppContext, f: impl Fn(&mut AllLanguageSettingsContent)) { +// cx.foreground().forbid_parking(); +// cx.set_global(SettingsStore::test(cx)); +// language::init(cx); +// crate::init(cx); +// Project::init_settings(cx); +// theme::init((), cx); +// cx.update_global::(|store, cx| { +// store.update_user_settings::(cx, f); +// }); +// } +// } diff --git a/crates/editor2/src/display_map/block_map.rs b/crates/editor2/src/display_map/block_map.rs new file mode 100644 index 0000000000..aa5ff0e3d2 --- /dev/null +++ b/crates/editor2/src/display_map/block_map.rs @@ -0,0 +1,1667 @@ +use super::{ + wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot}, + Highlights, +}; +use crate::{Anchor, Editor, ExcerptId, ExcerptRange, ToPoint as _}; +use collections::{Bound, HashMap, HashSet}; +use gpui::{AnyElement, ViewContext}; +use language::{BufferSnapshot, Chunk, Patch, Point}; +use parking_lot::Mutex; +use std::{ + cell::RefCell, + cmp::{self, Ordering}, + fmt::Debug, + ops::{Deref, DerefMut, Range}, + sync::{ + atomic::{AtomicUsize, Ordering::SeqCst}, + Arc, + }, +}; +use sum_tree::{Bias, SumTree}; +use text::Edit; + +const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize]; + +pub struct BlockMap { + next_block_id: AtomicUsize, + wrap_snapshot: RefCell, + blocks: Vec>, + transforms: RefCell>, + buffer_header_height: u8, + excerpt_header_height: u8, +} + +pub struct BlockMapWriter<'a>(&'a mut BlockMap); + +pub struct BlockSnapshot { + wrap_snapshot: WrapSnapshot, + transforms: SumTree, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct BlockId(usize); + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct BlockPoint(pub Point); + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +struct BlockRow(u32); + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +struct WrapRow(u32); + +pub type RenderBlock = Arc AnyElement>; + +pub struct Block { + id: BlockId, + position: Anchor, + height: u8, + style: BlockStyle, + render: Mutex, + disposition: BlockDisposition, +} + +#[derive(Clone)] +pub struct BlockProperties

+where + P: Clone, +{ + pub position: P, + pub height: u8, + pub style: BlockStyle, + pub render: Arc AnyElement>, + pub disposition: BlockDisposition, +} + +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum BlockStyle { + Fixed, + Flex, + Sticky, +} + +pub struct BlockContext<'a, 'b> { + pub view_context: &'b mut ViewContext<'a, Editor>, + pub anchor_x: f32, + pub scroll_x: f32, + pub gutter_width: f32, + pub gutter_padding: f32, + pub em_width: f32, + pub line_height: f32, + pub block_id: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum BlockDisposition { + Above, + Below, +} + +#[derive(Clone, Debug)] +struct Transform { + summary: TransformSummary, + block: Option, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Clone)] +pub enum TransformBlock { + Custom(Arc), + ExcerptHeader { + id: ExcerptId, + buffer: BufferSnapshot, + range: ExcerptRange, + height: u8, + starts_new_buffer: bool, + }, +} + +impl TransformBlock { + fn disposition(&self) -> BlockDisposition { + match self { + TransformBlock::Custom(block) => block.disposition, + TransformBlock::ExcerptHeader { .. } => BlockDisposition::Above, + } + } + + pub fn height(&self) -> u8 { + match self { + TransformBlock::Custom(block) => block.height, + TransformBlock::ExcerptHeader { height, .. } => *height, + } + } +} + +impl Debug for TransformBlock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(), + Self::ExcerptHeader { buffer, .. } => f + .debug_struct("ExcerptHeader") + .field("path", &buffer.file().map(|f| f.path())) + .finish(), + } + } +} + +#[derive(Clone, Debug, Default)] +struct TransformSummary { + input_rows: u32, + output_rows: u32, +} + +pub struct BlockChunks<'a> { + transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>, + input_chunks: wrap_map::WrapChunks<'a>, + input_chunk: Chunk<'a>, + output_row: u32, + max_output_row: u32, +} + +#[derive(Clone)] +pub struct BlockBufferRows<'a> { + transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>, + input_buffer_rows: wrap_map::WrapBufferRows<'a>, + output_row: u32, + started: bool, +} + +impl BlockMap { + pub fn new( + wrap_snapshot: WrapSnapshot, + buffer_header_height: u8, + excerpt_header_height: u8, + ) -> Self { + let row_count = wrap_snapshot.max_point().row() + 1; + let map = Self { + next_block_id: AtomicUsize::new(0), + blocks: Vec::new(), + transforms: RefCell::new(SumTree::from_item(Transform::isomorphic(row_count), &())), + wrap_snapshot: RefCell::new(wrap_snapshot.clone()), + buffer_header_height, + excerpt_header_height, + }; + map.sync( + &wrap_snapshot, + Patch::new(vec![Edit { + old: 0..row_count, + new: 0..row_count, + }]), + ); + map + } + + pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Patch) -> BlockSnapshot { + self.sync(&wrap_snapshot, edits); + *self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone(); + BlockSnapshot { + wrap_snapshot, + transforms: self.transforms.borrow().clone(), + } + } + + pub fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Patch) -> BlockMapWriter { + self.sync(&wrap_snapshot, edits); + *self.wrap_snapshot.borrow_mut() = wrap_snapshot; + BlockMapWriter(self) + } + + fn sync(&self, wrap_snapshot: &WrapSnapshot, mut edits: Patch) { + let buffer = wrap_snapshot.buffer_snapshot(); + + // Handle changing the last excerpt if it is empty. + if buffer.trailing_excerpt_update_count() + != self + .wrap_snapshot + .borrow() + .buffer_snapshot() + .trailing_excerpt_update_count() + { + let max_point = wrap_snapshot.max_point(); + let edit_start = wrap_snapshot.prev_row_boundary(max_point); + let edit_end = max_point.row() + 1; + edits = edits.compose([WrapEdit { + old: edit_start..edit_end, + new: edit_start..edit_end, + }]); + } + + let edits = edits.into_inner(); + if edits.is_empty() { + return; + } + + let mut transforms = self.transforms.borrow_mut(); + let mut new_transforms = SumTree::new(); + let old_row_count = transforms.summary().input_rows; + let new_row_count = wrap_snapshot.max_point().row() + 1; + let mut cursor = transforms.cursor::(); + let mut last_block_ix = 0; + let mut blocks_in_edit = Vec::new(); + let mut edits = edits.into_iter().peekable(); + + while let Some(edit) = edits.next() { + // Preserve any old transforms that precede this edit. + let old_start = WrapRow(edit.old.start); + let new_start = WrapRow(edit.new.start); + new_transforms.append(cursor.slice(&old_start, Bias::Left, &()), &()); + if let Some(transform) = cursor.item() { + if transform.is_isomorphic() && old_start == cursor.end(&()) { + new_transforms.push(transform.clone(), &()); + cursor.next(&()); + while let Some(transform) = cursor.item() { + if transform + .block + .as_ref() + .map_or(false, |b| b.disposition().is_below()) + { + new_transforms.push(transform.clone(), &()); + cursor.next(&()); + } else { + break; + } + } + } + } + + // Preserve any portion of an old transform that precedes this edit. + let extent_before_edit = old_start.0 - cursor.start().0; + push_isomorphic(&mut new_transforms, extent_before_edit); + + // Skip over any old transforms that intersect this edit. + let mut old_end = WrapRow(edit.old.end); + let mut new_end = WrapRow(edit.new.end); + cursor.seek(&old_end, Bias::Left, &()); + cursor.next(&()); + if old_end == *cursor.start() { + while let Some(transform) = cursor.item() { + if transform + .block + .as_ref() + .map_or(false, |b| b.disposition().is_below()) + { + cursor.next(&()); + } else { + break; + } + } + } + + // Combine this edit with any subsequent edits that intersect the same transform. + while let Some(next_edit) = edits.peek() { + if next_edit.old.start <= cursor.start().0 { + old_end = WrapRow(next_edit.old.end); + new_end = WrapRow(next_edit.new.end); + cursor.seek(&old_end, Bias::Left, &()); + cursor.next(&()); + if old_end == *cursor.start() { + while let Some(transform) = cursor.item() { + if transform + .block + .as_ref() + .map_or(false, |b| b.disposition().is_below()) + { + cursor.next(&()); + } else { + break; + } + } + } + edits.next(); + } else { + break; + } + } + + // Find the blocks within this edited region. + let new_buffer_start = + wrap_snapshot.to_point(WrapPoint::new(new_start.0, 0), Bias::Left); + let start_bound = Bound::Included(new_buffer_start); + let start_block_ix = match self.blocks[last_block_ix..].binary_search_by(|probe| { + probe + .position + .to_point(buffer) + .cmp(&new_buffer_start) + .then(Ordering::Greater) + }) { + Ok(ix) | Err(ix) => last_block_ix + ix, + }; + + let end_bound; + let end_block_ix = if new_end.0 > wrap_snapshot.max_point().row() { + end_bound = Bound::Unbounded; + self.blocks.len() + } else { + let new_buffer_end = + wrap_snapshot.to_point(WrapPoint::new(new_end.0, 0), Bias::Left); + end_bound = Bound::Excluded(new_buffer_end); + match self.blocks[start_block_ix..].binary_search_by(|probe| { + probe + .position + .to_point(buffer) + .cmp(&new_buffer_end) + .then(Ordering::Greater) + }) { + Ok(ix) | Err(ix) => start_block_ix + ix, + } + }; + last_block_ix = end_block_ix; + + debug_assert!(blocks_in_edit.is_empty()); + blocks_in_edit.extend( + self.blocks[start_block_ix..end_block_ix] + .iter() + .map(|block| { + let mut position = block.position.to_point(buffer); + match block.disposition { + BlockDisposition::Above => position.column = 0, + BlockDisposition::Below => { + position.column = buffer.line_len(position.row) + } + } + let position = wrap_snapshot.make_wrap_point(position, Bias::Left); + (position.row(), TransformBlock::Custom(block.clone())) + }), + ); + blocks_in_edit.extend( + buffer + .excerpt_boundaries_in_range((start_bound, end_bound)) + .map(|excerpt_boundary| { + ( + wrap_snapshot + .make_wrap_point(Point::new(excerpt_boundary.row, 0), Bias::Left) + .row(), + TransformBlock::ExcerptHeader { + id: excerpt_boundary.id, + buffer: excerpt_boundary.buffer, + range: excerpt_boundary.range, + height: if excerpt_boundary.starts_new_buffer { + self.buffer_header_height + } else { + self.excerpt_header_height + }, + starts_new_buffer: excerpt_boundary.starts_new_buffer, + }, + ) + }), + ); + + // Place excerpt headers above custom blocks on the same row. + blocks_in_edit.sort_unstable_by(|(row_a, block_a), (row_b, block_b)| { + row_a.cmp(row_b).then_with(|| match (block_a, block_b) { + ( + TransformBlock::ExcerptHeader { .. }, + TransformBlock::ExcerptHeader { .. }, + ) => Ordering::Equal, + (TransformBlock::ExcerptHeader { .. }, _) => Ordering::Less, + (_, TransformBlock::ExcerptHeader { .. }) => Ordering::Greater, + (TransformBlock::Custom(block_a), TransformBlock::Custom(block_b)) => block_a + .disposition + .cmp(&block_b.disposition) + .then_with(|| block_a.id.cmp(&block_b.id)), + }) + }); + + // For each of these blocks, insert a new isomorphic transform preceding the block, + // and then insert the block itself. + for (block_row, block) in blocks_in_edit.drain(..) { + let insertion_row = match block.disposition() { + BlockDisposition::Above => block_row, + BlockDisposition::Below => block_row + 1, + }; + let extent_before_block = insertion_row - new_transforms.summary().input_rows; + push_isomorphic(&mut new_transforms, extent_before_block); + new_transforms.push(Transform::block(block), &()); + } + + old_end = WrapRow(old_end.0.min(old_row_count)); + new_end = WrapRow(new_end.0.min(new_row_count)); + + // Insert an isomorphic transform after the final block. + let extent_after_last_block = new_end.0 - new_transforms.summary().input_rows; + push_isomorphic(&mut new_transforms, extent_after_last_block); + + // Preserve any portion of the old transform after this edit. + let extent_after_edit = cursor.start().0 - old_end.0; + push_isomorphic(&mut new_transforms, extent_after_edit); + } + + new_transforms.append(cursor.suffix(&()), &()); + debug_assert_eq!( + new_transforms.summary().input_rows, + wrap_snapshot.max_point().row() + 1 + ); + + drop(cursor); + *transforms = new_transforms; + } + + pub fn replace(&mut self, mut renderers: HashMap) { + for block in &self.blocks { + if let Some(render) = renderers.remove(&block.id) { + *block.render.lock() = render; + } + } + } +} + +fn push_isomorphic(tree: &mut SumTree, rows: u32) { + if rows == 0 { + return; + } + + let mut extent = Some(rows); + tree.update_last( + |last_transform| { + if last_transform.is_isomorphic() { + let extent = extent.take().unwrap(); + last_transform.summary.input_rows += extent; + last_transform.summary.output_rows += extent; + } + }, + &(), + ); + if let Some(extent) = extent { + tree.push(Transform::isomorphic(extent), &()); + } +} + +impl BlockPoint { + pub fn new(row: u32, column: u32) -> Self { + Self(Point::new(row, column)) + } +} + +impl Deref for BlockPoint { + type Target = Point; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for BlockPoint { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a> BlockMapWriter<'a> { + pub fn insert( + &mut self, + blocks: impl IntoIterator>, + ) -> Vec { + let mut ids = Vec::new(); + let mut edits = Patch::default(); + let wrap_snapshot = &*self.0.wrap_snapshot.borrow(); + let buffer = wrap_snapshot.buffer_snapshot(); + + for block in blocks { + let id = BlockId(self.0.next_block_id.fetch_add(1, SeqCst)); + ids.push(id); + + let position = block.position; + let point = position.to_point(buffer); + let wrap_row = wrap_snapshot + .make_wrap_point(Point::new(point.row, 0), Bias::Left) + .row(); + let start_row = wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0)); + let end_row = wrap_snapshot + .next_row_boundary(WrapPoint::new(wrap_row, 0)) + .unwrap_or(wrap_snapshot.max_point().row() + 1); + + let block_ix = match self + .0 + .blocks + .binary_search_by(|probe| probe.position.cmp(&position, buffer)) + { + Ok(ix) | Err(ix) => ix, + }; + self.0.blocks.insert( + block_ix, + Arc::new(Block { + id, + position, + height: block.height, + render: Mutex::new(block.render), + disposition: block.disposition, + style: block.style, + }), + ); + + edits = edits.compose([Edit { + old: start_row..end_row, + new: start_row..end_row, + }]); + } + + self.0.sync(wrap_snapshot, edits); + ids + } + + pub fn remove(&mut self, block_ids: HashSet) { + let wrap_snapshot = &*self.0.wrap_snapshot.borrow(); + let buffer = wrap_snapshot.buffer_snapshot(); + let mut edits = Patch::default(); + let mut last_block_buffer_row = None; + self.0.blocks.retain(|block| { + if block_ids.contains(&block.id) { + let buffer_row = block.position.to_point(buffer).row; + if last_block_buffer_row != Some(buffer_row) { + last_block_buffer_row = Some(buffer_row); + let wrap_row = wrap_snapshot + .make_wrap_point(Point::new(buffer_row, 0), Bias::Left) + .row(); + let start_row = wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0)); + let end_row = wrap_snapshot + .next_row_boundary(WrapPoint::new(wrap_row, 0)) + .unwrap_or(wrap_snapshot.max_point().row() + 1); + edits.push(Edit { + old: start_row..end_row, + new: start_row..end_row, + }) + } + false + } else { + true + } + }); + self.0.sync(wrap_snapshot, edits); + } +} + +impl BlockSnapshot { + #[cfg(test)] + pub fn text(&self) -> String { + self.chunks( + 0..self.transforms.summary().output_rows, + false, + Highlights::default(), + ) + .map(|chunk| chunk.text) + .collect() + } + + pub fn chunks<'a>( + &'a self, + rows: Range, + language_aware: bool, + highlights: Highlights<'a>, + ) -> BlockChunks<'a> { + let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows); + let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); + let input_end = { + cursor.seek(&BlockRow(rows.end), Bias::Right, &()); + let overshoot = if cursor + .item() + .map_or(false, |transform| transform.is_isomorphic()) + { + rows.end - cursor.start().0 .0 + } else { + 0 + }; + cursor.start().1 .0 + overshoot + }; + let input_start = { + cursor.seek(&BlockRow(rows.start), Bias::Right, &()); + let overshoot = if cursor + .item() + .map_or(false, |transform| transform.is_isomorphic()) + { + rows.start - cursor.start().0 .0 + } else { + 0 + }; + cursor.start().1 .0 + overshoot + }; + BlockChunks { + input_chunks: self.wrap_snapshot.chunks( + input_start..input_end, + language_aware, + highlights, + ), + input_chunk: Default::default(), + transforms: cursor, + output_row: rows.start, + max_output_row, + } + } + + pub fn buffer_rows(&self, start_row: u32) -> BlockBufferRows { + let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); + cursor.seek(&BlockRow(start_row), Bias::Right, &()); + let (output_start, input_start) = cursor.start(); + let overshoot = if cursor.item().map_or(false, |t| t.is_isomorphic()) { + start_row - output_start.0 + } else { + 0 + }; + let input_start_row = input_start.0 + overshoot; + BlockBufferRows { + transforms: cursor, + input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row), + output_row: start_row, + started: false, + } + } + + pub fn blocks_in_range( + &self, + rows: Range, + ) -> impl Iterator { + let mut cursor = self.transforms.cursor::(); + cursor.seek(&BlockRow(rows.start), Bias::Right, &()); + std::iter::from_fn(move || { + while let Some(transform) = cursor.item() { + let start_row = cursor.start().0; + if start_row >= rows.end { + break; + } + if let Some(block) = &transform.block { + cursor.next(&()); + return Some((start_row, block)); + } else { + cursor.next(&()); + } + } + None + }) + } + + pub fn max_point(&self) -> BlockPoint { + let row = self.transforms.summary().output_rows - 1; + BlockPoint::new(row, self.line_len(row)) + } + + pub fn longest_row(&self) -> u32 { + let input_row = self.wrap_snapshot.longest_row(); + self.to_block_point(WrapPoint::new(input_row, 0)).row + } + + pub fn line_len(&self, row: u32) -> u32 { + let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); + cursor.seek(&BlockRow(row), Bias::Right, &()); + if let Some(transform) = cursor.item() { + let (output_start, input_start) = cursor.start(); + let overshoot = row - output_start.0; + if transform.block.is_some() { + 0 + } else { + self.wrap_snapshot.line_len(input_start.0 + overshoot) + } + } else { + panic!("row out of range"); + } + } + + pub fn is_block_line(&self, row: u32) -> bool { + let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); + cursor.seek(&BlockRow(row), Bias::Right, &()); + cursor.item().map_or(false, |t| t.block.is_some()) + } + + pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint { + let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); + cursor.seek(&BlockRow(point.row), Bias::Right, &()); + + let max_input_row = WrapRow(self.transforms.summary().input_rows); + let mut search_left = + (bias == Bias::Left && cursor.start().1 .0 > 0) || cursor.end(&()).1 == max_input_row; + let mut reversed = false; + + loop { + if let Some(transform) = cursor.item() { + if transform.is_isomorphic() { + let (output_start_row, input_start_row) = cursor.start(); + let (output_end_row, input_end_row) = cursor.end(&()); + let output_start = Point::new(output_start_row.0, 0); + let input_start = Point::new(input_start_row.0, 0); + let input_end = Point::new(input_end_row.0, 0); + let input_point = if point.row >= output_end_row.0 { + let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1); + self.wrap_snapshot + .clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias) + } else { + let output_overshoot = point.0.saturating_sub(output_start); + self.wrap_snapshot + .clip_point(WrapPoint(input_start + output_overshoot), bias) + }; + + if (input_start..input_end).contains(&input_point.0) { + let input_overshoot = input_point.0.saturating_sub(input_start); + return BlockPoint(output_start + input_overshoot); + } + } + + if search_left { + cursor.prev(&()); + } else { + cursor.next(&()); + } + } else if reversed { + return self.max_point(); + } else { + reversed = true; + search_left = !search_left; + cursor.seek(&BlockRow(point.row), Bias::Right, &()); + } + } + } + + pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint { + let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(); + cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &()); + if let Some(transform) = cursor.item() { + debug_assert!(transform.is_isomorphic()); + } else { + return self.max_point(); + } + + let (input_start_row, output_start_row) = cursor.start(); + let input_start = Point::new(input_start_row.0, 0); + let output_start = Point::new(output_start_row.0, 0); + let input_overshoot = wrap_point.0 - input_start; + BlockPoint(output_start + input_overshoot) + } + + pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint { + let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); + cursor.seek(&BlockRow(block_point.row), Bias::Right, &()); + if let Some(transform) = cursor.item() { + match transform.block.as_ref().map(|b| b.disposition()) { + Some(BlockDisposition::Above) => WrapPoint::new(cursor.start().1 .0, 0), + Some(BlockDisposition::Below) => { + let wrap_row = cursor.start().1 .0 - 1; + WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row)) + } + None => { + let overshoot = block_point.row - cursor.start().0 .0; + let wrap_row = cursor.start().1 .0 + overshoot; + WrapPoint::new(wrap_row, block_point.column) + } + } + } else { + self.wrap_snapshot.max_point() + } + } +} + +impl Transform { + fn isomorphic(rows: u32) -> Self { + Self { + summary: TransformSummary { + input_rows: rows, + output_rows: rows, + }, + block: None, + } + } + + fn block(block: TransformBlock) -> Self { + Self { + summary: TransformSummary { + input_rows: 0, + output_rows: block.height() as u32, + }, + block: Some(block), + } + } + + fn is_isomorphic(&self) -> bool { + self.block.is_none() + } +} + +impl<'a> Iterator for BlockChunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + if self.output_row >= self.max_output_row { + return None; + } + + let transform = self.transforms.item()?; + if transform.block.is_some() { + let block_start = self.transforms.start().0 .0; + let mut block_end = self.transforms.end(&()).0 .0; + self.transforms.next(&()); + if self.transforms.item().is_none() { + block_end -= 1; + } + + let start_in_block = self.output_row - block_start; + let end_in_block = cmp::min(self.max_output_row, block_end) - block_start; + let line_count = end_in_block - start_in_block; + self.output_row += line_count; + + return Some(Chunk { + text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) }, + ..Default::default() + }); + } + + if self.input_chunk.text.is_empty() { + if let Some(input_chunk) = self.input_chunks.next() { + self.input_chunk = input_chunk; + } else { + self.output_row += 1; + if self.output_row < self.max_output_row { + self.transforms.next(&()); + return Some(Chunk { + text: "\n", + ..Default::default() + }); + } else { + return None; + } + } + } + + let transform_end = self.transforms.end(&()).0 .0; + let (prefix_rows, prefix_bytes) = + offset_for_row(self.input_chunk.text, transform_end - self.output_row); + self.output_row += prefix_rows; + let (prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes); + self.input_chunk.text = suffix; + if self.output_row == transform_end { + self.transforms.next(&()); + } + + Some(Chunk { + text: prefix, + ..self.input_chunk + }) + } +} + +impl<'a> Iterator for BlockBufferRows<'a> { + type Item = Option; + + fn next(&mut self) -> Option { + if self.started { + self.output_row += 1; + } else { + self.started = true; + } + + if self.output_row >= self.transforms.end(&()).0 .0 { + self.transforms.next(&()); + } + + let transform = self.transforms.item()?; + if transform.block.is_some() { + Some(None) + } else { + Some(self.input_buffer_rows.next().unwrap()) + } + } +} + +impl sum_tree::Item for Transform { + type Summary = TransformSummary; + + fn summary(&self) -> Self::Summary { + self.summary.clone() + } +} + +impl sum_tree::Summary for TransformSummary { + type Context = (); + + fn add_summary(&mut self, summary: &Self, _: &()) { + self.input_rows += summary.input_rows; + self.output_rows += summary.output_rows; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += summary.input_rows; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += summary.output_rows; + } +} + +impl BlockDisposition { + fn is_below(&self) -> bool { + matches!(self, BlockDisposition::Below) + } +} + +impl<'a> Deref for BlockContext<'a, '_> { + type Target = ViewContext<'a, Editor>; + + fn deref(&self) -> &Self::Target { + self.view_context + } +} + +impl DerefMut for BlockContext<'_, '_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.view_context + } +} + +impl Block { + pub fn render(&self, cx: &mut BlockContext) -> AnyElement { + self.render.lock()(cx) + } + + pub fn position(&self) -> &Anchor { + &self.position + } + + pub fn style(&self) -> BlockStyle { + self.style + } +} + +impl Debug for Block { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Block") + .field("id", &self.id) + .field("position", &self.position) + .field("disposition", &self.disposition) + .finish() + } +} + +// Count the number of bytes prior to a target point. If the string doesn't contain the target +// point, return its total extent. Otherwise return the target point itself. +fn offset_for_row(s: &str, target: u32) -> (u32, usize) { + let mut row = 0; + let mut offset = 0; + for (ix, line) in s.split('\n').enumerate() { + if ix > 0 { + row += 1; + offset += 1; + } + if row >= target { + break; + } + offset += line.len() as usize; + } + (row, offset) +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::display_map::inlay_map::InlayMap; +// use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; +// use gpui::Element; +// use multi_buffer::MultiBuffer; +// use rand::prelude::*; +// use settings::SettingsStore; +// use std::env; +// use util::RandomCharIter; + +// #[gpui::test] +// fn test_offset_for_row() { +// assert_eq!(offset_for_row("", 0), (0, 0)); +// assert_eq!(offset_for_row("", 1), (0, 0)); +// assert_eq!(offset_for_row("abcd", 0), (0, 0)); +// assert_eq!(offset_for_row("abcd", 1), (0, 4)); +// assert_eq!(offset_for_row("\n", 0), (0, 0)); +// assert_eq!(offset_for_row("\n", 1), (1, 1)); +// assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0)); +// assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4)); +// assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8)); +// assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11)); +// } + +// #[gpui::test] +// fn test_basic_blocks(cx: &mut gpui::AppContext) { +// init_test(cx); + +// let family_id = cx +// .font_cache() +// .load_family(&["Helvetica"], &Default::default()) +// .unwrap(); +// let font_id = cx +// .font_cache() +// .select_font(family_id, &Default::default()) +// .unwrap(); + +// let text = "aaa\nbbb\nccc\nddd"; + +// let buffer = MultiBuffer::build_simple(text, cx); +// let buffer_snapshot = buffer.read(cx).snapshot(cx); +// let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); +// let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); +// let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); +// let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap()); +// let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, None, cx); +// let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); + +// let mut writer = block_map.write(wraps_snapshot.clone(), Default::default()); +// let block_ids = writer.insert(vec![ +// BlockProperties { +// style: BlockStyle::Fixed, +// position: buffer_snapshot.anchor_after(Point::new(1, 0)), +// height: 1, +// disposition: BlockDisposition::Above, +// render: Arc::new(|_| Empty::new().into_any_named("block 1")), +// }, +// BlockProperties { +// style: BlockStyle::Fixed, +// position: buffer_snapshot.anchor_after(Point::new(1, 2)), +// height: 2, +// disposition: BlockDisposition::Above, +// render: Arc::new(|_| Empty::new().into_any_named("block 2")), +// }, +// BlockProperties { +// style: BlockStyle::Fixed, +// position: buffer_snapshot.anchor_after(Point::new(3, 3)), +// height: 3, +// disposition: BlockDisposition::Below, +// render: Arc::new(|_| Empty::new().into_any_named("block 3")), +// }, +// ]); + +// let snapshot = block_map.read(wraps_snapshot, Default::default()); +// assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n"); + +// let blocks = snapshot +// .blocks_in_range(0..8) +// .map(|(start_row, block)| { +// let block = block.as_custom().unwrap(); +// (start_row..start_row + block.height as u32, block.id) +// }) +// .collect::>(); + +// // When multiple blocks are on the same line, the newer blocks appear first. +// assert_eq!( +// blocks, +// &[ +// (1..2, block_ids[0]), +// (2..4, block_ids[1]), +// (7..10, block_ids[2]), +// ] +// ); + +// assert_eq!( +// snapshot.to_block_point(WrapPoint::new(0, 3)), +// BlockPoint::new(0, 3) +// ); +// assert_eq!( +// snapshot.to_block_point(WrapPoint::new(1, 0)), +// BlockPoint::new(4, 0) +// ); +// assert_eq!( +// snapshot.to_block_point(WrapPoint::new(3, 3)), +// BlockPoint::new(6, 3) +// ); + +// assert_eq!( +// snapshot.to_wrap_point(BlockPoint::new(0, 3)), +// WrapPoint::new(0, 3) +// ); +// assert_eq!( +// snapshot.to_wrap_point(BlockPoint::new(1, 0)), +// WrapPoint::new(1, 0) +// ); +// assert_eq!( +// snapshot.to_wrap_point(BlockPoint::new(3, 0)), +// WrapPoint::new(1, 0) +// ); +// assert_eq!( +// snapshot.to_wrap_point(BlockPoint::new(7, 0)), +// WrapPoint::new(3, 3) +// ); + +// assert_eq!( +// snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left), +// BlockPoint::new(0, 3) +// ); +// assert_eq!( +// snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right), +// BlockPoint::new(4, 0) +// ); +// assert_eq!( +// snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left), +// BlockPoint::new(0, 3) +// ); +// assert_eq!( +// snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right), +// BlockPoint::new(4, 0) +// ); +// assert_eq!( +// snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left), +// BlockPoint::new(4, 0) +// ); +// assert_eq!( +// snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right), +// BlockPoint::new(4, 0) +// ); +// assert_eq!( +// snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left), +// BlockPoint::new(6, 3) +// ); +// assert_eq!( +// snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right), +// BlockPoint::new(6, 3) +// ); +// assert_eq!( +// snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left), +// BlockPoint::new(6, 3) +// ); +// assert_eq!( +// snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right), +// BlockPoint::new(6, 3) +// ); + +// assert_eq!( +// snapshot.buffer_rows(0).collect::>(), +// &[ +// Some(0), +// None, +// None, +// None, +// Some(1), +// Some(2), +// Some(3), +// None, +// None, +// None +// ] +// ); + +// // Insert a line break, separating two block decorations into separate lines. +// let buffer_snapshot = buffer.update(cx, |buffer, cx| { +// buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx); +// buffer.snapshot(cx) +// }); + +// let (inlay_snapshot, inlay_edits) = +// inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); +// let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); +// let (tab_snapshot, tab_edits) = +// tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap()); +// let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { +// wrap_map.sync(tab_snapshot, tab_edits, cx) +// }); +// let snapshot = block_map.read(wraps_snapshot, wrap_edits); +// assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n"); +// } + +// #[gpui::test] +// fn test_blocks_on_wrapped_lines(cx: &mut gpui::AppContext) { +// init_test(cx); + +// let family_id = cx +// .font_cache() +// .load_family(&["Helvetica"], &Default::default()) +// .unwrap(); +// let font_id = cx +// .font_cache() +// .select_font(family_id, &Default::default()) +// .unwrap(); + +// let text = "one two three\nfour five six\nseven eight"; + +// let buffer = MultiBuffer::build_simple(text, cx); +// let buffer_snapshot = buffer.read(cx).snapshot(cx); +// let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); +// let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); +// let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); +// let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, Some(60.), cx); +// let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); + +// let mut writer = block_map.write(wraps_snapshot.clone(), Default::default()); +// writer.insert(vec![ +// BlockProperties { +// style: BlockStyle::Fixed, +// position: buffer_snapshot.anchor_after(Point::new(1, 12)), +// disposition: BlockDisposition::Above, +// render: Arc::new(|_| Empty::new().into_any_named("block 1")), +// height: 1, +// }, +// BlockProperties { +// style: BlockStyle::Fixed, +// position: buffer_snapshot.anchor_after(Point::new(1, 1)), +// disposition: BlockDisposition::Below, +// render: Arc::new(|_| Empty::new().into_any_named("block 2")), +// height: 1, +// }, +// ]); + +// // Blocks with an 'above' disposition go above their corresponding buffer line. +// // Blocks with a 'below' disposition go below their corresponding buffer line. +// let snapshot = block_map.read(wraps_snapshot, Default::default()); +// assert_eq!( +// snapshot.text(), +// "one two \nthree\n\nfour five \nsix\n\nseven \neight" +// ); +// } + +// #[gpui::test(iterations = 100)] +// fn test_random_blocks(cx: &mut gpui::AppContext, mut rng: StdRng) { +// init_test(cx); + +// let operations = env::var("OPERATIONS") +// .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) +// .unwrap_or(10); + +// let wrap_width = if rng.gen_bool(0.2) { +// None +// } else { +// Some(rng.gen_range(0.0..=100.0)) +// }; +// let tab_size = 1.try_into().unwrap(); +// let family_id = cx +// .font_cache() +// .load_family(&["Helvetica"], &Default::default()) +// .unwrap(); +// let font_id = cx +// .font_cache() +// .select_font(family_id, &Default::default()) +// .unwrap(); +// let font_size = 14.0; +// let buffer_start_header_height = rng.gen_range(1..=5); +// let excerpt_header_height = rng.gen_range(1..=5); + +// log::info!("Wrap width: {:?}", wrap_width); +// log::info!("Excerpt Header Height: {:?}", excerpt_header_height); + +// let buffer = if rng.gen() { +// let len = rng.gen_range(0..10); +// let text = RandomCharIter::new(&mut rng).take(len).collect::(); +// log::info!("initial buffer text: {:?}", text); +// MultiBuffer::build_simple(&text, cx) +// } else { +// MultiBuffer::build_random(&mut rng, cx) +// }; + +// let mut buffer_snapshot = buffer.read(cx).snapshot(cx); +// let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); +// let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); +// let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); +// let (wrap_map, wraps_snapshot) = +// WrapMap::new(tab_snapshot, font_id, font_size, wrap_width, cx); +// let mut block_map = BlockMap::new( +// wraps_snapshot, +// buffer_start_header_height, +// excerpt_header_height, +// ); +// let mut custom_blocks = Vec::new(); + +// for _ in 0..operations { +// let mut buffer_edits = Vec::new(); +// match rng.gen_range(0..=100) { +// 0..=19 => { +// let wrap_width = if rng.gen_bool(0.2) { +// None +// } else { +// Some(rng.gen_range(0.0..=100.0)) +// }; +// log::info!("Setting wrap width to {:?}", wrap_width); +// wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); +// } +// 20..=39 => { +// let block_count = rng.gen_range(1..=5); +// let block_properties = (0..block_count) +// .map(|_| { +// let buffer = buffer.read(cx).read(cx); +// let position = buffer.anchor_after( +// buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left), +// ); + +// let disposition = if rng.gen() { +// BlockDisposition::Above +// } else { +// BlockDisposition::Below +// }; +// let height = rng.gen_range(1..5); +// log::info!( +// "inserting block {:?} {:?} with height {}", +// disposition, +// position.to_point(&buffer), +// height +// ); +// BlockProperties { +// style: BlockStyle::Fixed, +// position, +// height, +// disposition, +// render: Arc::new(|_| Empty::new().into_any()), +// } +// }) +// .collect::>(); + +// let (inlay_snapshot, inlay_edits) = +// inlay_map.sync(buffer_snapshot.clone(), vec![]); +// let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); +// let (tab_snapshot, tab_edits) = +// tab_map.sync(fold_snapshot, fold_edits, tab_size); +// let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { +// wrap_map.sync(tab_snapshot, tab_edits, cx) +// }); +// let mut block_map = block_map.write(wraps_snapshot, wrap_edits); +// let block_ids = block_map.insert(block_properties.clone()); +// for (block_id, props) in block_ids.into_iter().zip(block_properties) { +// custom_blocks.push((block_id, props)); +// } +// } +// 40..=59 if !custom_blocks.is_empty() => { +// let block_count = rng.gen_range(1..=4.min(custom_blocks.len())); +// let block_ids_to_remove = (0..block_count) +// .map(|_| { +// custom_blocks +// .remove(rng.gen_range(0..custom_blocks.len())) +// .0 +// }) +// .collect(); + +// let (inlay_snapshot, inlay_edits) = +// inlay_map.sync(buffer_snapshot.clone(), vec![]); +// let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); +// let (tab_snapshot, tab_edits) = +// tab_map.sync(fold_snapshot, fold_edits, tab_size); +// let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { +// wrap_map.sync(tab_snapshot, tab_edits, cx) +// }); +// let mut block_map = block_map.write(wraps_snapshot, wrap_edits); +// block_map.remove(block_ids_to_remove); +// } +// _ => { +// buffer.update(cx, |buffer, cx| { +// let mutation_count = rng.gen_range(1..=5); +// let subscription = buffer.subscribe(); +// buffer.randomly_mutate(&mut rng, mutation_count, cx); +// buffer_snapshot = buffer.snapshot(cx); +// buffer_edits.extend(subscription.consume()); +// log::info!("buffer text: {:?}", buffer_snapshot.text()); +// }); +// } +// } + +// let (inlay_snapshot, inlay_edits) = +// inlay_map.sync(buffer_snapshot.clone(), buffer_edits); +// let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); +// let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); +// let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { +// wrap_map.sync(tab_snapshot, tab_edits, cx) +// }); +// let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits); +// assert_eq!( +// blocks_snapshot.transforms.summary().input_rows, +// wraps_snapshot.max_point().row() + 1 +// ); +// log::info!("blocks text: {:?}", blocks_snapshot.text()); + +// let mut expected_blocks = Vec::new(); +// expected_blocks.extend(custom_blocks.iter().map(|(id, block)| { +// let mut position = block.position.to_point(&buffer_snapshot); +// match block.disposition { +// BlockDisposition::Above => { +// position.column = 0; +// } +// BlockDisposition::Below => { +// position.column = buffer_snapshot.line_len(position.row); +// } +// }; +// let row = wraps_snapshot.make_wrap_point(position, Bias::Left).row(); +// ( +// row, +// ExpectedBlock::Custom { +// disposition: block.disposition, +// id: *id, +// height: block.height, +// }, +// ) +// })); +// expected_blocks.extend(buffer_snapshot.excerpt_boundaries_in_range(0..).map( +// |boundary| { +// let position = +// wraps_snapshot.make_wrap_point(Point::new(boundary.row, 0), Bias::Left); +// ( +// position.row(), +// ExpectedBlock::ExcerptHeader { +// height: if boundary.starts_new_buffer { +// buffer_start_header_height +// } else { +// excerpt_header_height +// }, +// starts_new_buffer: boundary.starts_new_buffer, +// }, +// ) +// }, +// )); +// expected_blocks.sort_unstable(); +// let mut sorted_blocks_iter = expected_blocks.into_iter().peekable(); + +// let input_buffer_rows = buffer_snapshot.buffer_rows(0).collect::>(); +// let mut expected_buffer_rows = Vec::new(); +// let mut expected_text = String::new(); +// let mut expected_block_positions = Vec::new(); +// let input_text = wraps_snapshot.text(); +// for (row, input_line) in input_text.split('\n').enumerate() { +// let row = row as u32; +// if row > 0 { +// expected_text.push('\n'); +// } + +// let buffer_row = input_buffer_rows[wraps_snapshot +// .to_point(WrapPoint::new(row, 0), Bias::Left) +// .row as usize]; + +// while let Some((block_row, block)) = sorted_blocks_iter.peek() { +// if *block_row == row && block.disposition() == BlockDisposition::Above { +// let (_, block) = sorted_blocks_iter.next().unwrap(); +// let height = block.height() as usize; +// expected_block_positions +// .push((expected_text.matches('\n').count() as u32, block)); +// let text = "\n".repeat(height); +// expected_text.push_str(&text); +// for _ in 0..height { +// expected_buffer_rows.push(None); +// } +// } else { +// break; +// } +// } + +// let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0; +// expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row }); +// expected_text.push_str(input_line); + +// while let Some((block_row, block)) = sorted_blocks_iter.peek() { +// if *block_row == row && block.disposition() == BlockDisposition::Below { +// let (_, block) = sorted_blocks_iter.next().unwrap(); +// let height = block.height() as usize; +// expected_block_positions +// .push((expected_text.matches('\n').count() as u32 + 1, block)); +// let text = "\n".repeat(height); +// expected_text.push_str(&text); +// for _ in 0..height { +// expected_buffer_rows.push(None); +// } +// } else { +// break; +// } +// } +// } + +// let expected_lines = expected_text.split('\n').collect::>(); +// let expected_row_count = expected_lines.len(); +// for start_row in 0..expected_row_count { +// let expected_text = expected_lines[start_row..].join("\n"); +// let actual_text = blocks_snapshot +// .chunks( +// start_row as u32..blocks_snapshot.max_point().row + 1, +// false, +// Highlights::default(), +// ) +// .map(|chunk| chunk.text) +// .collect::(); +// assert_eq!( +// actual_text, expected_text, +// "incorrect text starting from row {}", +// start_row +// ); +// assert_eq!( +// blocks_snapshot +// .buffer_rows(start_row as u32) +// .collect::>(), +// &expected_buffer_rows[start_row..] +// ); +// } + +// assert_eq!( +// blocks_snapshot +// .blocks_in_range(0..(expected_row_count as u32)) +// .map(|(row, block)| (row, block.clone().into())) +// .collect::>(), +// expected_block_positions +// ); + +// let mut expected_longest_rows = Vec::new(); +// let mut longest_line_len = -1_isize; +// for (row, line) in expected_lines.iter().enumerate() { +// let row = row as u32; + +// assert_eq!( +// blocks_snapshot.line_len(row), +// line.len() as u32, +// "invalid line len for row {}", +// row +// ); + +// let line_char_count = line.chars().count() as isize; +// match line_char_count.cmp(&longest_line_len) { +// Ordering::Less => {} +// Ordering::Equal => expected_longest_rows.push(row), +// Ordering::Greater => { +// longest_line_len = line_char_count; +// expected_longest_rows.clear(); +// expected_longest_rows.push(row); +// } +// } +// } + +// let longest_row = blocks_snapshot.longest_row(); +// assert!( +// expected_longest_rows.contains(&longest_row), +// "incorrect longest row {}. expected {:?} with length {}", +// longest_row, +// expected_longest_rows, +// longest_line_len, +// ); + +// for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() { +// let wrap_point = WrapPoint::new(row, 0); +// let block_point = blocks_snapshot.to_block_point(wrap_point); +// assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point); +// } + +// let mut block_point = BlockPoint::new(0, 0); +// for c in expected_text.chars() { +// let left_point = blocks_snapshot.clip_point(block_point, Bias::Left); +// let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left); +// assert_eq!( +// blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)), +// left_point +// ); +// assert_eq!( +// left_buffer_point, +// buffer_snapshot.clip_point(left_buffer_point, Bias::Right), +// "{:?} is not valid in buffer coordinates", +// left_point +// ); + +// let right_point = blocks_snapshot.clip_point(block_point, Bias::Right); +// let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right); +// assert_eq!( +// blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)), +// right_point +// ); +// assert_eq!( +// right_buffer_point, +// buffer_snapshot.clip_point(right_buffer_point, Bias::Left), +// "{:?} is not valid in buffer coordinates", +// right_point +// ); + +// if c == '\n' { +// block_point.0 += Point::new(1, 0); +// } else { +// block_point.column += c.len_utf8() as u32; +// } +// } +// } + +// #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] +// enum ExpectedBlock { +// ExcerptHeader { +// height: u8, +// starts_new_buffer: bool, +// }, +// Custom { +// disposition: BlockDisposition, +// id: BlockId, +// height: u8, +// }, +// } + +// impl ExpectedBlock { +// fn height(&self) -> u8 { +// match self { +// ExpectedBlock::ExcerptHeader { height, .. } => *height, +// ExpectedBlock::Custom { height, .. } => *height, +// } +// } + +// fn disposition(&self) -> BlockDisposition { +// match self { +// ExpectedBlock::ExcerptHeader { .. } => BlockDisposition::Above, +// ExpectedBlock::Custom { disposition, .. } => *disposition, +// } +// } +// } + +// impl From for ExpectedBlock { +// fn from(block: TransformBlock) -> Self { +// match block { +// TransformBlock::Custom(block) => ExpectedBlock::Custom { +// id: block.id, +// disposition: block.disposition, +// height: block.height, +// }, +// TransformBlock::ExcerptHeader { +// height, +// starts_new_buffer, +// .. +// } => ExpectedBlock::ExcerptHeader { +// height, +// starts_new_buffer, +// }, +// } +// } +// } +// } + +// fn init_test(cx: &mut gpui::AppContext) { +// cx.set_global(SettingsStore::test(cx)); +// theme::init(cx); +// } + +// impl TransformBlock { +// fn as_custom(&self) -> Option<&Block> { +// match self { +// TransformBlock::Custom(block) => Some(block), +// TransformBlock::ExcerptHeader { .. } => None, +// } +// } +// } + +// impl BlockSnapshot { +// fn to_point(&self, point: BlockPoint, bias: Bias) -> Point { +// self.wrap_snapshot.to_point(self.to_wrap_point(point), bias) +// } +// } +// } diff --git a/crates/editor2/src/display_map/fold_map.rs b/crates/editor2/src/display_map/fold_map.rs new file mode 100644 index 0000000000..88cd202b08 --- /dev/null +++ b/crates/editor2/src/display_map/fold_map.rs @@ -0,0 +1,1707 @@ +use super::{ + inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot}, + Highlights, +}; +use crate::{Anchor, AnchorRangeExt, MultiBufferSnapshot, ToOffset}; +use gpui::{HighlightStyle, Hsla}; +use language::{Chunk, Edit, Point, TextSummary}; +use std::{ + any::TypeId, + cmp::{self, Ordering}, + iter, + ops::{Add, AddAssign, Range, Sub}, +}; +use sum_tree::{Bias, Cursor, FilterCursor, SumTree}; + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct FoldPoint(pub Point); + +impl FoldPoint { + pub fn new(row: u32, column: u32) -> Self { + Self(Point::new(row, column)) + } + + pub fn row(self) -> u32 { + self.0.row + } + + pub fn column(self) -> u32 { + self.0.column + } + + pub fn row_mut(&mut self) -> &mut u32 { + &mut self.0.row + } + + #[cfg(test)] + pub fn column_mut(&mut self) -> &mut u32 { + &mut self.0.column + } + + pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint { + let mut cursor = snapshot.transforms.cursor::<(FoldPoint, InlayPoint)>(); + cursor.seek(&self, Bias::Right, &()); + let overshoot = self.0 - cursor.start().0 .0; + InlayPoint(cursor.start().1 .0 + overshoot) + } + + pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset { + let mut cursor = snapshot + .transforms + .cursor::<(FoldPoint, TransformSummary)>(); + cursor.seek(&self, Bias::Right, &()); + let overshoot = self.0 - cursor.start().1.output.lines; + let mut offset = cursor.start().1.output.len; + if !overshoot.is_zero() { + let transform = cursor.item().expect("display point out of range"); + assert!(transform.output_text.is_none()); + let end_inlay_offset = snapshot + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1.input.lines + overshoot)); + offset += end_inlay_offset.0 - cursor.start().1.input.len; + } + FoldOffset(offset) + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += &summary.output.lines; + } +} + +pub struct FoldMapWriter<'a>(&'a mut FoldMap); + +impl<'a> FoldMapWriter<'a> { + pub fn fold( + &mut self, + ranges: impl IntoIterator>, + ) -> (FoldSnapshot, Vec) { + let mut edits = Vec::new(); + let mut folds = Vec::new(); + let snapshot = self.0.snapshot.inlay_snapshot.clone(); + for range in ranges.into_iter() { + let buffer = &snapshot.buffer; + let range = range.start.to_offset(&buffer)..range.end.to_offset(&buffer); + + // Ignore any empty ranges. + if range.start == range.end { + continue; + } + + // For now, ignore any ranges that span an excerpt boundary. + let fold = Fold(buffer.anchor_after(range.start)..buffer.anchor_before(range.end)); + if fold.0.start.excerpt_id != fold.0.end.excerpt_id { + continue; + } + + folds.push(fold); + + let inlay_range = + snapshot.to_inlay_offset(range.start)..snapshot.to_inlay_offset(range.end); + edits.push(InlayEdit { + old: inlay_range.clone(), + new: inlay_range, + }); + } + + let buffer = &snapshot.buffer; + folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(a, b, buffer)); + + self.0.snapshot.folds = { + let mut new_tree = SumTree::new(); + let mut cursor = self.0.snapshot.folds.cursor::(); + for fold in folds { + new_tree.append(cursor.slice(&fold, Bias::Right, buffer), buffer); + new_tree.push(fold, buffer); + } + new_tree.append(cursor.suffix(buffer), buffer); + new_tree + }; + + consolidate_inlay_edits(&mut edits); + let edits = self.0.sync(snapshot.clone(), edits); + (self.0.snapshot.clone(), edits) + } + + pub fn unfold( + &mut self, + ranges: impl IntoIterator>, + inclusive: bool, + ) -> (FoldSnapshot, Vec) { + let mut edits = Vec::new(); + let mut fold_ixs_to_delete = Vec::new(); + let snapshot = self.0.snapshot.inlay_snapshot.clone(); + let buffer = &snapshot.buffer; + for range in ranges.into_iter() { + // Remove intersecting folds and add their ranges to edits that are passed to sync. + let mut folds_cursor = + intersecting_folds(&snapshot, &self.0.snapshot.folds, range, inclusive); + while let Some(fold) = folds_cursor.item() { + let offset_range = fold.0.start.to_offset(buffer)..fold.0.end.to_offset(buffer); + if offset_range.end > offset_range.start { + let inlay_range = snapshot.to_inlay_offset(offset_range.start) + ..snapshot.to_inlay_offset(offset_range.end); + edits.push(InlayEdit { + old: inlay_range.clone(), + new: inlay_range, + }); + } + fold_ixs_to_delete.push(*folds_cursor.start()); + folds_cursor.next(buffer); + } + } + + fold_ixs_to_delete.sort_unstable(); + fold_ixs_to_delete.dedup(); + + self.0.snapshot.folds = { + let mut cursor = self.0.snapshot.folds.cursor::(); + let mut folds = SumTree::new(); + for fold_ix in fold_ixs_to_delete { + folds.append(cursor.slice(&fold_ix, Bias::Right, buffer), buffer); + cursor.next(buffer); + } + folds.append(cursor.suffix(buffer), buffer); + folds + }; + + consolidate_inlay_edits(&mut edits); + let edits = self.0.sync(snapshot.clone(), edits); + (self.0.snapshot.clone(), edits) + } +} + +pub struct FoldMap { + snapshot: FoldSnapshot, + ellipses_color: Option, +} + +impl FoldMap { + pub fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) { + let this = Self { + snapshot: FoldSnapshot { + folds: Default::default(), + transforms: SumTree::from_item( + Transform { + summary: TransformSummary { + input: inlay_snapshot.text_summary(), + output: inlay_snapshot.text_summary(), + }, + output_text: None, + }, + &(), + ), + inlay_snapshot: inlay_snapshot.clone(), + version: 0, + ellipses_color: None, + }, + ellipses_color: None, + }; + let snapshot = this.snapshot.clone(); + (this, snapshot) + } + + pub fn read( + &mut self, + inlay_snapshot: InlaySnapshot, + edits: Vec, + ) -> (FoldSnapshot, Vec) { + let edits = self.sync(inlay_snapshot, edits); + self.check_invariants(); + (self.snapshot.clone(), edits) + } + + pub fn write( + &mut self, + inlay_snapshot: InlaySnapshot, + edits: Vec, + ) -> (FoldMapWriter, FoldSnapshot, Vec) { + let (snapshot, edits) = self.read(inlay_snapshot, edits); + (FoldMapWriter(self), snapshot, edits) + } + + pub fn set_ellipses_color(&mut self, color: Hsla) -> bool { + if self.ellipses_color != Some(color) { + self.ellipses_color = Some(color); + true + } else { + false + } + } + + fn check_invariants(&self) { + if cfg!(test) { + assert_eq!( + self.snapshot.transforms.summary().input.len, + self.snapshot.inlay_snapshot.len().0, + "transform tree does not match inlay snapshot's length" + ); + + let mut folds = self.snapshot.folds.iter().peekable(); + while let Some(fold) = folds.next() { + if let Some(next_fold) = folds.peek() { + let comparison = fold + .0 + .cmp(&next_fold.0, &self.snapshot.inlay_snapshot.buffer); + assert!(comparison.is_le()); + } + } + } + } + + fn sync( + &mut self, + inlay_snapshot: InlaySnapshot, + inlay_edits: Vec, + ) -> Vec { + if inlay_edits.is_empty() { + if self.snapshot.inlay_snapshot.version != inlay_snapshot.version { + self.snapshot.version += 1; + } + self.snapshot.inlay_snapshot = inlay_snapshot; + Vec::new() + } else { + let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable(); + + let mut new_transforms = SumTree::new(); + let mut cursor = self.snapshot.transforms.cursor::(); + cursor.seek(&InlayOffset(0), Bias::Right, &()); + + while let Some(mut edit) = inlay_edits_iter.next() { + new_transforms.append(cursor.slice(&edit.old.start, Bias::Left, &()), &()); + edit.new.start -= edit.old.start - *cursor.start(); + edit.old.start = *cursor.start(); + + cursor.seek(&edit.old.end, Bias::Right, &()); + cursor.next(&()); + + let mut delta = edit.new_len().0 as isize - edit.old_len().0 as isize; + loop { + edit.old.end = *cursor.start(); + + if let Some(next_edit) = inlay_edits_iter.peek() { + if next_edit.old.start > edit.old.end { + break; + } + + let next_edit = inlay_edits_iter.next().unwrap(); + delta += next_edit.new_len().0 as isize - next_edit.old_len().0 as isize; + + if next_edit.old.end >= edit.old.end { + edit.old.end = next_edit.old.end; + cursor.seek(&edit.old.end, Bias::Right, &()); + cursor.next(&()); + } + } else { + break; + } + } + + edit.new.end = + InlayOffset(((edit.new.start + edit.old_len()).0 as isize + delta) as usize); + + let anchor = inlay_snapshot + .buffer + .anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start)); + let mut folds_cursor = self.snapshot.folds.cursor::(); + folds_cursor.seek( + &Fold(anchor..Anchor::max()), + Bias::Left, + &inlay_snapshot.buffer, + ); + + let mut folds = iter::from_fn({ + let inlay_snapshot = &inlay_snapshot; + move || { + let item = folds_cursor.item().map(|f| { + let buffer_start = f.0.start.to_offset(&inlay_snapshot.buffer); + let buffer_end = f.0.end.to_offset(&inlay_snapshot.buffer); + inlay_snapshot.to_inlay_offset(buffer_start) + ..inlay_snapshot.to_inlay_offset(buffer_end) + }); + folds_cursor.next(&inlay_snapshot.buffer); + item + } + }) + .peekable(); + + while folds.peek().map_or(false, |fold| fold.start < edit.new.end) { + let mut fold = folds.next().unwrap(); + let sum = new_transforms.summary(); + + assert!(fold.start.0 >= sum.input.len); + + while folds + .peek() + .map_or(false, |next_fold| next_fold.start <= fold.end) + { + let next_fold = folds.next().unwrap(); + if next_fold.end > fold.end { + fold.end = next_fold.end; + } + } + + if fold.start.0 > sum.input.len { + let text_summary = inlay_snapshot + .text_summary_for_range(InlayOffset(sum.input.len)..fold.start); + new_transforms.push( + Transform { + summary: TransformSummary { + output: text_summary.clone(), + input: text_summary, + }, + output_text: None, + }, + &(), + ); + } + + if fold.end > fold.start { + let output_text = "⋯"; + new_transforms.push( + Transform { + summary: TransformSummary { + output: TextSummary::from(output_text), + input: inlay_snapshot + .text_summary_for_range(fold.start..fold.end), + }, + output_text: Some(output_text), + }, + &(), + ); + } + } + + let sum = new_transforms.summary(); + if sum.input.len < edit.new.end.0 { + let text_summary = inlay_snapshot + .text_summary_for_range(InlayOffset(sum.input.len)..edit.new.end); + new_transforms.push( + Transform { + summary: TransformSummary { + output: text_summary.clone(), + input: text_summary, + }, + output_text: None, + }, + &(), + ); + } + } + + new_transforms.append(cursor.suffix(&()), &()); + if new_transforms.is_empty() { + let text_summary = inlay_snapshot.text_summary(); + new_transforms.push( + Transform { + summary: TransformSummary { + output: text_summary.clone(), + input: text_summary, + }, + output_text: None, + }, + &(), + ); + } + + drop(cursor); + + let mut fold_edits = Vec::with_capacity(inlay_edits.len()); + { + let mut old_transforms = self + .snapshot + .transforms + .cursor::<(InlayOffset, FoldOffset)>(); + let mut new_transforms = new_transforms.cursor::<(InlayOffset, FoldOffset)>(); + + for mut edit in inlay_edits { + old_transforms.seek(&edit.old.start, Bias::Left, &()); + if old_transforms.item().map_or(false, |t| t.is_fold()) { + edit.old.start = old_transforms.start().0; + } + let old_start = + old_transforms.start().1 .0 + (edit.old.start - old_transforms.start().0).0; + + old_transforms.seek_forward(&edit.old.end, Bias::Right, &()); + if old_transforms.item().map_or(false, |t| t.is_fold()) { + old_transforms.next(&()); + edit.old.end = old_transforms.start().0; + } + let old_end = + old_transforms.start().1 .0 + (edit.old.end - old_transforms.start().0).0; + + new_transforms.seek(&edit.new.start, Bias::Left, &()); + if new_transforms.item().map_or(false, |t| t.is_fold()) { + edit.new.start = new_transforms.start().0; + } + let new_start = + new_transforms.start().1 .0 + (edit.new.start - new_transforms.start().0).0; + + new_transforms.seek_forward(&edit.new.end, Bias::Right, &()); + if new_transforms.item().map_or(false, |t| t.is_fold()) { + new_transforms.next(&()); + edit.new.end = new_transforms.start().0; + } + let new_end = + new_transforms.start().1 .0 + (edit.new.end - new_transforms.start().0).0; + + fold_edits.push(FoldEdit { + old: FoldOffset(old_start)..FoldOffset(old_end), + new: FoldOffset(new_start)..FoldOffset(new_end), + }); + } + + consolidate_fold_edits(&mut fold_edits); + } + + self.snapshot.transforms = new_transforms; + self.snapshot.inlay_snapshot = inlay_snapshot; + self.snapshot.version += 1; + fold_edits + } + } +} + +#[derive(Clone)] +pub struct FoldSnapshot { + transforms: SumTree, + folds: SumTree, + pub inlay_snapshot: InlaySnapshot, + pub version: usize, + pub ellipses_color: Option, +} + +impl FoldSnapshot { + #[cfg(test)] + pub fn text(&self) -> String { + self.chunks(FoldOffset(0)..self.len(), false, Highlights::default()) + .map(|c| c.text) + .collect() + } + + #[cfg(test)] + pub fn fold_count(&self) -> usize { + self.folds.items(&self.inlay_snapshot.buffer).len() + } + + pub fn text_summary_for_range(&self, range: Range) -> TextSummary { + let mut summary = TextSummary::default(); + + let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(); + cursor.seek(&range.start, Bias::Right, &()); + if let Some(transform) = cursor.item() { + let start_in_transform = range.start.0 - cursor.start().0 .0; + let end_in_transform = cmp::min(range.end, cursor.end(&()).0).0 - cursor.start().0 .0; + if let Some(output_text) = transform.output_text { + summary = TextSummary::from( + &output_text + [start_in_transform.column as usize..end_in_transform.column as usize], + ); + } else { + let inlay_start = self + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1 .0 + start_in_transform)); + let inlay_end = self + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform)); + summary = self + .inlay_snapshot + .text_summary_for_range(inlay_start..inlay_end); + } + } + + if range.end > cursor.end(&()).0 { + cursor.next(&()); + summary += &cursor + .summary::<_, TransformSummary>(&range.end, Bias::Right, &()) + .output; + if let Some(transform) = cursor.item() { + let end_in_transform = range.end.0 - cursor.start().0 .0; + if let Some(output_text) = transform.output_text { + summary += TextSummary::from(&output_text[..end_in_transform.column as usize]); + } else { + let inlay_start = self.inlay_snapshot.to_offset(cursor.start().1); + let inlay_end = self + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform)); + summary += self + .inlay_snapshot + .text_summary_for_range(inlay_start..inlay_end); + } + } + } + + summary + } + + pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint { + let mut cursor = self.transforms.cursor::<(InlayPoint, 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.0 - cursor.start().0 .0; + FoldPoint(cmp::min( + cursor.start().1 .0 + overshoot, + cursor.end(&()).1 .0, + )) + } + } + + pub fn len(&self) -> FoldOffset { + FoldOffset(self.transforms.summary().output.len) + } + + pub fn line_len(&self, row: u32) -> u32 { + let line_start = FoldPoint::new(row, 0).to_offset(self).0; + let line_end = if row >= self.max_point().row() { + self.len().0 + } else { + FoldPoint::new(row + 1, 0).to_offset(self).0 - 1 + }; + (line_end - line_start) as u32 + } + + pub fn buffer_rows(&self, start_row: u32) -> FoldBufferRows { + if start_row > self.transforms.summary().output.lines.row { + panic!("invalid display row {}", start_row); + } + + let fold_point = FoldPoint::new(start_row, 0); + let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(); + cursor.seek(&fold_point, Bias::Left, &()); + + let overshoot = fold_point.0 - cursor.start().0 .0; + let inlay_point = InlayPoint(cursor.start().1 .0 + overshoot); + let input_buffer_rows = self.inlay_snapshot.buffer_rows(inlay_point.row()); + + FoldBufferRows { + fold_point, + input_buffer_rows, + cursor, + } + } + + pub fn max_point(&self) -> FoldPoint { + FoldPoint(self.transforms.summary().output.lines) + } + + #[cfg(test)] + pub fn longest_row(&self) -> u32 { + self.transforms.summary().output.longest_row + } + + pub fn folds_in_range(&self, range: Range) -> impl Iterator> + where + T: ToOffset, + { + let mut folds = intersecting_folds(&self.inlay_snapshot, &self.folds, range, false); + iter::from_fn(move || { + let item = folds.item().map(|f| &f.0); + folds.next(&self.inlay_snapshot.buffer); + item + }) + } + + pub fn intersects_fold(&self, offset: T) -> bool + where + T: ToOffset, + { + let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer); + let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset); + let mut cursor = self.transforms.cursor::(); + cursor.seek(&inlay_offset, Bias::Right, &()); + cursor.item().map_or(false, |t| t.output_text.is_some()) + } + + pub fn is_line_folded(&self, buffer_row: u32) -> bool { + let mut inlay_point = self + .inlay_snapshot + .to_inlay_point(Point::new(buffer_row, 0)); + let mut cursor = self.transforms.cursor::(); + cursor.seek(&inlay_point, Bias::Right, &()); + loop { + match cursor.item() { + Some(transform) => { + let buffer_point = self.inlay_snapshot.to_buffer_point(inlay_point); + if buffer_point.row != buffer_row { + return false; + } else if transform.output_text.is_some() { + return true; + } + } + None => return false, + } + + if cursor.end(&()).row() == inlay_point.row() { + cursor.next(&()); + } else { + inlay_point.0 += Point::new(1, 0); + cursor.seek(&inlay_point, Bias::Right, &()); + } + } + } + + pub fn chunks<'a>( + &'a self, + range: Range, + language_aware: bool, + highlights: Highlights<'a>, + ) -> FoldChunks<'a> { + let mut transform_cursor = self.transforms.cursor::<(FoldOffset, InlayOffset)>(); + + let inlay_end = { + transform_cursor.seek(&range.end, Bias::Right, &()); + let overshoot = range.end.0 - transform_cursor.start().0 .0; + transform_cursor.start().1 + InlayOffset(overshoot) + }; + + let inlay_start = { + transform_cursor.seek(&range.start, Bias::Right, &()); + let overshoot = range.start.0 - transform_cursor.start().0 .0; + transform_cursor.start().1 + InlayOffset(overshoot) + }; + + FoldChunks { + transform_cursor, + inlay_chunks: self.inlay_snapshot.chunks( + inlay_start..inlay_end, + language_aware, + highlights, + ), + inlay_chunk: None, + inlay_offset: inlay_start, + output_offset: range.start.0, + max_output_offset: range.end.0, + ellipses_color: self.ellipses_color, + } + } + + pub fn chars_at(&self, start: FoldPoint) -> impl '_ + Iterator { + self.chunks( + start.to_offset(self)..self.len(), + false, + Highlights::default(), + ) + .flat_map(|chunk| chunk.text.chars()) + } + + #[cfg(test)] + pub fn clip_offset(&self, offset: FoldOffset, bias: Bias) -> FoldOffset { + if offset > self.len() { + self.len() + } else { + self.clip_point(offset.to_point(self), bias).to_offset(self) + } + } + + pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint { + let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(); + cursor.seek(&point, Bias::Right, &()); + if let Some(transform) = cursor.item() { + let transform_start = cursor.start().0 .0; + if transform.output_text.is_some() { + if point.0 == transform_start || matches!(bias, Bias::Left) { + FoldPoint(transform_start) + } else { + FoldPoint(cursor.end(&()).0 .0) + } + } else { + let overshoot = InlayPoint(point.0 - transform_start); + let inlay_point = cursor.start().1 + overshoot; + let clipped_inlay_point = self.inlay_snapshot.clip_point(inlay_point, bias); + FoldPoint(cursor.start().0 .0 + (clipped_inlay_point - cursor.start().1).0) + } + } else { + FoldPoint(self.transforms.summary().output.lines) + } + } +} + +fn intersecting_folds<'a, T>( + inlay_snapshot: &'a InlaySnapshot, + folds: &'a SumTree, + range: Range, + inclusive: bool, +) -> FilterCursor<'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize> +where + T: ToOffset, +{ + let buffer = &inlay_snapshot.buffer; + let start = buffer.anchor_before(range.start.to_offset(buffer)); + let end = buffer.anchor_after(range.end.to_offset(buffer)); + let mut cursor = folds.filter::<_, usize>(move |summary| { + let start_cmp = start.cmp(&summary.max_end, buffer); + let end_cmp = end.cmp(&summary.min_start, buffer); + + if inclusive { + start_cmp <= Ordering::Equal && end_cmp >= Ordering::Equal + } else { + start_cmp == Ordering::Less && end_cmp == Ordering::Greater + } + }); + cursor.next(buffer); + cursor +} + +fn consolidate_inlay_edits(edits: &mut Vec) { + edits.sort_unstable_by(|a, b| { + a.old + .start + .cmp(&b.old.start) + .then_with(|| b.old.end.cmp(&a.old.end)) + }); + + let mut i = 1; + while i < edits.len() { + let edit = edits[i].clone(); + let prev_edit = &mut edits[i - 1]; + if prev_edit.old.end >= edit.old.start { + prev_edit.old.end = prev_edit.old.end.max(edit.old.end); + prev_edit.new.start = prev_edit.new.start.min(edit.new.start); + prev_edit.new.end = prev_edit.new.end.max(edit.new.end); + edits.remove(i); + continue; + } + i += 1; + } +} + +fn consolidate_fold_edits(edits: &mut Vec) { + edits.sort_unstable_by(|a, b| { + a.old + .start + .cmp(&b.old.start) + .then_with(|| b.old.end.cmp(&a.old.end)) + }); + + let mut i = 1; + while i < edits.len() { + let edit = edits[i].clone(); + let prev_edit = &mut edits[i - 1]; + if prev_edit.old.end >= edit.old.start { + prev_edit.old.end = prev_edit.old.end.max(edit.old.end); + prev_edit.new.start = prev_edit.new.start.min(edit.new.start); + prev_edit.new.end = prev_edit.new.end.max(edit.new.end); + edits.remove(i); + continue; + } + i += 1; + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +struct Transform { + summary: TransformSummary, + output_text: Option<&'static str>, +} + +impl Transform { + fn is_fold(&self) -> bool { + self.output_text.is_some() + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +struct TransformSummary { + output: TextSummary, + input: TextSummary, +} + +impl sum_tree::Item for Transform { + type Summary = TransformSummary; + + fn summary(&self) -> Self::Summary { + self.summary.clone() + } +} + +impl sum_tree::Summary for TransformSummary { + type Context = (); + + fn add_summary(&mut self, other: &Self, _: &()) { + self.input += &other.input; + self.output += &other.output; + } +} + +#[derive(Clone, Debug)] +struct Fold(Range); + +impl Default for Fold { + fn default() -> Self { + Self(Anchor::min()..Anchor::max()) + } +} + +impl sum_tree::Item for Fold { + type Summary = FoldSummary; + + fn summary(&self) -> Self::Summary { + FoldSummary { + start: self.0.start.clone(), + end: self.0.end.clone(), + min_start: self.0.start.clone(), + max_end: self.0.end.clone(), + count: 1, + } + } +} + +#[derive(Clone, Debug)] +struct FoldSummary { + start: Anchor, + end: Anchor, + min_start: Anchor, + max_end: Anchor, + count: usize, +} + +impl Default for FoldSummary { + fn default() -> Self { + Self { + start: Anchor::min(), + end: Anchor::max(), + min_start: Anchor::max(), + max_end: Anchor::min(), + count: 0, + } + } +} + +impl sum_tree::Summary for FoldSummary { + type Context = MultiBufferSnapshot; + + fn add_summary(&mut self, other: &Self, buffer: &Self::Context) { + if other.min_start.cmp(&self.min_start, buffer) == Ordering::Less { + self.min_start = other.min_start.clone(); + } + if other.max_end.cmp(&self.max_end, buffer) == Ordering::Greater { + self.max_end = other.max_end.clone(); + } + + #[cfg(debug_assertions)] + { + let start_comparison = self.start.cmp(&other.start, buffer); + assert!(start_comparison <= Ordering::Equal); + if start_comparison == Ordering::Equal { + assert!(self.end.cmp(&other.end, buffer) >= Ordering::Equal); + } + } + + self.start = other.start.clone(); + self.end = other.end.clone(); + self.count += other.count; + } +} + +impl<'a> sum_tree::Dimension<'a, FoldSummary> for Fold { + fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) { + self.0.start = summary.start.clone(); + self.0.end = summary.end.clone(); + } +} + +impl<'a> sum_tree::SeekTarget<'a, FoldSummary, Fold> for Fold { + fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering { + self.0.cmp(&other.0, buffer) + } +} + +impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize { + fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) { + *self += summary.count; + } +} + +#[derive(Clone)] +pub struct FoldBufferRows<'a> { + cursor: Cursor<'a, Transform, (FoldPoint, InlayPoint)>, + input_buffer_rows: InlayBufferRows<'a>, + fold_point: FoldPoint, +} + +impl<'a> Iterator for FoldBufferRows<'a> { + type Item = Option; + + fn next(&mut self) -> Option { + let mut traversed_fold = false; + while self.fold_point > self.cursor.end(&()).0 { + self.cursor.next(&()); + traversed_fold = true; + if self.cursor.item().is_none() { + break; + } + } + + if self.cursor.item().is_some() { + if traversed_fold { + self.input_buffer_rows.seek(self.cursor.start().1.row()); + self.input_buffer_rows.next(); + } + *self.fold_point.row_mut() += 1; + self.input_buffer_rows.next() + } else { + None + } + } +} + +pub struct FoldChunks<'a> { + transform_cursor: Cursor<'a, Transform, (FoldOffset, InlayOffset)>, + inlay_chunks: InlayChunks<'a>, + inlay_chunk: Option<(InlayOffset, Chunk<'a>)>, + inlay_offset: InlayOffset, + output_offset: usize, + max_output_offset: usize, + ellipses_color: Option, +} + +impl<'a> Iterator for FoldChunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + if self.output_offset >= self.max_output_offset { + return None; + } + + let transform = self.transform_cursor.item()?; + + // If we're in a fold, then return the fold's display text and + // advance the transform and buffer cursors to the end of the fold. + if let Some(output_text) = transform.output_text { + self.inlay_chunk.take(); + self.inlay_offset += InlayOffset(transform.summary.input.len); + self.inlay_chunks.seek(self.inlay_offset); + + while self.inlay_offset >= self.transform_cursor.end(&()).1 + && self.transform_cursor.item().is_some() + { + self.transform_cursor.next(&()); + } + + self.output_offset += output_text.len(); + return Some(Chunk { + text: output_text, + highlight_style: self.ellipses_color.map(|color| HighlightStyle { + color: Some(color), + ..Default::default() + }), + ..Default::default() + }); + } + + // Retrieve a chunk from the current location in the buffer. + if self.inlay_chunk.is_none() { + let chunk_offset = self.inlay_chunks.offset(); + self.inlay_chunk = self.inlay_chunks.next().map(|chunk| (chunk_offset, chunk)); + } + + // Otherwise, take a chunk from the buffer's text. + if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk { + let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len()); + let transform_end = self.transform_cursor.end(&()).1; + let chunk_end = buffer_chunk_end.min(transform_end); + + chunk.text = &chunk.text + [(self.inlay_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0]; + + if chunk_end == transform_end { + self.transform_cursor.next(&()); + } else if chunk_end == buffer_chunk_end { + self.inlay_chunk.take(); + } + + self.inlay_offset = chunk_end; + self.output_offset += chunk.text.len(); + return Some(chunk); + } + + None + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +struct HighlightEndpoint { + offset: InlayOffset, + is_start: bool, + tag: Option, + style: HighlightStyle, +} + +impl PartialOrd for HighlightEndpoint { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for HighlightEndpoint { + fn cmp(&self, other: &Self) -> Ordering { + self.offset + .cmp(&other.offset) + .then_with(|| other.is_start.cmp(&self.is_start)) + } +} + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct FoldOffset(pub usize); + +impl FoldOffset { + pub fn to_point(self, snapshot: &FoldSnapshot) -> FoldPoint { + let mut cursor = snapshot + .transforms + .cursor::<(FoldOffset, TransformSummary)>(); + cursor.seek(&self, Bias::Right, &()); + let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) { + Point::new(0, (self.0 - cursor.start().0 .0) as u32) + } else { + let inlay_offset = cursor.start().1.input.len + self.0 - cursor.start().0 .0; + let inlay_point = snapshot.inlay_snapshot.to_point(InlayOffset(inlay_offset)); + inlay_point.0 - cursor.start().1.input.lines + }; + FoldPoint(cursor.start().1.output.lines + overshoot) + } + + #[cfg(test)] + pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset { + let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>(); + cursor.seek(&self, Bias::Right, &()); + let overshoot = self.0 - cursor.start().0 .0; + InlayOffset(cursor.start().1 .0 + overshoot) + } +} + +impl Add for FoldOffset { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl AddAssign for FoldOffset { + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + } +} + +impl Sub for FoldOffset { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += &summary.output.len; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += &summary.input.lines; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += &summary.input.len; + } +} + +pub type FoldEdit = Edit; + +#[cfg(test)] +mod tests { + use super::*; + use crate::{display_map::inlay_map::InlayMap, MultiBuffer, ToPoint}; + use collections::HashSet; + use rand::prelude::*; + use settings::SettingsStore; + use std::{env, mem}; + use text::Patch; + use util::test::sample_text; + use util::RandomCharIter; + use Bias::{Left, Right}; + + #[gpui::test] + fn test_basic_folds(cx: &mut gpui::AppContext) { + init_test(cx); + let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); + let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); + let buffer_snapshot = buffer.read(cx).snapshot(cx); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; + + let (mut writer, _, _) = map.write(inlay_snapshot, vec![]); + let (snapshot2, edits) = writer.fold(vec![ + Point::new(0, 2)..Point::new(2, 2), + Point::new(2, 4)..Point::new(4, 1), + ]); + assert_eq!(snapshot2.text(), "aa⋯cc⋯eeeee"); + assert_eq!( + edits, + &[ + FoldEdit { + old: FoldOffset(2)..FoldOffset(16), + new: FoldOffset(2)..FoldOffset(5), + }, + FoldEdit { + old: FoldOffset(18)..FoldOffset(29), + new: FoldOffset(7)..FoldOffset(10) + }, + ] + ); + + let buffer_snapshot = buffer.update(cx, |buffer, cx| { + buffer.edit( + vec![ + (Point::new(0, 0)..Point::new(0, 1), "123"), + (Point::new(2, 3)..Point::new(2, 3), "123"), + ], + None, + cx, + ); + buffer.snapshot(cx) + }); + + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); + let (snapshot3, edits) = map.read(inlay_snapshot, inlay_edits); + assert_eq!(snapshot3.text(), "123a⋯c123c⋯eeeee"); + assert_eq!( + edits, + &[ + FoldEdit { + old: FoldOffset(0)..FoldOffset(1), + new: FoldOffset(0)..FoldOffset(3), + }, + FoldEdit { + old: FoldOffset(6)..FoldOffset(6), + new: FoldOffset(8)..FoldOffset(11), + }, + ] + ); + + let buffer_snapshot = buffer.update(cx, |buffer, cx| { + buffer.edit([(Point::new(2, 6)..Point::new(4, 3), "456")], None, cx); + buffer.snapshot(cx) + }); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); + let (snapshot4, _) = map.read(inlay_snapshot.clone(), inlay_edits); + assert_eq!(snapshot4.text(), "123a⋯c123456eee"); + + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), false); + let (snapshot5, _) = map.read(inlay_snapshot.clone(), vec![]); + assert_eq!(snapshot5.text(), "123a⋯c123456eee"); + + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), true); + let (snapshot6, _) = map.read(inlay_snapshot, vec![]); + assert_eq!(snapshot6.text(), "123aaaaa\nbbbbbb\nccc123456eee"); + } + + #[gpui::test] + fn test_adjacent_folds(cx: &mut gpui::AppContext) { + init_test(cx); + let buffer = MultiBuffer::build_simple("abcdefghijkl", cx); + let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); + let buffer_snapshot = buffer.read(cx).snapshot(cx); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + + { + let mut map = FoldMap::new(inlay_snapshot.clone()).0; + + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + writer.fold(vec![5..8]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); + assert_eq!(snapshot.text(), "abcde⋯ijkl"); + + // Create an fold adjacent to the start of the first fold. + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + writer.fold(vec![0..1, 2..5]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); + assert_eq!(snapshot.text(), "⋯b⋯ijkl"); + + // Create an fold adjacent to the end of the first fold. + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + writer.fold(vec![11..11, 8..10]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); + assert_eq!(snapshot.text(), "⋯b⋯kl"); + } + + { + let mut map = FoldMap::new(inlay_snapshot.clone()).0; + + // Create two adjacent folds. + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + writer.fold(vec![0..2, 2..5]); + let (snapshot, _) = map.read(inlay_snapshot, vec![]); + assert_eq!(snapshot.text(), "⋯fghijkl"); + + // Edit within one of the folds. + let buffer_snapshot = buffer.update(cx, |buffer, cx| { + buffer.edit([(0..1, "12345")], None, cx); + buffer.snapshot(cx) + }); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); + let (snapshot, _) = map.read(inlay_snapshot, inlay_edits); + assert_eq!(snapshot.text(), "12345⋯fghijkl"); + } + } + + #[gpui::test] + fn test_overlapping_folds(cx: &mut gpui::AppContext) { + let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); + let buffer_snapshot = buffer.read(cx).snapshot(cx); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + writer.fold(vec![ + Point::new(0, 2)..Point::new(2, 2), + Point::new(0, 4)..Point::new(1, 0), + Point::new(1, 2)..Point::new(3, 2), + Point::new(3, 1)..Point::new(4, 1), + ]); + let (snapshot, _) = map.read(inlay_snapshot, vec![]); + assert_eq!(snapshot.text(), "aa⋯eeeee"); + } + + #[gpui::test] + fn test_merging_folds_via_edit(cx: &mut gpui::AppContext) { + init_test(cx); + let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); + let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); + let buffer_snapshot = buffer.read(cx).snapshot(cx); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; + + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + writer.fold(vec![ + Point::new(0, 2)..Point::new(2, 2), + Point::new(3, 1)..Point::new(4, 1), + ]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); + assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee"); + + let buffer_snapshot = buffer.update(cx, |buffer, cx| { + buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], None, cx); + buffer.snapshot(cx) + }); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); + let (snapshot, _) = map.read(inlay_snapshot, inlay_edits); + assert_eq!(snapshot.text(), "aa⋯eeeee"); + } + + #[gpui::test] + fn test_folds_in_range(cx: &mut gpui::AppContext) { + let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); + let buffer_snapshot = buffer.read(cx).snapshot(cx); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; + + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + writer.fold(vec![ + Point::new(0, 2)..Point::new(2, 2), + Point::new(0, 4)..Point::new(1, 0), + Point::new(1, 2)..Point::new(3, 2), + Point::new(3, 1)..Point::new(4, 1), + ]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); + let fold_ranges = snapshot + .folds_in_range(Point::new(1, 0)..Point::new(1, 3)) + .map(|fold| fold.start.to_point(&buffer_snapshot)..fold.end.to_point(&buffer_snapshot)) + .collect::>(); + assert_eq!( + fold_ranges, + vec![ + Point::new(0, 2)..Point::new(2, 2), + Point::new(1, 2)..Point::new(3, 2) + ] + ); + } + + #[gpui::test(iterations = 100)] + fn test_random_folds(cx: &mut gpui::AppContext, mut rng: StdRng) { + init_test(cx); + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let len = rng.gen_range(0..10); + let text = RandomCharIter::new(&mut rng).take(len).collect::(); + let buffer = if rng.gen() { + MultiBuffer::build_simple(&text, cx) + } else { + MultiBuffer::build_random(&mut rng, cx) + }; + let mut buffer_snapshot = buffer.read(cx).snapshot(cx); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; + + let (mut initial_snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); + let mut snapshot_edits = Vec::new(); + + let mut next_inlay_id = 0; + for _ in 0..operations { + log::info!("text: {:?}", buffer_snapshot.text()); + let mut buffer_edits = Vec::new(); + let mut inlay_edits = Vec::new(); + match rng.gen_range(0..=100) { + 0..=39 => { + snapshot_edits.extend(map.randomly_mutate(&mut rng)); + } + 40..=59 => { + let (_, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); + inlay_edits = edits; + } + _ => buffer.update(cx, |buffer, cx| { + let subscription = buffer.subscribe(); + let edit_count = rng.gen_range(1..=5); + buffer.randomly_mutate(&mut rng, edit_count, cx); + buffer_snapshot = buffer.snapshot(cx); + let edits = subscription.consume().into_inner(); + log::info!("editing {:?}", edits); + buffer_edits.extend(edits); + }), + }; + + let (inlay_snapshot, new_inlay_edits) = + inlay_map.sync(buffer_snapshot.clone(), buffer_edits); + log::info!("inlay text {:?}", inlay_snapshot.text()); + + let inlay_edits = Patch::new(inlay_edits) + .compose(new_inlay_edits) + .into_inner(); + let (snapshot, edits) = map.read(inlay_snapshot.clone(), inlay_edits); + snapshot_edits.push((snapshot.clone(), edits)); + + let mut expected_text: String = inlay_snapshot.text().to_string(); + for fold_range in map.merged_fold_ranges().into_iter().rev() { + let fold_inlay_start = inlay_snapshot.to_inlay_offset(fold_range.start); + let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end); + expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, "⋯"); + } + + assert_eq!(snapshot.text(), expected_text); + log::info!( + "fold text {:?} ({} lines)", + expected_text, + expected_text.matches('\n').count() + 1 + ); + + let mut prev_row = 0; + let mut expected_buffer_rows = Vec::new(); + for fold_range in map.merged_fold_ranges().into_iter() { + let fold_start = inlay_snapshot + .to_point(inlay_snapshot.to_inlay_offset(fold_range.start)) + .row(); + let fold_end = inlay_snapshot + .to_point(inlay_snapshot.to_inlay_offset(fold_range.end)) + .row(); + expected_buffer_rows.extend( + inlay_snapshot + .buffer_rows(prev_row) + .take((1 + fold_start - prev_row) as usize), + ); + prev_row = 1 + fold_end; + } + expected_buffer_rows.extend(inlay_snapshot.buffer_rows(prev_row)); + + assert_eq!( + expected_buffer_rows.len(), + expected_text.matches('\n').count() + 1, + "wrong expected buffer rows {:?}. text: {:?}", + expected_buffer_rows, + expected_text + ); + + for (output_row, line) in expected_text.lines().enumerate() { + let line_len = snapshot.line_len(output_row as u32); + assert_eq!(line_len, line.len() as u32); + } + + let longest_row = snapshot.longest_row(); + let longest_char_column = expected_text + .split('\n') + .nth(longest_row as usize) + .unwrap() + .chars() + .count(); + let mut fold_point = FoldPoint::new(0, 0); + let mut fold_offset = FoldOffset(0); + let mut char_column = 0; + for c in expected_text.chars() { + let inlay_point = fold_point.to_inlay_point(&snapshot); + let inlay_offset = fold_offset.to_inlay_offset(&snapshot); + assert_eq!( + snapshot.to_fold_point(inlay_point, Right), + fold_point, + "{:?} -> fold point", + inlay_point, + ); + assert_eq!( + inlay_snapshot.to_offset(inlay_point), + inlay_offset, + "inlay_snapshot.to_offset({:?})", + inlay_point, + ); + assert_eq!( + fold_point.to_offset(&snapshot), + fold_offset, + "fold_point.to_offset({:?})", + fold_point, + ); + + if c == '\n' { + *fold_point.row_mut() += 1; + *fold_point.column_mut() = 0; + char_column = 0; + } else { + *fold_point.column_mut() += c.len_utf8() as u32; + char_column += 1; + } + fold_offset.0 += c.len_utf8(); + if char_column > longest_char_column { + panic!( + "invalid longest row {:?} (chars {}), found row {:?} (chars: {})", + longest_row, + longest_char_column, + fold_point.row(), + char_column + ); + } + } + + for _ in 0..5 { + let mut start = snapshot + .clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Left); + let mut end = snapshot + .clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Right); + if start > end { + mem::swap(&mut start, &mut end); + } + + let text = &expected_text[start.0..end.0]; + assert_eq!( + snapshot + .chunks(start..end, false, Highlights::default()) + .map(|c| c.text) + .collect::(), + text, + ); + } + + let mut fold_row = 0; + while fold_row < expected_buffer_rows.len() as u32 { + assert_eq!( + snapshot.buffer_rows(fold_row).collect::>(), + expected_buffer_rows[(fold_row as usize)..], + "wrong buffer rows starting at fold row {}", + fold_row, + ); + fold_row += 1; + } + + let folded_buffer_rows = map + .merged_fold_ranges() + .iter() + .flat_map(|range| { + let start_row = range.start.to_point(&buffer_snapshot).row; + let end = range.end.to_point(&buffer_snapshot); + if end.column == 0 { + start_row..end.row + } else { + start_row..end.row + 1 + } + }) + .collect::>(); + for row in 0..=buffer_snapshot.max_point().row { + assert_eq!( + snapshot.is_line_folded(row), + folded_buffer_rows.contains(&row), + "expected buffer row {}{} to be folded", + row, + if folded_buffer_rows.contains(&row) { + "" + } else { + " not" + } + ); + } + + for _ in 0..5 { + let end = + buffer_snapshot.clip_offset(rng.gen_range(0..=buffer_snapshot.len()), Right); + let start = buffer_snapshot.clip_offset(rng.gen_range(0..=end), Left); + let expected_folds = map + .snapshot + .folds + .items(&buffer_snapshot) + .into_iter() + .filter(|fold| { + let start = buffer_snapshot.anchor_before(start); + let end = buffer_snapshot.anchor_after(end); + start.cmp(&fold.0.end, &buffer_snapshot) == Ordering::Less + && end.cmp(&fold.0.start, &buffer_snapshot) == Ordering::Greater + }) + .map(|fold| fold.0) + .collect::>(); + + assert_eq!( + snapshot + .folds_in_range(start..end) + .cloned() + .collect::>(), + expected_folds + ); + } + + let text = snapshot.text(); + for _ in 0..5 { + let start_row = rng.gen_range(0..=snapshot.max_point().row()); + let start_column = rng.gen_range(0..=snapshot.line_len(start_row)); + let end_row = rng.gen_range(0..=snapshot.max_point().row()); + let end_column = rng.gen_range(0..=snapshot.line_len(end_row)); + let mut start = + snapshot.clip_point(FoldPoint::new(start_row, start_column), Bias::Left); + let mut end = snapshot.clip_point(FoldPoint::new(end_row, end_column), Bias::Right); + if start > end { + mem::swap(&mut start, &mut end); + } + + let lines = start..end; + let bytes = start.to_offset(&snapshot)..end.to_offset(&snapshot); + assert_eq!( + snapshot.text_summary_for_range(lines), + TextSummary::from(&text[bytes.start.0..bytes.end.0]) + ) + } + + let mut text = initial_snapshot.text(); + for (snapshot, edits) in snapshot_edits.drain(..) { + let new_text = snapshot.text(); + for edit in edits { + let old_bytes = edit.new.start.0..edit.new.start.0 + edit.old_len().0; + let new_bytes = edit.new.start.0..edit.new.end.0; + text.replace_range(old_bytes, &new_text[new_bytes]); + } + + assert_eq!(text, new_text); + initial_snapshot = snapshot; + } + } + } + + #[gpui::test] + fn test_buffer_rows(cx: &mut gpui::AppContext) { + let text = sample_text(6, 6, 'a') + "\n"; + let buffer = MultiBuffer::build_simple(&text, cx); + + let buffer_snapshot = buffer.read(cx).snapshot(cx); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; + + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + writer.fold(vec![ + Point::new(0, 2)..Point::new(2, 2), + Point::new(3, 1)..Point::new(4, 1), + ]); + + let (snapshot, _) = map.read(inlay_snapshot, vec![]); + assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n"); + assert_eq!( + snapshot.buffer_rows(0).collect::>(), + [Some(0), Some(3), Some(5), Some(6)] + ); + assert_eq!(snapshot.buffer_rows(3).collect::>(), [Some(6)]); + } + + fn init_test(cx: &mut gpui::AppContext) { + let store = SettingsStore::test(cx); + cx.set_global(store); + } + + impl FoldMap { + fn merged_fold_ranges(&self) -> Vec> { + let inlay_snapshot = self.snapshot.inlay_snapshot.clone(); + let buffer = &inlay_snapshot.buffer; + let mut folds = self.snapshot.folds.items(buffer); + // Ensure sorting doesn't change how folds get merged and displayed. + folds.sort_by(|a, b| a.0.cmp(&b.0, buffer)); + let mut fold_ranges = folds + .iter() + .map(|fold| fold.0.start.to_offset(buffer)..fold.0.end.to_offset(buffer)) + .peekable(); + + let mut merged_ranges = Vec::new(); + while let Some(mut fold_range) = fold_ranges.next() { + while let Some(next_range) = fold_ranges.peek() { + if fold_range.end >= next_range.start { + if next_range.end > fold_range.end { + fold_range.end = next_range.end; + } + fold_ranges.next(); + } else { + break; + } + } + if fold_range.end > fold_range.start { + merged_ranges.push(fold_range); + } + } + merged_ranges + } + + pub fn randomly_mutate( + &mut self, + rng: &mut impl Rng, + ) -> Vec<(FoldSnapshot, Vec)> { + let mut snapshot_edits = Vec::new(); + match rng.gen_range(0..=100) { + 0..=39 if !self.snapshot.folds.is_empty() => { + let inlay_snapshot = self.snapshot.inlay_snapshot.clone(); + let buffer = &inlay_snapshot.buffer; + let mut to_unfold = Vec::new(); + for _ in 0..rng.gen_range(1..=3) { + let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right); + let start = buffer.clip_offset(rng.gen_range(0..=end), Left); + to_unfold.push(start..end); + } + log::info!("unfolding {:?}", to_unfold); + let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]); + snapshot_edits.push((snapshot, edits)); + let (snapshot, edits) = writer.fold(to_unfold); + snapshot_edits.push((snapshot, edits)); + } + _ => { + let inlay_snapshot = self.snapshot.inlay_snapshot.clone(); + let buffer = &inlay_snapshot.buffer; + let mut to_fold = Vec::new(); + for _ in 0..rng.gen_range(1..=2) { + let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right); + let start = buffer.clip_offset(rng.gen_range(0..=end), Left); + to_fold.push(start..end); + } + log::info!("folding {:?}", to_fold); + let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]); + snapshot_edits.push((snapshot, edits)); + let (snapshot, edits) = writer.fold(to_fold); + snapshot_edits.push((snapshot, edits)); + } + } + snapshot_edits + } + } +} diff --git a/crates/editor2/src/display_map/inlay_map.rs b/crates/editor2/src/display_map/inlay_map.rs new file mode 100644 index 0000000000..a6bc6343f4 --- /dev/null +++ b/crates/editor2/src/display_map/inlay_map.rs @@ -0,0 +1,1896 @@ +use crate::{Anchor, InlayId, MultiBufferSnapshot, ToOffset}; +use collections::{BTreeMap, BTreeSet}; +use gpui::HighlightStyle; +use language::{Chunk, Edit, Point, TextSummary}; +use multi_buffer::{MultiBufferChunks, MultiBufferRows}; +use std::{ + any::TypeId, + cmp, + iter::Peekable, + ops::{Add, AddAssign, Range, Sub, SubAssign}, + sync::Arc, + vec, +}; +use sum_tree::{Bias, Cursor, SumTree, TreeMap}; +use text::{Patch, Rope}; + +use super::Highlights; + +pub struct InlayMap { + snapshot: InlaySnapshot, + inlays: Vec, +} + +#[derive(Clone)] +pub struct InlaySnapshot { + pub buffer: MultiBufferSnapshot, + transforms: SumTree, + pub version: usize, +} + +#[derive(Clone, Debug)] +enum Transform { + Isomorphic(TextSummary), + Inlay(Inlay), +} + +#[derive(Debug, Clone)] +pub struct Inlay { + pub id: InlayId, + pub position: Anchor, + pub text: text::Rope, +} + +impl Inlay { + pub fn hint(id: usize, position: Anchor, hint: &project::InlayHint) -> Self { + let mut text = hint.text(); + if hint.padding_right && !text.ends_with(' ') { + text.push(' '); + } + if hint.padding_left && !text.starts_with(' ') { + text.insert(0, ' '); + } + Self { + id: InlayId::Hint(id), + position, + text: text.into(), + } + } + + pub fn suggestion>(id: usize, position: Anchor, text: T) -> Self { + Self { + id: InlayId::Suggestion(id), + position, + text: text.into(), + } + } +} + +impl sum_tree::Item for Transform { + type Summary = TransformSummary; + + fn summary(&self) -> Self::Summary { + match self { + Transform::Isomorphic(summary) => TransformSummary { + input: summary.clone(), + output: summary.clone(), + }, + Transform::Inlay(inlay) => TransformSummary { + input: TextSummary::default(), + output: inlay.text.summary(), + }, + } + } +} + +#[derive(Clone, Debug, Default)] +struct TransformSummary { + input: TextSummary, + output: TextSummary, +} + +impl sum_tree::Summary for TransformSummary { + type Context = (); + + fn add_summary(&mut self, other: &Self, _: &()) { + self.input += &other.input; + self.output += &other.output; + } +} + +pub type InlayEdit = Edit; + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct InlayOffset(pub usize); + +impl Add for InlayOffset { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Sub for InlayOffset { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl AddAssign for InlayOffset { + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + } +} + +impl SubAssign for InlayOffset { + fn sub_assign(&mut self, rhs: Self) { + self.0 -= rhs.0; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += &summary.output.len; + } +} + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct InlayPoint(pub Point); + +impl Add for InlayPoint { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Sub for InlayPoint { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += &summary.output.lines; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + *self += &summary.input.len; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + *self += &summary.input.lines; + } +} + +#[derive(Clone)] +pub struct InlayBufferRows<'a> { + transforms: Cursor<'a, Transform, (InlayPoint, Point)>, + buffer_rows: MultiBufferRows<'a>, + inlay_row: u32, + max_buffer_row: u32, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +struct HighlightEndpoint { + offset: InlayOffset, + is_start: bool, + tag: Option, + style: HighlightStyle, +} + +impl PartialOrd for HighlightEndpoint { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for HighlightEndpoint { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.offset + .cmp(&other.offset) + .then_with(|| other.is_start.cmp(&self.is_start)) + } +} + +pub struct InlayChunks<'a> { + transforms: Cursor<'a, Transform, (InlayOffset, usize)>, + buffer_chunks: MultiBufferChunks<'a>, + buffer_chunk: Option>, + inlay_chunks: Option>, + inlay_chunk: Option<&'a str>, + output_offset: InlayOffset, + max_output_offset: InlayOffset, + inlay_highlight_style: Option, + suggestion_highlight_style: Option, + highlight_endpoints: Peekable>, + active_highlights: BTreeMap, HighlightStyle>, + highlights: Highlights<'a>, + snapshot: &'a InlaySnapshot, +} + +impl<'a> InlayChunks<'a> { + pub fn seek(&mut self, offset: InlayOffset) { + self.transforms.seek(&offset, Bias::Right, &()); + + let buffer_offset = self.snapshot.to_buffer_offset(offset); + self.buffer_chunks.seek(buffer_offset); + self.inlay_chunks = None; + self.buffer_chunk = None; + self.output_offset = offset; + } + + pub fn offset(&self) -> InlayOffset { + self.output_offset + } +} + +impl<'a> Iterator for InlayChunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + if self.output_offset == self.max_output_offset { + return None; + } + + let mut next_highlight_endpoint = InlayOffset(usize::MAX); + while let Some(endpoint) = self.highlight_endpoints.peek().copied() { + if endpoint.offset <= self.output_offset { + if endpoint.is_start { + self.active_highlights.insert(endpoint.tag, endpoint.style); + } else { + self.active_highlights.remove(&endpoint.tag); + } + self.highlight_endpoints.next(); + } else { + next_highlight_endpoint = endpoint.offset; + break; + } + } + + let chunk = match self.transforms.item()? { + Transform::Isomorphic(_) => { + let chunk = self + .buffer_chunk + .get_or_insert_with(|| self.buffer_chunks.next().unwrap()); + if chunk.text.is_empty() { + *chunk = self.buffer_chunks.next().unwrap(); + } + + let (prefix, suffix) = chunk.text.split_at( + chunk + .text + .len() + .min(self.transforms.end(&()).0 .0 - self.output_offset.0) + .min(next_highlight_endpoint.0 - self.output_offset.0), + ); + + chunk.text = suffix; + self.output_offset.0 += prefix.len(); + let mut prefix = Chunk { + text: prefix, + ..chunk.clone() + }; + if !self.active_highlights.is_empty() { + let mut highlight_style = HighlightStyle::default(); + for active_highlight in self.active_highlights.values() { + highlight_style.highlight(*active_highlight); + } + prefix.highlight_style = Some(highlight_style); + } + prefix + } + Transform::Inlay(inlay) => { + let mut inlay_style_and_highlight = None; + if let Some(inlay_highlights) = self.highlights.inlay_highlights { + for (_, inlay_id_to_data) in inlay_highlights.iter() { + let style_and_highlight = inlay_id_to_data.get(&inlay.id); + if style_and_highlight.is_some() { + inlay_style_and_highlight = style_and_highlight; + break; + } + } + } + + let mut highlight_style = match inlay.id { + InlayId::Suggestion(_) => self.suggestion_highlight_style, + InlayId::Hint(_) => self.inlay_highlight_style, + }; + let next_inlay_highlight_endpoint; + let offset_in_inlay = self.output_offset - self.transforms.start().0; + if let Some((style, highlight)) = inlay_style_and_highlight { + let range = &highlight.range; + if offset_in_inlay.0 < range.start { + next_inlay_highlight_endpoint = range.start - offset_in_inlay.0; + } else if offset_in_inlay.0 >= range.end { + next_inlay_highlight_endpoint = usize::MAX; + } else { + next_inlay_highlight_endpoint = range.end - offset_in_inlay.0; + highlight_style + .get_or_insert_with(|| Default::default()) + .highlight(style.clone()); + } + } else { + next_inlay_highlight_endpoint = usize::MAX; + } + + let inlay_chunks = self.inlay_chunks.get_or_insert_with(|| { + let start = offset_in_inlay; + let end = cmp::min(self.max_output_offset, self.transforms.end(&()).0) + - self.transforms.start().0; + inlay.text.chunks_in_range(start.0..end.0) + }); + let inlay_chunk = self + .inlay_chunk + .get_or_insert_with(|| inlay_chunks.next().unwrap()); + let (chunk, remainder) = + inlay_chunk.split_at(inlay_chunk.len().min(next_inlay_highlight_endpoint)); + *inlay_chunk = remainder; + if inlay_chunk.is_empty() { + self.inlay_chunk = None; + } + + self.output_offset.0 += chunk.len(); + + if !self.active_highlights.is_empty() { + for active_highlight in self.active_highlights.values() { + highlight_style + .get_or_insert(Default::default()) + .highlight(*active_highlight); + } + } + Chunk { + text: chunk, + highlight_style, + ..Default::default() + } + } + }; + + if self.output_offset == self.transforms.end(&()).0 { + self.inlay_chunks = None; + self.transforms.next(&()); + } + + Some(chunk) + } +} + +impl<'a> InlayBufferRows<'a> { + pub fn seek(&mut self, row: u32) { + let inlay_point = InlayPoint::new(row, 0); + self.transforms.seek(&inlay_point, Bias::Left, &()); + + let mut buffer_point = self.transforms.start().1; + let buffer_row = if row == 0 { + 0 + } else { + match self.transforms.item() { + Some(Transform::Isomorphic(_)) => { + buffer_point += inlay_point.0 - self.transforms.start().0 .0; + buffer_point.row + } + _ => cmp::min(buffer_point.row + 1, self.max_buffer_row), + } + }; + self.inlay_row = inlay_point.row(); + self.buffer_rows.seek(buffer_row); + } +} + +impl<'a> Iterator for InlayBufferRows<'a> { + type Item = Option; + + fn next(&mut self) -> Option { + let buffer_row = if self.inlay_row == 0 { + self.buffer_rows.next().unwrap() + } else { + match self.transforms.item()? { + Transform::Inlay(_) => None, + Transform::Isomorphic(_) => self.buffer_rows.next().unwrap(), + } + }; + + self.inlay_row += 1; + self.transforms + .seek_forward(&InlayPoint::new(self.inlay_row, 0), Bias::Left, &()); + + Some(buffer_row) + } +} + +impl InlayPoint { + pub fn new(row: u32, column: u32) -> Self { + Self(Point::new(row, column)) + } + + pub fn row(self) -> u32 { + self.0.row + } +} + +impl InlayMap { + pub fn new(buffer: MultiBufferSnapshot) -> (Self, InlaySnapshot) { + let version = 0; + let snapshot = InlaySnapshot { + buffer: buffer.clone(), + transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), &()), + version, + }; + + ( + Self { + snapshot: snapshot.clone(), + inlays: Vec::new(), + }, + snapshot, + ) + } + + pub fn sync( + &mut self, + buffer_snapshot: MultiBufferSnapshot, + mut buffer_edits: Vec>, + ) -> (InlaySnapshot, Vec) { + let snapshot = &mut self.snapshot; + + if buffer_edits.is_empty() { + if snapshot.buffer.trailing_excerpt_update_count() + != buffer_snapshot.trailing_excerpt_update_count() + { + buffer_edits.push(Edit { + old: snapshot.buffer.len()..snapshot.buffer.len(), + new: buffer_snapshot.len()..buffer_snapshot.len(), + }); + } + } + + if buffer_edits.is_empty() { + if snapshot.buffer.edit_count() != buffer_snapshot.edit_count() + || snapshot.buffer.parse_count() != buffer_snapshot.parse_count() + || snapshot.buffer.diagnostics_update_count() + != buffer_snapshot.diagnostics_update_count() + || snapshot.buffer.git_diff_update_count() + != buffer_snapshot.git_diff_update_count() + || snapshot.buffer.trailing_excerpt_update_count() + != buffer_snapshot.trailing_excerpt_update_count() + { + snapshot.version += 1; + } + + snapshot.buffer = buffer_snapshot; + (snapshot.clone(), Vec::new()) + } else { + let mut inlay_edits = Patch::default(); + let mut new_transforms = SumTree::new(); + let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>(); + let mut buffer_edits_iter = buffer_edits.iter().peekable(); + while let Some(buffer_edit) = buffer_edits_iter.next() { + new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &()); + if let Some(Transform::Isomorphic(transform)) = cursor.item() { + if cursor.end(&()).0 == buffer_edit.old.start { + push_isomorphic(&mut new_transforms, transform.clone()); + cursor.next(&()); + } + } + + // Remove all the inlays and transforms contained by the edit. + let old_start = + cursor.start().1 + InlayOffset(buffer_edit.old.start - cursor.start().0); + cursor.seek(&buffer_edit.old.end, Bias::Right, &()); + let old_end = + cursor.start().1 + InlayOffset(buffer_edit.old.end - cursor.start().0); + + // Push the unchanged prefix. + let prefix_start = new_transforms.summary().input.len; + let prefix_end = buffer_edit.new.start; + push_isomorphic( + &mut new_transforms, + buffer_snapshot.text_summary_for_range(prefix_start..prefix_end), + ); + let new_start = InlayOffset(new_transforms.summary().output.len); + + let start_ix = match self.inlays.binary_search_by(|probe| { + probe + .position + .to_offset(&buffer_snapshot) + .cmp(&buffer_edit.new.start) + .then(std::cmp::Ordering::Greater) + }) { + Ok(ix) | Err(ix) => ix, + }; + + for inlay in &self.inlays[start_ix..] { + let buffer_offset = inlay.position.to_offset(&buffer_snapshot); + if buffer_offset > buffer_edit.new.end { + break; + } + + let prefix_start = new_transforms.summary().input.len; + let prefix_end = buffer_offset; + push_isomorphic( + &mut new_transforms, + buffer_snapshot.text_summary_for_range(prefix_start..prefix_end), + ); + + if inlay.position.is_valid(&buffer_snapshot) { + new_transforms.push(Transform::Inlay(inlay.clone()), &()); + } + } + + // Apply the rest of the edit. + let transform_start = new_transforms.summary().input.len; + push_isomorphic( + &mut new_transforms, + buffer_snapshot.text_summary_for_range(transform_start..buffer_edit.new.end), + ); + let new_end = InlayOffset(new_transforms.summary().output.len); + inlay_edits.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + // If the next edit doesn't intersect the current isomorphic transform, then + // we can push its remainder. + if buffer_edits_iter + .peek() + .map_or(true, |edit| edit.old.start >= cursor.end(&()).0) + { + let transform_start = new_transforms.summary().input.len; + let transform_end = + buffer_edit.new.end + (cursor.end(&()).0 - buffer_edit.old.end); + push_isomorphic( + &mut new_transforms, + buffer_snapshot.text_summary_for_range(transform_start..transform_end), + ); + cursor.next(&()); + } + } + + new_transforms.append(cursor.suffix(&()), &()); + if new_transforms.is_empty() { + new_transforms.push(Transform::Isomorphic(Default::default()), &()); + } + + drop(cursor); + snapshot.transforms = new_transforms; + snapshot.version += 1; + snapshot.buffer = buffer_snapshot; + snapshot.check_invariants(); + + (snapshot.clone(), inlay_edits.into_inner()) + } + } + + pub fn splice( + &mut self, + to_remove: Vec, + to_insert: Vec, + ) -> (InlaySnapshot, Vec) { + let snapshot = &mut self.snapshot; + let mut edits = BTreeSet::new(); + + self.inlays.retain(|inlay| { + let retain = !to_remove.contains(&inlay.id); + if !retain { + let offset = inlay.position.to_offset(&snapshot.buffer); + edits.insert(offset); + } + retain + }); + + for inlay_to_insert in to_insert { + // Avoid inserting empty inlays. + if inlay_to_insert.text.is_empty() { + continue; + } + + let offset = inlay_to_insert.position.to_offset(&snapshot.buffer); + match self.inlays.binary_search_by(|probe| { + probe + .position + .cmp(&inlay_to_insert.position, &snapshot.buffer) + }) { + Ok(ix) | Err(ix) => { + self.inlays.insert(ix, inlay_to_insert); + } + } + + edits.insert(offset); + } + + let buffer_edits = edits + .into_iter() + .map(|offset| Edit { + old: offset..offset, + new: offset..offset, + }) + .collect(); + let buffer_snapshot = snapshot.buffer.clone(); + let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits); + (snapshot, edits) + } + + pub fn current_inlays(&self) -> impl Iterator { + self.inlays.iter() + } + + #[cfg(test)] + pub(crate) fn randomly_mutate( + &mut self, + next_inlay_id: &mut usize, + rng: &mut rand::rngs::StdRng, + ) -> (InlaySnapshot, Vec) { + use rand::prelude::*; + use util::post_inc; + + let mut to_remove = Vec::new(); + let mut to_insert = Vec::new(); + let snapshot = &mut self.snapshot; + for i in 0..rng.gen_range(1..=5) { + if self.inlays.is_empty() || rng.gen() { + let position = snapshot.buffer.random_byte_range(0, rng).start; + let bias = if rng.gen() { Bias::Left } else { Bias::Right }; + let len = if rng.gen_bool(0.01) { + 0 + } else { + rng.gen_range(1..=5) + }; + let text = util::RandomCharIter::new(&mut *rng) + .filter(|ch| *ch != '\r') + .take(len) + .collect::(); + + let inlay_id = if i % 2 == 0 { + InlayId::Hint(post_inc(next_inlay_id)) + } else { + InlayId::Suggestion(post_inc(next_inlay_id)) + }; + log::info!( + "creating inlay {:?} at buffer offset {} with bias {:?} and text {:?}", + inlay_id, + position, + bias, + text + ); + + to_insert.push(Inlay { + id: inlay_id, + position: snapshot.buffer.anchor_at(position, bias), + text: text.into(), + }); + } else { + to_remove.push( + self.inlays + .iter() + .choose(rng) + .map(|inlay| inlay.id) + .unwrap(), + ); + } + } + log::info!("removing inlays: {:?}", to_remove); + + let (snapshot, edits) = self.splice(to_remove, to_insert); + (snapshot, edits) + } +} + +impl InlaySnapshot { + pub fn to_point(&self, offset: InlayOffset) -> InlayPoint { + let mut cursor = self + .transforms + .cursor::<(InlayOffset, (InlayPoint, usize))>(); + cursor.seek(&offset, Bias::Right, &()); + let overshoot = offset.0 - cursor.start().0 .0; + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + let buffer_offset_start = cursor.start().1 .1; + let buffer_offset_end = buffer_offset_start + overshoot; + let buffer_start = self.buffer.offset_to_point(buffer_offset_start); + let buffer_end = self.buffer.offset_to_point(buffer_offset_end); + InlayPoint(cursor.start().1 .0 .0 + (buffer_end - buffer_start)) + } + Some(Transform::Inlay(inlay)) => { + let overshoot = inlay.text.offset_to_point(overshoot); + InlayPoint(cursor.start().1 .0 .0 + overshoot) + } + None => self.max_point(), + } + } + + pub fn len(&self) -> InlayOffset { + InlayOffset(self.transforms.summary().output.len) + } + + pub fn max_point(&self) -> InlayPoint { + InlayPoint(self.transforms.summary().output.lines) + } + + pub fn to_offset(&self, point: InlayPoint) -> InlayOffset { + let mut cursor = self + .transforms + .cursor::<(InlayPoint, (InlayOffset, Point))>(); + cursor.seek(&point, Bias::Right, &()); + let overshoot = point.0 - cursor.start().0 .0; + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + let buffer_point_start = cursor.start().1 .1; + let buffer_point_end = buffer_point_start + overshoot; + let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start); + let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end); + InlayOffset(cursor.start().1 .0 .0 + (buffer_offset_end - buffer_offset_start)) + } + Some(Transform::Inlay(inlay)) => { + let overshoot = inlay.text.point_to_offset(overshoot); + InlayOffset(cursor.start().1 .0 .0 + overshoot) + } + None => self.len(), + } + } + + pub fn to_buffer_point(&self, point: InlayPoint) -> Point { + let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(); + cursor.seek(&point, Bias::Right, &()); + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + let overshoot = point.0 - cursor.start().0 .0; + cursor.start().1 + overshoot + } + Some(Transform::Inlay(_)) => cursor.start().1, + None => self.buffer.max_point(), + } + } + + pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize { + let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(); + cursor.seek(&offset, Bias::Right, &()); + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + let overshoot = offset - cursor.start().0; + cursor.start().1 + overshoot.0 + } + Some(Transform::Inlay(_)) => cursor.start().1, + None => self.buffer.len(), + } + } + + pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset { + let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>(); + cursor.seek(&offset, Bias::Left, &()); + loop { + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + if offset == cursor.end(&()).0 { + while let Some(Transform::Inlay(inlay)) = cursor.next_item() { + if inlay.position.bias() == Bias::Right { + break; + } else { + cursor.next(&()); + } + } + return cursor.end(&()).1; + } else { + let overshoot = offset - cursor.start().0; + return InlayOffset(cursor.start().1 .0 + overshoot); + } + } + Some(Transform::Inlay(inlay)) => { + if inlay.position.bias() == Bias::Left { + cursor.next(&()); + } else { + return cursor.start().1; + } + } + None => { + return self.len(); + } + } + } + } + + pub fn to_inlay_point(&self, point: Point) -> InlayPoint { + let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>(); + cursor.seek(&point, Bias::Left, &()); + loop { + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + if point == cursor.end(&()).0 { + while let Some(Transform::Inlay(inlay)) = cursor.next_item() { + if inlay.position.bias() == Bias::Right { + break; + } else { + cursor.next(&()); + } + } + return cursor.end(&()).1; + } else { + let overshoot = point - cursor.start().0; + return InlayPoint(cursor.start().1 .0 + overshoot); + } + } + Some(Transform::Inlay(inlay)) => { + if inlay.position.bias() == Bias::Left { + cursor.next(&()); + } else { + return cursor.start().1; + } + } + None => { + return self.max_point(); + } + } + } + } + + pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint { + let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(); + cursor.seek(&point, Bias::Left, &()); + loop { + match cursor.item() { + Some(Transform::Isomorphic(transform)) => { + if cursor.start().0 == point { + if let Some(Transform::Inlay(inlay)) = cursor.prev_item() { + if inlay.position.bias() == Bias::Left { + return point; + } else if bias == Bias::Left { + cursor.prev(&()); + } else if transform.first_line_chars == 0 { + point.0 += Point::new(1, 0); + } else { + point.0 += Point::new(0, 1); + } + } else { + return point; + } + } else if cursor.end(&()).0 == point { + if let Some(Transform::Inlay(inlay)) = cursor.next_item() { + if inlay.position.bias() == Bias::Right { + return point; + } else if bias == Bias::Right { + cursor.next(&()); + } else if point.0.column == 0 { + point.0.row -= 1; + point.0.column = self.line_len(point.0.row); + } else { + point.0.column -= 1; + } + } else { + return point; + } + } else { + let overshoot = point.0 - cursor.start().0 .0; + let buffer_point = cursor.start().1 + overshoot; + let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias); + let clipped_overshoot = clipped_buffer_point - cursor.start().1; + let clipped_point = InlayPoint(cursor.start().0 .0 + clipped_overshoot); + if clipped_point == point { + return clipped_point; + } else { + point = clipped_point; + } + } + } + Some(Transform::Inlay(inlay)) => { + if point == cursor.start().0 && inlay.position.bias() == Bias::Right { + match cursor.prev_item() { + Some(Transform::Inlay(inlay)) => { + if inlay.position.bias() == Bias::Left { + return point; + } + } + _ => return point, + } + } else if point == cursor.end(&()).0 && inlay.position.bias() == Bias::Left { + match cursor.next_item() { + Some(Transform::Inlay(inlay)) => { + if inlay.position.bias() == Bias::Right { + return point; + } + } + _ => return point, + } + } + + if bias == Bias::Left { + point = cursor.start().0; + cursor.prev(&()); + } else { + cursor.next(&()); + point = cursor.start().0; + } + } + None => { + bias = bias.invert(); + if bias == Bias::Left { + point = cursor.start().0; + cursor.prev(&()); + } else { + cursor.next(&()); + point = cursor.start().0; + } + } + } + } + } + + pub fn text_summary(&self) -> TextSummary { + self.transforms.summary().output.clone() + } + + pub fn text_summary_for_range(&self, range: Range) -> TextSummary { + let mut summary = TextSummary::default(); + + let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(); + cursor.seek(&range.start, Bias::Right, &()); + + let overshoot = range.start.0 - cursor.start().0 .0; + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + let buffer_start = cursor.start().1; + let suffix_start = buffer_start + overshoot; + let suffix_end = + buffer_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0); + summary = self.buffer.text_summary_for_range(suffix_start..suffix_end); + cursor.next(&()); + } + Some(Transform::Inlay(inlay)) => { + let suffix_start = overshoot; + let suffix_end = cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0; + summary = inlay.text.cursor(suffix_start).summary(suffix_end); + cursor.next(&()); + } + None => {} + } + + if range.end > cursor.start().0 { + summary += cursor + .summary::<_, TransformSummary>(&range.end, Bias::Right, &()) + .output; + + let overshoot = range.end.0 - cursor.start().0 .0; + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + let prefix_start = cursor.start().1; + let prefix_end = prefix_start + overshoot; + summary += self + .buffer + .text_summary_for_range::(prefix_start..prefix_end); + } + Some(Transform::Inlay(inlay)) => { + let prefix_end = overshoot; + summary += inlay.text.cursor(0).summary::(prefix_end); + } + None => {} + } + } + + summary + } + + pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> { + let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(); + let inlay_point = InlayPoint::new(row, 0); + cursor.seek(&inlay_point, Bias::Left, &()); + + let max_buffer_row = self.buffer.max_point().row; + let mut buffer_point = cursor.start().1; + let buffer_row = if row == 0 { + 0 + } else { + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + buffer_point += inlay_point.0 - cursor.start().0 .0; + buffer_point.row + } + _ => cmp::min(buffer_point.row + 1, max_buffer_row), + } + }; + + InlayBufferRows { + transforms: cursor, + inlay_row: inlay_point.row(), + buffer_rows: self.buffer.buffer_rows(buffer_row), + max_buffer_row, + } + } + + pub fn line_len(&self, row: u32) -> u32 { + let line_start = self.to_offset(InlayPoint::new(row, 0)).0; + let line_end = if row >= self.max_point().row() { + self.len().0 + } else { + self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1 + }; + (line_end - line_start) as u32 + } + + pub fn chunks<'a>( + &'a self, + range: Range, + language_aware: bool, + highlights: Highlights<'a>, + ) -> InlayChunks<'a> { + let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(); + cursor.seek(&range.start, Bias::Right, &()); + + let mut highlight_endpoints = Vec::new(); + if let Some(text_highlights) = highlights.text_highlights { + if !text_highlights.is_empty() { + self.apply_text_highlights( + &mut cursor, + &range, + text_highlights, + &mut highlight_endpoints, + ); + cursor.seek(&range.start, Bias::Right, &()); + } + } + highlight_endpoints.sort(); + let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end); + let buffer_chunks = self.buffer.chunks(buffer_range, language_aware); + + InlayChunks { + transforms: cursor, + buffer_chunks, + inlay_chunks: None, + inlay_chunk: None, + buffer_chunk: None, + output_offset: range.start, + max_output_offset: range.end, + inlay_highlight_style: highlights.inlay_highlight_style, + suggestion_highlight_style: highlights.suggestion_highlight_style, + highlight_endpoints: highlight_endpoints.into_iter().peekable(), + active_highlights: Default::default(), + highlights, + snapshot: self, + } + } + + fn apply_text_highlights( + &self, + cursor: &mut Cursor<'_, Transform, (InlayOffset, usize)>, + range: &Range, + text_highlights: &TreeMap, Arc<(HighlightStyle, Vec>)>>, + highlight_endpoints: &mut Vec, + ) { + while cursor.start().0 < range.end { + let transform_start = self + .buffer + .anchor_after(self.to_buffer_offset(cmp::max(range.start, cursor.start().0))); + let transform_end = + { + let overshoot = InlayOffset(range.end.0 - cursor.start().0 .0); + self.buffer.anchor_before(self.to_buffer_offset(cmp::min( + cursor.end(&()).0, + cursor.start().0 + overshoot, + ))) + }; + + for (tag, text_highlights) in text_highlights.iter() { + let style = text_highlights.0; + let ranges = &text_highlights.1; + + let start_ix = match ranges.binary_search_by(|probe| { + let cmp = probe.end.cmp(&transform_start, &self.buffer); + if cmp.is_gt() { + cmp::Ordering::Greater + } else { + cmp::Ordering::Less + } + }) { + Ok(i) | Err(i) => i, + }; + for range in &ranges[start_ix..] { + if range.start.cmp(&transform_end, &self.buffer).is_ge() { + break; + } + + highlight_endpoints.push(HighlightEndpoint { + offset: self.to_inlay_offset(range.start.to_offset(&self.buffer)), + is_start: true, + tag: *tag, + style, + }); + highlight_endpoints.push(HighlightEndpoint { + offset: self.to_inlay_offset(range.end.to_offset(&self.buffer)), + is_start: false, + tag: *tag, + style, + }); + } + } + + cursor.next(&()); + } + } + + #[cfg(test)] + pub fn text(&self) -> String { + self.chunks(Default::default()..self.len(), false, Highlights::default()) + .map(|chunk| chunk.text) + .collect() + } + + fn check_invariants(&self) { + #[cfg(any(debug_assertions, feature = "test-support"))] + { + assert_eq!(self.transforms.summary().input, self.buffer.text_summary()); + let mut transforms = self.transforms.iter().peekable(); + while let Some(transform) = transforms.next() { + let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_)); + if let Some(next_transform) = transforms.peek() { + let next_transform_is_isomorphic = + matches!(next_transform, Transform::Isomorphic(_)); + assert!( + !transform_is_isomorphic || !next_transform_is_isomorphic, + "two adjacent isomorphic transforms" + ); + } + } + } + } +} + +fn push_isomorphic(sum_tree: &mut SumTree, summary: TextSummary) { + if summary.len == 0 { + return; + } + + let mut summary = Some(summary); + sum_tree.update_last( + |transform| { + if let Transform::Isomorphic(transform) = transform { + *transform += summary.take().unwrap(); + } + }, + &(), + ); + + if let Some(summary) = summary { + sum_tree.push(Transform::Isomorphic(summary), &()); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + display_map::{InlayHighlights, TextHighlights}, + link_go_to_definition::InlayHighlight, + InlayId, MultiBuffer, + }; + use gpui::AppContext; + use project::{InlayHint, InlayHintLabel, ResolveState}; + use rand::prelude::*; + use settings::SettingsStore; + use std::{cmp::Reverse, env, sync::Arc}; + use text::Patch; + use util::post_inc; + + #[test] + fn test_inlay_properties_label_padding() { + assert_eq!( + Inlay::hint( + 0, + Anchor::min(), + &InlayHint { + label: InlayHintLabel::String("a".to_string()), + position: text::Anchor::default(), + padding_left: false, + padding_right: false, + tooltip: None, + kind: None, + resolve_state: ResolveState::Resolved, + }, + ) + .text + .to_string(), + "a", + "Should not pad label if not requested" + ); + + assert_eq!( + Inlay::hint( + 0, + Anchor::min(), + &InlayHint { + label: InlayHintLabel::String("a".to_string()), + position: text::Anchor::default(), + padding_left: true, + padding_right: true, + tooltip: None, + kind: None, + resolve_state: ResolveState::Resolved, + }, + ) + .text + .to_string(), + " a ", + "Should pad label for every side requested" + ); + + assert_eq!( + Inlay::hint( + 0, + Anchor::min(), + &InlayHint { + label: InlayHintLabel::String(" a ".to_string()), + position: text::Anchor::default(), + padding_left: false, + padding_right: false, + tooltip: None, + kind: None, + resolve_state: ResolveState::Resolved, + }, + ) + .text + .to_string(), + " a ", + "Should not change already padded label" + ); + + assert_eq!( + Inlay::hint( + 0, + Anchor::min(), + &InlayHint { + label: InlayHintLabel::String(" a ".to_string()), + position: text::Anchor::default(), + padding_left: true, + padding_right: true, + tooltip: None, + kind: None, + resolve_state: ResolveState::Resolved, + }, + ) + .text + .to_string(), + " a ", + "Should not change already padded label" + ); + } + + #[gpui::test] + fn test_basic_inlays(cx: &mut AppContext) { + let buffer = MultiBuffer::build_simple("abcdefghi", cx); + let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx)); + assert_eq!(inlay_snapshot.text(), "abcdefghi"); + let mut next_inlay_id = 0; + + let (inlay_snapshot, _) = inlay_map.splice( + Vec::new(), + vec![Inlay { + id: InlayId::Hint(post_inc(&mut next_inlay_id)), + position: buffer.read(cx).snapshot(cx).anchor_after(3), + text: "|123|".into(), + }], + ); + assert_eq!(inlay_snapshot.text(), "abc|123|defghi"); + assert_eq!( + inlay_snapshot.to_inlay_point(Point::new(0, 0)), + InlayPoint::new(0, 0) + ); + assert_eq!( + inlay_snapshot.to_inlay_point(Point::new(0, 1)), + InlayPoint::new(0, 1) + ); + assert_eq!( + inlay_snapshot.to_inlay_point(Point::new(0, 2)), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.to_inlay_point(Point::new(0, 3)), + InlayPoint::new(0, 3) + ); + assert_eq!( + inlay_snapshot.to_inlay_point(Point::new(0, 4)), + InlayPoint::new(0, 9) + ); + assert_eq!( + inlay_snapshot.to_inlay_point(Point::new(0, 5)), + InlayPoint::new(0, 10) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left), + InlayPoint::new(0, 0) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right), + InlayPoint::new(0, 0) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left), + InlayPoint::new(0, 3) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right), + InlayPoint::new(0, 3) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left), + InlayPoint::new(0, 3) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right), + InlayPoint::new(0, 9) + ); + + // Edits before or after the inlay should not affect it. + buffer.update(cx, |buffer, cx| { + buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx) + }); + let (inlay_snapshot, _) = inlay_map.sync( + buffer.read(cx).snapshot(cx), + buffer_edits.consume().into_inner(), + ); + assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi"); + + // An edit surrounding the inlay should invalidate it. + buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx)); + let (inlay_snapshot, _) = inlay_map.sync( + buffer.read(cx).snapshot(cx), + buffer_edits.consume().into_inner(), + ); + assert_eq!(inlay_snapshot.text(), "abxyDzefghi"); + + let (inlay_snapshot, _) = inlay_map.splice( + Vec::new(), + vec![ + Inlay { + id: InlayId::Hint(post_inc(&mut next_inlay_id)), + position: buffer.read(cx).snapshot(cx).anchor_before(3), + text: "|123|".into(), + }, + Inlay { + id: InlayId::Suggestion(post_inc(&mut next_inlay_id)), + position: buffer.read(cx).snapshot(cx).anchor_after(3), + text: "|456|".into(), + }, + ], + ); + assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi"); + + // Edits ending where the inlay starts should not move it if it has a left bias. + buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx)); + let (inlay_snapshot, _) = inlay_map.sync( + buffer.read(cx).snapshot(cx), + buffer_edits.consume().into_inner(), + ); + assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi"); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left), + InlayPoint::new(0, 0) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right), + InlayPoint::new(0, 0) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Left), + InlayPoint::new(0, 1) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Right), + InlayPoint::new(0, 1) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Right), + InlayPoint::new(0, 2) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Left), + InlayPoint::new(0, 8) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left), + InlayPoint::new(0, 9) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right), + InlayPoint::new(0, 9) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Left), + InlayPoint::new(0, 10) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Right), + InlayPoint::new(0, 10) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Right), + InlayPoint::new(0, 11) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Left), + InlayPoint::new(0, 17) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Left), + InlayPoint::new(0, 18) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Right), + InlayPoint::new(0, 18) + ); + + // The inlays can be manually removed. + let (inlay_snapshot, _) = inlay_map.splice( + inlay_map.inlays.iter().map(|inlay| inlay.id).collect(), + Vec::new(), + ); + assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi"); + } + + #[gpui::test] + fn test_inlay_buffer_rows(cx: &mut AppContext) { + let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx)); + assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi"); + let mut next_inlay_id = 0; + + let (inlay_snapshot, _) = inlay_map.splice( + Vec::new(), + vec![ + Inlay { + id: InlayId::Hint(post_inc(&mut next_inlay_id)), + position: buffer.read(cx).snapshot(cx).anchor_before(0), + text: "|123|\n".into(), + }, + Inlay { + id: InlayId::Hint(post_inc(&mut next_inlay_id)), + position: buffer.read(cx).snapshot(cx).anchor_before(4), + text: "|456|".into(), + }, + Inlay { + id: InlayId::Suggestion(post_inc(&mut next_inlay_id)), + position: buffer.read(cx).snapshot(cx).anchor_before(7), + text: "\n|567|\n".into(), + }, + ], + ); + assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi"); + assert_eq!( + inlay_snapshot.buffer_rows(0).collect::>(), + vec![Some(0), None, Some(1), None, None, Some(2)] + ); + } + + #[gpui::test(iterations = 100)] + fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) { + init_test(cx); + + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let len = rng.gen_range(0..30); + let buffer = if rng.gen() { + let text = util::RandomCharIter::new(&mut rng) + .take(len) + .collect::(); + MultiBuffer::build_simple(&text, cx) + } else { + MultiBuffer::build_random(&mut rng, cx) + }; + let mut buffer_snapshot = buffer.read(cx).snapshot(cx); + let mut next_inlay_id = 0; + log::info!("buffer text: {:?}", buffer_snapshot.text()); + let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + for _ in 0..operations { + let mut inlay_edits = Patch::default(); + + let mut prev_inlay_text = inlay_snapshot.text(); + let mut buffer_edits = Vec::new(); + match rng.gen_range(0..=100) { + 0..=50 => { + let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); + log::info!("mutated text: {:?}", snapshot.text()); + inlay_edits = Patch::new(edits); + } + _ => buffer.update(cx, |buffer, cx| { + let subscription = buffer.subscribe(); + let edit_count = rng.gen_range(1..=5); + buffer.randomly_mutate(&mut rng, edit_count, cx); + buffer_snapshot = buffer.snapshot(cx); + let edits = subscription.consume().into_inner(); + log::info!("editing {:?}", edits); + buffer_edits.extend(edits); + }), + }; + + let (new_inlay_snapshot, new_inlay_edits) = + inlay_map.sync(buffer_snapshot.clone(), buffer_edits); + inlay_snapshot = new_inlay_snapshot; + inlay_edits = inlay_edits.compose(new_inlay_edits); + + log::info!("buffer text: {:?}", buffer_snapshot.text()); + log::info!("inlay text: {:?}", inlay_snapshot.text()); + + let inlays = inlay_map + .inlays + .iter() + .filter(|inlay| inlay.position.is_valid(&buffer_snapshot)) + .map(|inlay| { + let offset = inlay.position.to_offset(&buffer_snapshot); + (offset, inlay.clone()) + }) + .collect::>(); + let mut expected_text = Rope::from(buffer_snapshot.text()); + for (offset, inlay) in inlays.iter().rev() { + expected_text.replace(*offset..*offset, &inlay.text.to_string()); + } + assert_eq!(inlay_snapshot.text(), expected_text.to_string()); + + let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::>(); + assert_eq!( + expected_buffer_rows.len() as u32, + expected_text.max_point().row + 1 + ); + for row_start in 0..expected_buffer_rows.len() { + assert_eq!( + inlay_snapshot + .buffer_rows(row_start as u32) + .collect::>(), + &expected_buffer_rows[row_start..], + "incorrect buffer rows starting at {}", + row_start + ); + } + + let mut text_highlights = TextHighlights::default(); + let text_highlight_count = rng.gen_range(0_usize..10); + let mut text_highlight_ranges = (0..text_highlight_count) + .map(|_| buffer_snapshot.random_byte_range(0, &mut rng)) + .collect::>(); + text_highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end))); + log::info!("highlighting text ranges {text_highlight_ranges:?}"); + text_highlights.insert( + Some(TypeId::of::<()>()), + Arc::new(( + HighlightStyle::default(), + text_highlight_ranges + .into_iter() + .map(|range| { + buffer_snapshot.anchor_before(range.start) + ..buffer_snapshot.anchor_after(range.end) + }) + .collect(), + )), + ); + + let mut inlay_highlights = InlayHighlights::default(); + if !inlays.is_empty() { + let inlay_highlight_count = rng.gen_range(0..inlays.len()); + let mut inlay_indices = BTreeSet::default(); + while inlay_indices.len() < inlay_highlight_count { + inlay_indices.insert(rng.gen_range(0..inlays.len())); + } + let new_highlights = inlay_indices + .into_iter() + .filter_map(|i| { + let (_, inlay) = &inlays[i]; + let inlay_text_len = inlay.text.len(); + match inlay_text_len { + 0 => None, + 1 => Some(InlayHighlight { + inlay: inlay.id, + inlay_position: inlay.position, + range: 0..1, + }), + n => { + let inlay_text = inlay.text.to_string(); + let mut highlight_end = rng.gen_range(1..n); + let mut highlight_start = rng.gen_range(0..highlight_end); + while !inlay_text.is_char_boundary(highlight_end) { + highlight_end += 1; + } + while !inlay_text.is_char_boundary(highlight_start) { + highlight_start -= 1; + } + Some(InlayHighlight { + inlay: inlay.id, + inlay_position: inlay.position, + range: highlight_start..highlight_end, + }) + } + } + }) + .map(|highlight| (highlight.inlay, (HighlightStyle::default(), highlight))) + .collect(); + log::info!("highlighting inlay ranges {new_highlights:?}"); + inlay_highlights.insert(TypeId::of::<()>(), new_highlights); + } + + for _ in 0..5 { + let mut end = rng.gen_range(0..=inlay_snapshot.len().0); + end = expected_text.clip_offset(end, Bias::Right); + let mut start = rng.gen_range(0..=end); + start = expected_text.clip_offset(start, Bias::Right); + + let range = InlayOffset(start)..InlayOffset(end); + log::info!("calling inlay_snapshot.chunks({range:?})"); + let actual_text = inlay_snapshot + .chunks( + range, + false, + Highlights { + text_highlights: Some(&text_highlights), + inlay_highlights: Some(&inlay_highlights), + ..Highlights::default() + }, + ) + .map(|chunk| chunk.text) + .collect::(); + assert_eq!( + actual_text, + expected_text.slice(start..end).to_string(), + "incorrect text in range {:?}", + start..end + ); + + assert_eq!( + inlay_snapshot.text_summary_for_range(InlayOffset(start)..InlayOffset(end)), + expected_text.slice(start..end).summary() + ); + } + + for edit in inlay_edits { + prev_inlay_text.replace_range( + edit.new.start.0..edit.new.start.0 + edit.old_len().0, + &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0], + ); + } + assert_eq!(prev_inlay_text, inlay_snapshot.text()); + + assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0); + assert_eq!(expected_text.len(), inlay_snapshot.len().0); + + let mut buffer_point = Point::default(); + let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point); + let mut buffer_chars = buffer_snapshot.chars_at(0); + loop { + // Ensure conversion from buffer coordinates to inlay coordinates + // is consistent. + let buffer_offset = buffer_snapshot.point_to_offset(buffer_point); + assert_eq!( + inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)), + inlay_point + ); + + // No matter which bias we clip an inlay point with, it doesn't move + // because it was constructed from a buffer point. + assert_eq!( + inlay_snapshot.clip_point(inlay_point, Bias::Left), + inlay_point, + "invalid inlay point for buffer point {:?} when clipped left", + buffer_point + ); + assert_eq!( + inlay_snapshot.clip_point(inlay_point, Bias::Right), + inlay_point, + "invalid inlay point for buffer point {:?} when clipped right", + buffer_point + ); + + if let Some(ch) = buffer_chars.next() { + if ch == '\n' { + buffer_point += Point::new(1, 0); + } else { + buffer_point += Point::new(0, ch.len_utf8() as u32); + } + + // Ensure that moving forward in the buffer always moves the inlay point forward as well. + let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point); + assert!(new_inlay_point > inlay_point); + inlay_point = new_inlay_point; + } else { + break; + } + } + + let mut inlay_point = InlayPoint::default(); + let mut inlay_offset = InlayOffset::default(); + for ch in expected_text.chars() { + assert_eq!( + inlay_snapshot.to_offset(inlay_point), + inlay_offset, + "invalid to_offset({:?})", + inlay_point + ); + assert_eq!( + inlay_snapshot.to_point(inlay_offset), + inlay_point, + "invalid to_point({:?})", + inlay_offset + ); + + let mut bytes = [0; 4]; + for byte in ch.encode_utf8(&mut bytes).as_bytes() { + inlay_offset.0 += 1; + if *byte == b'\n' { + inlay_point.0 += Point::new(1, 0); + } else { + inlay_point.0 += Point::new(0, 1); + } + + let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left); + let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right); + assert!( + clipped_left_point <= clipped_right_point, + "inlay point {:?} when clipped left is greater than when clipped right ({:?} > {:?})", + inlay_point, + clipped_left_point, + clipped_right_point + ); + + // Ensure the clipped points are at valid text locations. + assert_eq!( + clipped_left_point.0, + expected_text.clip_point(clipped_left_point.0, Bias::Left) + ); + assert_eq!( + clipped_right_point.0, + expected_text.clip_point(clipped_right_point.0, Bias::Right) + ); + + // Ensure the clipped points never overshoot the end of the map. + assert!(clipped_left_point <= inlay_snapshot.max_point()); + assert!(clipped_right_point <= inlay_snapshot.max_point()); + + // Ensure the clipped points are at valid buffer locations. + assert_eq!( + inlay_snapshot + .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)), + clipped_left_point, + "to_buffer_point({:?}) = {:?}", + clipped_left_point, + inlay_snapshot.to_buffer_point(clipped_left_point), + ); + assert_eq!( + inlay_snapshot + .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)), + clipped_right_point, + "to_buffer_point({:?}) = {:?}", + clipped_right_point, + inlay_snapshot.to_buffer_point(clipped_right_point), + ); + } + } + } + } + + fn init_test(cx: &mut AppContext) { + let store = SettingsStore::test(cx); + cx.set_global(store); + theme::init(cx); + } +} diff --git a/crates/editor2/src/display_map/tab_map.rs b/crates/editor2/src/display_map/tab_map.rs new file mode 100644 index 0000000000..6b38ea2d24 --- /dev/null +++ b/crates/editor2/src/display_map/tab_map.rs @@ -0,0 +1,765 @@ +use super::{ + fold_map::{self, FoldChunks, FoldEdit, FoldPoint, FoldSnapshot}, + Highlights, +}; +use crate::MultiBufferSnapshot; +use language::{Chunk, Point}; +use std::{cmp, mem, num::NonZeroU32, ops::Range}; +use sum_tree::Bias; + +const MAX_EXPANSION_COLUMN: u32 = 256; + +pub struct TabMap(TabSnapshot); + +impl TabMap { + pub fn new(fold_snapshot: FoldSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) { + let snapshot = TabSnapshot { + fold_snapshot, + tab_size, + max_expansion_column: MAX_EXPANSION_COLUMN, + version: 0, + }; + (Self(snapshot.clone()), snapshot) + } + + #[cfg(test)] + pub fn set_max_expansion_column(&mut self, column: u32) -> TabSnapshot { + self.0.max_expansion_column = column; + self.0.clone() + } + + pub fn sync( + &mut self, + fold_snapshot: FoldSnapshot, + mut fold_edits: Vec, + tab_size: NonZeroU32, + ) -> (TabSnapshot, Vec) { + let old_snapshot = &mut self.0; + let mut new_snapshot = TabSnapshot { + fold_snapshot, + tab_size, + max_expansion_column: old_snapshot.max_expansion_column, + version: old_snapshot.version, + }; + + if old_snapshot.fold_snapshot.version != new_snapshot.fold_snapshot.version { + new_snapshot.version += 1; + } + + let mut tab_edits = Vec::with_capacity(fold_edits.len()); + + if old_snapshot.tab_size == new_snapshot.tab_size { + // Expand each edit to include the next tab on the same line as the edit, + // and any subsequent tabs on that line that moved across the tab expansion + // boundary. + for fold_edit in &mut fold_edits { + let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot); + let old_end_row_successor_offset = cmp::min( + FoldPoint::new(old_end.row() + 1, 0), + old_snapshot.fold_snapshot.max_point(), + ) + .to_offset(&old_snapshot.fold_snapshot); + let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot); + + let mut offset_from_edit = 0; + let mut first_tab_offset = None; + let mut last_tab_with_changed_expansion_offset = None; + 'outer: for chunk in old_snapshot.fold_snapshot.chunks( + fold_edit.old.end..old_end_row_successor_offset, + false, + Highlights::default(), + ) { + for (ix, _) in chunk.text.match_indices('\t') { + let offset_from_edit = offset_from_edit + (ix as u32); + if first_tab_offset.is_none() { + first_tab_offset = Some(offset_from_edit); + } + + let old_column = old_end.column() + offset_from_edit; + let new_column = new_end.column() + offset_from_edit; + let was_expanded = old_column < old_snapshot.max_expansion_column; + let is_expanded = new_column < new_snapshot.max_expansion_column; + if was_expanded != is_expanded { + last_tab_with_changed_expansion_offset = Some(offset_from_edit); + } else if !was_expanded && !is_expanded { + break 'outer; + } + } + + offset_from_edit += chunk.text.len() as u32; + if old_end.column() + offset_from_edit >= old_snapshot.max_expansion_column + && new_end.column() + offset_from_edit >= new_snapshot.max_expansion_column + { + break; + } + } + + if let Some(offset) = last_tab_with_changed_expansion_offset.or(first_tab_offset) { + fold_edit.old.end.0 += offset as usize + 1; + fold_edit.new.end.0 += offset as usize + 1; + } + } + + // Combine any edits that overlap due to the expansion. + let mut ix = 1; + while ix < fold_edits.len() { + let (prev_edits, next_edits) = fold_edits.split_at_mut(ix); + let prev_edit = prev_edits.last_mut().unwrap(); + let edit = &next_edits[0]; + if prev_edit.old.end >= edit.old.start { + prev_edit.old.end = edit.old.end; + prev_edit.new.end = edit.new.end; + fold_edits.remove(ix); + } else { + ix += 1; + } + } + + for fold_edit in fold_edits { + let old_start = fold_edit.old.start.to_point(&old_snapshot.fold_snapshot); + let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot); + let new_start = fold_edit.new.start.to_point(&new_snapshot.fold_snapshot); + let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot); + tab_edits.push(TabEdit { + old: old_snapshot.to_tab_point(old_start)..old_snapshot.to_tab_point(old_end), + new: new_snapshot.to_tab_point(new_start)..new_snapshot.to_tab_point(new_end), + }); + } + } else { + new_snapshot.version += 1; + tab_edits.push(TabEdit { + old: TabPoint::zero()..old_snapshot.max_point(), + new: TabPoint::zero()..new_snapshot.max_point(), + }); + } + + *old_snapshot = new_snapshot; + (old_snapshot.clone(), tab_edits) + } +} + +#[derive(Clone)] +pub struct TabSnapshot { + pub fold_snapshot: FoldSnapshot, + pub tab_size: NonZeroU32, + pub max_expansion_column: u32, + pub version: usize, +} + +impl TabSnapshot { + pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { + &self.fold_snapshot.inlay_snapshot.buffer + } + + pub fn line_len(&self, row: u32) -> u32 { + let max_point = self.max_point(); + if row < max_point.row() { + self.to_tab_point(FoldPoint::new(row, self.fold_snapshot.line_len(row))) + .0 + .column + } else { + max_point.column() + } + } + + pub fn text_summary(&self) -> TextSummary { + self.text_summary_for_range(TabPoint::zero()..self.max_point()) + } + + pub fn text_summary_for_range(&self, range: Range) -> TextSummary { + let input_start = self.to_fold_point(range.start, Bias::Left).0; + let input_end = self.to_fold_point(range.end, Bias::Right).0; + let input_summary = self + .fold_snapshot + .text_summary_for_range(input_start..input_end); + + let mut first_line_chars = 0; + let line_end = if range.start.row() == range.end.row() { + range.end + } else { + self.max_point() + }; + for c in self + .chunks(range.start..line_end, false, Highlights::default()) + .flat_map(|chunk| chunk.text.chars()) + { + if c == '\n' { + break; + } + first_line_chars += 1; + } + + let mut last_line_chars = 0; + if range.start.row() == range.end.row() { + last_line_chars = first_line_chars; + } else { + for _ in self + .chunks( + TabPoint::new(range.end.row(), 0)..range.end, + false, + Highlights::default(), + ) + .flat_map(|chunk| chunk.text.chars()) + { + last_line_chars += 1; + } + } + + TextSummary { + lines: range.end.0 - range.start.0, + first_line_chars, + last_line_chars, + longest_row: input_summary.longest_row, + longest_row_chars: input_summary.longest_row_chars, + } + } + + pub fn chunks<'a>( + &'a self, + range: Range, + language_aware: bool, + highlights: Highlights<'a>, + ) -> TabChunks<'a> { + let (input_start, expanded_char_column, to_next_stop) = + self.to_fold_point(range.start, Bias::Left); + let input_column = input_start.column(); + let input_start = input_start.to_offset(&self.fold_snapshot); + let input_end = self + .to_fold_point(range.end, Bias::Right) + .0 + .to_offset(&self.fold_snapshot); + let to_next_stop = if range.start.0 + Point::new(0, to_next_stop) > range.end.0 { + range.end.column() - range.start.column() + } else { + to_next_stop + }; + + TabChunks { + fold_chunks: self.fold_snapshot.chunks( + input_start..input_end, + language_aware, + highlights, + ), + input_column, + column: expanded_char_column, + max_expansion_column: self.max_expansion_column, + output_position: range.start.0, + max_output_position: range.end.0, + tab_size: self.tab_size, + chunk: Chunk { + text: &SPACES[0..(to_next_stop as usize)], + is_tab: true, + ..Default::default() + }, + inside_leading_tab: to_next_stop > 0, + } + } + + pub fn buffer_rows(&self, row: u32) -> fold_map::FoldBufferRows<'_> { + self.fold_snapshot.buffer_rows(row) + } + + #[cfg(test)] + pub fn text(&self) -> String { + self.chunks( + TabPoint::zero()..self.max_point(), + false, + Highlights::default(), + ) + .map(|chunk| chunk.text) + .collect() + } + + pub fn max_point(&self) -> TabPoint { + self.to_tab_point(self.fold_snapshot.max_point()) + } + + pub fn clip_point(&self, point: TabPoint, bias: Bias) -> TabPoint { + self.to_tab_point( + self.fold_snapshot + .clip_point(self.to_fold_point(point, bias).0, bias), + ) + } + + pub fn to_tab_point(&self, input: FoldPoint) -> TabPoint { + let chars = self.fold_snapshot.chars_at(FoldPoint::new(input.row(), 0)); + let expanded = self.expand_tabs(chars, input.column()); + TabPoint::new(input.row(), expanded) + } + + pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, u32, u32) { + let chars = self.fold_snapshot.chars_at(FoldPoint::new(output.row(), 0)); + let expanded = output.column(); + let (collapsed, expanded_char_column, to_next_stop) = + self.collapse_tabs(chars, expanded, bias); + ( + FoldPoint::new(output.row(), collapsed as u32), + expanded_char_column, + to_next_stop, + ) + } + + pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint { + let inlay_point = self.fold_snapshot.inlay_snapshot.to_inlay_point(point); + let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias); + self.to_tab_point(fold_point) + } + + pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point { + let fold_point = self.to_fold_point(point, bias).0; + let inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); + self.fold_snapshot + .inlay_snapshot + .to_buffer_point(inlay_point) + } + + fn expand_tabs(&self, chars: impl Iterator, column: u32) -> u32 { + let tab_size = self.tab_size.get(); + + let mut expanded_chars = 0; + let mut expanded_bytes = 0; + let mut collapsed_bytes = 0; + let end_column = column.min(self.max_expansion_column); + for c in chars { + if collapsed_bytes >= end_column { + break; + } + if c == '\t' { + let tab_len = tab_size - expanded_chars % tab_size; + expanded_bytes += tab_len; + expanded_chars += tab_len; + } else { + expanded_bytes += c.len_utf8() as u32; + expanded_chars += 1; + } + collapsed_bytes += c.len_utf8() as u32; + } + expanded_bytes + column.saturating_sub(collapsed_bytes) + } + + fn collapse_tabs( + &self, + chars: impl Iterator, + column: u32, + bias: Bias, + ) -> (u32, u32, u32) { + let tab_size = self.tab_size.get(); + + let mut expanded_bytes = 0; + let mut expanded_chars = 0; + let mut collapsed_bytes = 0; + for c in chars { + if expanded_bytes >= column { + break; + } + if collapsed_bytes >= self.max_expansion_column { + break; + } + + if c == '\t' { + let tab_len = tab_size - (expanded_chars % tab_size); + expanded_chars += tab_len; + expanded_bytes += tab_len; + if expanded_bytes > column { + expanded_chars -= expanded_bytes - column; + return match bias { + Bias::Left => (collapsed_bytes, expanded_chars, expanded_bytes - column), + Bias::Right => (collapsed_bytes + 1, expanded_chars, 0), + }; + } + } else { + expanded_chars += 1; + expanded_bytes += c.len_utf8() as u32; + } + + if expanded_bytes > column && matches!(bias, Bias::Left) { + expanded_chars -= 1; + break; + } + + collapsed_bytes += c.len_utf8() as u32; + } + ( + collapsed_bytes + column.saturating_sub(expanded_bytes), + expanded_chars, + 0, + ) + } +} + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct TabPoint(pub Point); + +impl TabPoint { + pub fn new(row: u32, column: u32) -> Self { + Self(Point::new(row, column)) + } + + pub fn zero() -> Self { + Self::new(0, 0) + } + + pub fn row(self) -> u32 { + self.0.row + } + + pub fn column(self) -> u32 { + self.0.column + } +} + +impl From for TabPoint { + fn from(point: Point) -> Self { + Self(point) + } +} + +pub type TabEdit = text::Edit; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct TextSummary { + pub lines: Point, + pub first_line_chars: u32, + pub last_line_chars: u32, + pub longest_row: u32, + pub longest_row_chars: u32, +} + +impl<'a> From<&'a str> for TextSummary { + fn from(text: &'a str) -> Self { + let sum = text::TextSummary::from(text); + + TextSummary { + lines: sum.lines, + first_line_chars: sum.first_line_chars, + last_line_chars: sum.last_line_chars, + longest_row: sum.longest_row, + longest_row_chars: sum.longest_row_chars, + } + } +} + +impl<'a> std::ops::AddAssign<&'a Self> for TextSummary { + fn add_assign(&mut self, other: &'a Self) { + let joined_chars = self.last_line_chars + other.first_line_chars; + if joined_chars > self.longest_row_chars { + self.longest_row = self.lines.row; + self.longest_row_chars = joined_chars; + } + if other.longest_row_chars > self.longest_row_chars { + self.longest_row = self.lines.row + other.longest_row; + self.longest_row_chars = other.longest_row_chars; + } + + if self.lines.row == 0 { + self.first_line_chars += other.first_line_chars; + } + + if other.lines.row == 0 { + self.last_line_chars += other.first_line_chars; + } else { + self.last_line_chars = other.last_line_chars; + } + + self.lines += &other.lines; + } +} + +// Handles a tab width <= 16 +const SPACES: &str = " "; + +pub struct TabChunks<'a> { + fold_chunks: FoldChunks<'a>, + chunk: Chunk<'a>, + column: u32, + max_expansion_column: u32, + output_position: Point, + input_column: u32, + max_output_position: Point, + tab_size: NonZeroU32, + inside_leading_tab: bool, +} + +impl<'a> Iterator for TabChunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + if self.chunk.text.is_empty() { + if let Some(chunk) = self.fold_chunks.next() { + self.chunk = chunk; + if self.inside_leading_tab { + self.chunk.text = &self.chunk.text[1..]; + self.inside_leading_tab = false; + self.input_column += 1; + } + } else { + return None; + } + } + + for (ix, c) in self.chunk.text.char_indices() { + match c { + '\t' => { + if ix > 0 { + let (prefix, suffix) = self.chunk.text.split_at(ix); + self.chunk.text = suffix; + return Some(Chunk { + text: prefix, + ..self.chunk + }); + } else { + self.chunk.text = &self.chunk.text[1..]; + let tab_size = if self.input_column < self.max_expansion_column { + self.tab_size.get() as u32 + } else { + 1 + }; + let mut len = tab_size - self.column % tab_size; + let next_output_position = cmp::min( + self.output_position + Point::new(0, len), + self.max_output_position, + ); + len = next_output_position.column - self.output_position.column; + self.column += len; + self.input_column += 1; + self.output_position = next_output_position; + return Some(Chunk { + text: &SPACES[..len as usize], + is_tab: true, + ..self.chunk + }); + } + } + '\n' => { + self.column = 0; + self.input_column = 0; + self.output_position += Point::new(1, 0); + } + _ => { + self.column += 1; + if !self.inside_leading_tab { + self.input_column += c.len_utf8() as u32; + } + self.output_position.column += c.len_utf8() as u32; + } + } + } + + Some(mem::take(&mut self.chunk)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + display_map::{fold_map::FoldMap, inlay_map::InlayMap}, + MultiBuffer, + }; + use rand::{prelude::StdRng, Rng}; + + #[gpui::test] + fn test_expand_tabs(cx: &mut gpui::AppContext) { + let buffer = MultiBuffer::build_simple("", cx); + let buffer_snapshot = buffer.read(cx).snapshot(cx); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); + + assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 0), 0); + assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 1), 4); + assert_eq!(tab_snapshot.expand_tabs("\ta".chars(), 2), 5); + } + + #[gpui::test] + fn test_long_lines(cx: &mut gpui::AppContext) { + let max_expansion_column = 12; + let input = "A\tBC\tDEF\tG\tHI\tJ\tK\tL\tM"; + let output = "A BC DEF G HI J K L M"; + + let buffer = MultiBuffer::build_simple(input, cx); + let buffer_snapshot = buffer.read(cx).snapshot(cx); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); + + tab_snapshot.max_expansion_column = max_expansion_column; + assert_eq!(tab_snapshot.text(), output); + + for (ix, c) in input.char_indices() { + assert_eq!( + tab_snapshot + .chunks( + TabPoint::new(0, ix as u32)..tab_snapshot.max_point(), + false, + Highlights::default(), + ) + .map(|c| c.text) + .collect::(), + &output[ix..], + "text from index {ix}" + ); + + if c != '\t' { + let input_point = Point::new(0, ix as u32); + let output_point = Point::new(0, output.find(c).unwrap() as u32); + assert_eq!( + tab_snapshot.to_tab_point(FoldPoint(input_point)), + TabPoint(output_point), + "to_tab_point({input_point:?})" + ); + assert_eq!( + tab_snapshot + .to_fold_point(TabPoint(output_point), Bias::Left) + .0, + FoldPoint(input_point), + "to_fold_point({output_point:?})" + ); + } + } + } + + #[gpui::test] + fn test_long_lines_with_character_spanning_max_expansion_column(cx: &mut gpui::AppContext) { + let max_expansion_column = 8; + let input = "abcdefg⋯hij"; + + let buffer = MultiBuffer::build_simple(input, cx); + let buffer_snapshot = buffer.read(cx).snapshot(cx); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); + + tab_snapshot.max_expansion_column = max_expansion_column; + assert_eq!(tab_snapshot.text(), input); + } + + #[gpui::test] + fn test_marking_tabs(cx: &mut gpui::AppContext) { + let input = "\t \thello"; + + let buffer = MultiBuffer::build_simple(&input, cx); + let buffer_snapshot = buffer.read(cx).snapshot(cx); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); + + assert_eq!( + chunks(&tab_snapshot, TabPoint::zero()), + vec![ + (" ".to_string(), true), + (" ".to_string(), false), + (" ".to_string(), true), + ("hello".to_string(), false), + ] + ); + assert_eq!( + chunks(&tab_snapshot, TabPoint::new(0, 2)), + vec![ + (" ".to_string(), true), + (" ".to_string(), false), + (" ".to_string(), true), + ("hello".to_string(), false), + ] + ); + + fn chunks(snapshot: &TabSnapshot, start: TabPoint) -> Vec<(String, bool)> { + let mut chunks = Vec::new(); + let mut was_tab = false; + let mut text = String::new(); + for chunk in snapshot.chunks(start..snapshot.max_point(), false, Highlights::default()) + { + if chunk.is_tab != was_tab { + if !text.is_empty() { + chunks.push((mem::take(&mut text), was_tab)); + } + was_tab = chunk.is_tab; + } + text.push_str(chunk.text); + } + + if !text.is_empty() { + chunks.push((text, was_tab)); + } + chunks + } + } + + #[gpui::test(iterations = 100)] + fn test_random_tabs(cx: &mut gpui::AppContext, mut rng: StdRng) { + let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap(); + let len = rng.gen_range(0..30); + let buffer = if rng.gen() { + let text = util::RandomCharIter::new(&mut rng) + .take(len) + .collect::(); + MultiBuffer::build_simple(&text, cx) + } else { + MultiBuffer::build_random(&mut rng, cx) + }; + let buffer_snapshot = buffer.read(cx).snapshot(cx); + log::info!("Buffer text: {:?}", buffer_snapshot.text()); + + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + log::info!("InlayMap text: {:?}", inlay_snapshot.text()); + let (mut fold_map, _) = FoldMap::new(inlay_snapshot.clone()); + fold_map.randomly_mutate(&mut rng); + let (fold_snapshot, _) = fold_map.read(inlay_snapshot, vec![]); + log::info!("FoldMap text: {:?}", fold_snapshot.text()); + let (inlay_snapshot, _) = inlay_map.randomly_mutate(&mut 0, &mut rng); + log::info!("InlayMap text: {:?}", inlay_snapshot.text()); + + let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size); + let tabs_snapshot = tab_map.set_max_expansion_column(32); + + let text = text::Rope::from(tabs_snapshot.text().as_str()); + log::info!( + "TabMap text (tab size: {}): {:?}", + tab_size, + tabs_snapshot.text(), + ); + + for _ in 0..5 { + let end_row = rng.gen_range(0..=text.max_point().row); + let end_column = rng.gen_range(0..=text.line_len(end_row)); + let mut end = TabPoint(text.clip_point(Point::new(end_row, end_column), Bias::Right)); + let start_row = rng.gen_range(0..=text.max_point().row); + let start_column = rng.gen_range(0..=text.line_len(start_row)); + let mut start = + TabPoint(text.clip_point(Point::new(start_row, start_column), Bias::Left)); + if start > end { + mem::swap(&mut start, &mut end); + } + + let expected_text = text + .chunks_in_range(text.point_to_offset(start.0)..text.point_to_offset(end.0)) + .collect::(); + let expected_summary = TextSummary::from(expected_text.as_str()); + assert_eq!( + tabs_snapshot + .chunks(start..end, false, Highlights::default()) + .map(|c| c.text) + .collect::(), + expected_text, + "chunks({:?}..{:?})", + start, + end + ); + + let mut actual_summary = tabs_snapshot.text_summary_for_range(start..end); + if tab_size.get() > 1 && inlay_snapshot.text().contains('\t') { + actual_summary.longest_row = expected_summary.longest_row; + actual_summary.longest_row_chars = expected_summary.longest_row_chars; + } + assert_eq!(actual_summary, expected_summary); + } + + for row in 0..=text.max_point().row { + assert_eq!( + tabs_snapshot.line_len(row), + text.line_len(row), + "line_len({row})" + ); + } + } +} diff --git a/crates/editor2/src/display_map/wrap_map.rs b/crates/editor2/src/display_map/wrap_map.rs new file mode 100644 index 0000000000..408142ae07 --- /dev/null +++ b/crates/editor2/src/display_map/wrap_map.rs @@ -0,0 +1,1362 @@ +use super::{ + fold_map::FoldBufferRows, + tab_map::{self, TabEdit, TabPoint, TabSnapshot}, + Highlights, +}; +use crate::MultiBufferSnapshot; +use gpui::{AppContext, Context, Font, LineWrapper, Model, ModelContext, Pixels, Task}; +use language::{Chunk, Point}; +use lazy_static::lazy_static; +use smol::future::yield_now; +use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration}; +use sum_tree::{Bias, Cursor, SumTree}; +use text::Patch; +use util::ResultExt; + +pub use super::tab_map::TextSummary; +pub type WrapEdit = text::Edit; + +pub struct WrapMap { + snapshot: WrapSnapshot, + pending_edits: VecDeque<(TabSnapshot, Vec)>, + interpolated_edits: Patch, + edits_since_sync: Patch, + wrap_width: Option, + background_task: Option>, + font_with_size: (Font, Pixels), +} + +#[derive(Clone)] +pub struct WrapSnapshot { + tab_snapshot: TabSnapshot, + transforms: SumTree, + interpolated: bool, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +struct Transform { + summary: TransformSummary, + display_text: Option<&'static str>, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +struct TransformSummary { + input: TextSummary, + output: TextSummary, +} + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct WrapPoint(pub Point); + +pub struct WrapChunks<'a> { + input_chunks: tab_map::TabChunks<'a>, + input_chunk: Chunk<'a>, + output_position: WrapPoint, + max_output_row: u32, + transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>, +} + +#[derive(Clone)] +pub struct WrapBufferRows<'a> { + input_buffer_rows: FoldBufferRows<'a>, + input_buffer_row: Option, + output_row: u32, + soft_wrapped: bool, + max_output_row: u32, + transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>, +} + +impl WrapMap { + pub fn new( + tab_snapshot: TabSnapshot, + font: Font, + font_size: Pixels, + wrap_width: Option, + cx: &mut AppContext, + ) -> (Model, WrapSnapshot) { + let handle = cx.build_model(|cx| { + let mut this = Self { + font_with_size: (font, font_size), + wrap_width: None, + pending_edits: Default::default(), + interpolated_edits: Default::default(), + edits_since_sync: Default::default(), + snapshot: WrapSnapshot::new(tab_snapshot), + background_task: None, + }; + this.set_wrap_width(wrap_width, cx); + mem::take(&mut this.edits_since_sync); + this + }); + let snapshot = handle.read(cx).snapshot.clone(); + (handle, snapshot) + } + + #[cfg(test)] + pub fn is_rewrapping(&self) -> bool { + self.background_task.is_some() + } + + pub fn sync( + &mut self, + tab_snapshot: TabSnapshot, + edits: Vec, + cx: &mut ModelContext, + ) -> (WrapSnapshot, Patch) { + if self.wrap_width.is_some() { + self.pending_edits.push_back((tab_snapshot, edits)); + self.flush_edits(cx); + } else { + self.edits_since_sync = self + .edits_since_sync + .compose(&self.snapshot.interpolate(tab_snapshot, &edits)); + self.snapshot.interpolated = false; + } + + (self.snapshot.clone(), mem::take(&mut self.edits_since_sync)) + } + + pub fn set_font_with_size( + &mut self, + font: Font, + font_size: Pixels, + cx: &mut ModelContext, + ) -> bool { + let font_with_size = (font, font_size); + + if font_with_size != self.font_with_size { + self.font_with_size = font_with_size; + self.rewrap(cx); + true + } else { + false + } + } + + pub fn set_wrap_width( + &mut self, + wrap_width: Option, + cx: &mut ModelContext, + ) -> bool { + if wrap_width == self.wrap_width { + return false; + } + + self.wrap_width = wrap_width; + self.rewrap(cx); + true + } + + fn rewrap(&mut self, cx: &mut ModelContext) { + self.background_task.take(); + self.interpolated_edits.clear(); + self.pending_edits.clear(); + + if let Some(wrap_width) = self.wrap_width { + let mut new_snapshot = self.snapshot.clone(); + let mut edits = Patch::default(); + let text_system = cx.text_system().clone(); + let (font, font_size) = self.font_with_size.clone(); + let task = cx.background_executor().spawn(async move { + if let Some(mut line_wrapper) = text_system.line_wrapper(font, font_size).log_err() + { + let tab_snapshot = new_snapshot.tab_snapshot.clone(); + let range = TabPoint::zero()..tab_snapshot.max_point(); + let edits = new_snapshot + .update( + tab_snapshot, + &[TabEdit { + old: range.clone(), + new: range.clone(), + }], + wrap_width, + &mut line_wrapper, + ) + .await; + } + (new_snapshot, edits) + }); + + match cx + .background_executor() + .block_with_timeout(Duration::from_millis(5), task) + { + Ok((snapshot, edits)) => { + self.snapshot = snapshot; + self.edits_since_sync = self.edits_since_sync.compose(&edits); + cx.notify(); + } + Err(wrap_task) => { + self.background_task = Some(cx.spawn(|this, mut cx| async move { + let (snapshot, edits) = wrap_task.await; + this.update(&mut cx, |this, cx| { + this.snapshot = snapshot; + this.edits_since_sync = this + .edits_since_sync + .compose(mem::take(&mut this.interpolated_edits).invert()) + .compose(&edits); + this.background_task = None; + this.flush_edits(cx); + cx.notify(); + }); + })); + } + } + } else { + let old_rows = self.snapshot.transforms.summary().output.lines.row + 1; + self.snapshot.transforms = SumTree::new(); + let summary = self.snapshot.tab_snapshot.text_summary(); + if !summary.lines.is_zero() { + self.snapshot + .transforms + .push(Transform::isomorphic(summary), &()); + } + let new_rows = self.snapshot.transforms.summary().output.lines.row + 1; + self.snapshot.interpolated = false; + self.edits_since_sync = self.edits_since_sync.compose(&Patch::new(vec![WrapEdit { + old: 0..old_rows, + new: 0..new_rows, + }])); + } + } + + fn flush_edits(&mut self, cx: &mut ModelContext) { + if !self.snapshot.interpolated { + let mut to_remove_len = 0; + for (tab_snapshot, _) in &self.pending_edits { + if tab_snapshot.version <= self.snapshot.tab_snapshot.version { + to_remove_len += 1; + } else { + break; + } + } + self.pending_edits.drain(..to_remove_len); + } + + if self.pending_edits.is_empty() { + return; + } + + if let Some(wrap_width) = self.wrap_width { + if self.background_task.is_none() { + let pending_edits = self.pending_edits.clone(); + let mut snapshot = self.snapshot.clone(); + let text_system = cx.text_system().clone(); + let (font, font_size) = self.font_with_size.clone(); + let update_task = cx.background_executor().spawn(async move { + let mut edits = Patch::default(); + if let Some(mut line_wrapper) = + text_system.line_wrapper(font, font_size).log_err() + { + for (tab_snapshot, tab_edits) in pending_edits { + let wrap_edits = snapshot + .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper) + .await; + edits = edits.compose(&wrap_edits); + } + } + (snapshot, edits) + }); + + match cx + .background_executor() + .block_with_timeout(Duration::from_millis(1), update_task) + { + Ok((snapshot, output_edits)) => { + self.snapshot = snapshot; + self.edits_since_sync = self.edits_since_sync.compose(&output_edits); + } + Err(update_task) => { + self.background_task = Some(cx.spawn(|this, mut cx| async move { + let (snapshot, edits) = update_task.await; + this.update(&mut cx, |this, cx| { + this.snapshot = snapshot; + this.edits_since_sync = this + .edits_since_sync + .compose(mem::take(&mut this.interpolated_edits).invert()) + .compose(&edits); + this.background_task = None; + this.flush_edits(cx); + cx.notify(); + }); + })); + } + } + } + } + + let was_interpolated = self.snapshot.interpolated; + let mut to_remove_len = 0; + for (tab_snapshot, edits) in &self.pending_edits { + if tab_snapshot.version <= self.snapshot.tab_snapshot.version { + to_remove_len += 1; + } else { + let interpolated_edits = self.snapshot.interpolate(tab_snapshot.clone(), edits); + self.edits_since_sync = self.edits_since_sync.compose(&interpolated_edits); + self.interpolated_edits = self.interpolated_edits.compose(&interpolated_edits); + } + } + + if !was_interpolated { + self.pending_edits.drain(..to_remove_len); + } + } +} + +impl WrapSnapshot { + fn new(tab_snapshot: TabSnapshot) -> Self { + let mut transforms = SumTree::new(); + let extent = tab_snapshot.text_summary(); + if !extent.lines.is_zero() { + transforms.push(Transform::isomorphic(extent), &()); + } + Self { + transforms, + tab_snapshot, + interpolated: true, + } + } + + pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { + self.tab_snapshot.buffer_snapshot() + } + + fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> Patch { + let mut new_transforms; + if tab_edits.is_empty() { + new_transforms = self.transforms.clone(); + } else { + let mut old_cursor = self.transforms.cursor::(); + + let mut tab_edits_iter = tab_edits.iter().peekable(); + new_transforms = + old_cursor.slice(&tab_edits_iter.peek().unwrap().old.start, Bias::Right, &()); + + while let Some(edit) = tab_edits_iter.next() { + if edit.new.start > TabPoint::from(new_transforms.summary().input.lines) { + let summary = new_tab_snapshot.text_summary_for_range( + TabPoint::from(new_transforms.summary().input.lines)..edit.new.start, + ); + new_transforms.push_or_extend(Transform::isomorphic(summary)); + } + + if !edit.new.is_empty() { + new_transforms.push_or_extend(Transform::isomorphic( + new_tab_snapshot.text_summary_for_range(edit.new.clone()), + )); + } + + old_cursor.seek_forward(&edit.old.end, Bias::Right, &()); + if let Some(next_edit) = tab_edits_iter.peek() { + if next_edit.old.start > old_cursor.end(&()) { + if old_cursor.end(&()) > edit.old.end { + let summary = self + .tab_snapshot + .text_summary_for_range(edit.old.end..old_cursor.end(&())); + new_transforms.push_or_extend(Transform::isomorphic(summary)); + } + + old_cursor.next(&()); + new_transforms.append( + old_cursor.slice(&next_edit.old.start, Bias::Right, &()), + &(), + ); + } + } else { + if old_cursor.end(&()) > edit.old.end { + let summary = self + .tab_snapshot + .text_summary_for_range(edit.old.end..old_cursor.end(&())); + new_transforms.push_or_extend(Transform::isomorphic(summary)); + } + old_cursor.next(&()); + new_transforms.append(old_cursor.suffix(&()), &()); + } + } + } + + let old_snapshot = mem::replace( + self, + WrapSnapshot { + tab_snapshot: new_tab_snapshot, + transforms: new_transforms, + interpolated: true, + }, + ); + self.check_invariants(); + old_snapshot.compute_edits(tab_edits, self) + } + + async fn update( + &mut self, + new_tab_snapshot: TabSnapshot, + tab_edits: &[TabEdit], + wrap_width: Pixels, + line_wrapper: &mut LineWrapper, + ) -> Patch { + #[derive(Debug)] + struct RowEdit { + old_rows: Range, + new_rows: Range, + } + + let mut tab_edits_iter = tab_edits.iter().peekable(); + let mut row_edits = Vec::new(); + while let Some(edit) = tab_edits_iter.next() { + let mut row_edit = RowEdit { + old_rows: edit.old.start.row()..edit.old.end.row() + 1, + new_rows: edit.new.start.row()..edit.new.end.row() + 1, + }; + + while let Some(next_edit) = tab_edits_iter.peek() { + if next_edit.old.start.row() <= row_edit.old_rows.end { + row_edit.old_rows.end = next_edit.old.end.row() + 1; + row_edit.new_rows.end = next_edit.new.end.row() + 1; + tab_edits_iter.next(); + } else { + break; + } + } + + row_edits.push(row_edit); + } + + let mut new_transforms; + if row_edits.is_empty() { + new_transforms = self.transforms.clone(); + } else { + let mut row_edits = row_edits.into_iter().peekable(); + let mut old_cursor = self.transforms.cursor::(); + + new_transforms = old_cursor.slice( + &TabPoint::new(row_edits.peek().unwrap().old_rows.start, 0), + Bias::Right, + &(), + ); + + while let Some(edit) = row_edits.next() { + if edit.new_rows.start > new_transforms.summary().input.lines.row { + let summary = new_tab_snapshot.text_summary_for_range( + TabPoint(new_transforms.summary().input.lines) + ..TabPoint::new(edit.new_rows.start, 0), + ); + new_transforms.push_or_extend(Transform::isomorphic(summary)); + } + + let mut line = String::new(); + let mut remaining = None; + let mut chunks = new_tab_snapshot.chunks( + TabPoint::new(edit.new_rows.start, 0)..new_tab_snapshot.max_point(), + false, + Highlights::default(), + ); + let mut edit_transforms = Vec::::new(); + for _ in edit.new_rows.start..edit.new_rows.end { + while let Some(chunk) = + remaining.take().or_else(|| chunks.next().map(|c| c.text)) + { + if let Some(ix) = chunk.find('\n') { + line.push_str(&chunk[..ix + 1]); + remaining = Some(&chunk[ix + 1..]); + break; + } else { + line.push_str(chunk) + } + } + + if line.is_empty() { + break; + } + + let mut prev_boundary_ix = 0; + for boundary in line_wrapper.wrap_line(&line, wrap_width) { + let wrapped = &line[prev_boundary_ix..boundary.ix]; + push_isomorphic(&mut edit_transforms, TextSummary::from(wrapped)); + edit_transforms.push(Transform::wrap(boundary.next_indent)); + prev_boundary_ix = boundary.ix; + } + + if prev_boundary_ix < line.len() { + push_isomorphic( + &mut edit_transforms, + TextSummary::from(&line[prev_boundary_ix..]), + ); + } + + line.clear(); + yield_now().await; + } + + let mut edit_transforms = edit_transforms.into_iter(); + if let Some(transform) = edit_transforms.next() { + new_transforms.push_or_extend(transform); + } + new_transforms.extend(edit_transforms, &()); + + old_cursor.seek_forward(&TabPoint::new(edit.old_rows.end, 0), Bias::Right, &()); + if let Some(next_edit) = row_edits.peek() { + if next_edit.old_rows.start > old_cursor.end(&()).row() { + if old_cursor.end(&()) > TabPoint::new(edit.old_rows.end, 0) { + let summary = self.tab_snapshot.text_summary_for_range( + TabPoint::new(edit.old_rows.end, 0)..old_cursor.end(&()), + ); + new_transforms.push_or_extend(Transform::isomorphic(summary)); + } + old_cursor.next(&()); + new_transforms.append( + old_cursor.slice( + &TabPoint::new(next_edit.old_rows.start, 0), + Bias::Right, + &(), + ), + &(), + ); + } + } else { + if old_cursor.end(&()) > TabPoint::new(edit.old_rows.end, 0) { + let summary = self.tab_snapshot.text_summary_for_range( + TabPoint::new(edit.old_rows.end, 0)..old_cursor.end(&()), + ); + new_transforms.push_or_extend(Transform::isomorphic(summary)); + } + old_cursor.next(&()); + new_transforms.append(old_cursor.suffix(&()), &()); + } + } + } + + let old_snapshot = mem::replace( + self, + WrapSnapshot { + tab_snapshot: new_tab_snapshot, + transforms: new_transforms, + interpolated: false, + }, + ); + self.check_invariants(); + old_snapshot.compute_edits(tab_edits, self) + } + + fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &WrapSnapshot) -> Patch { + let mut wrap_edits = Vec::new(); + let mut old_cursor = self.transforms.cursor::(); + let mut new_cursor = new_snapshot.transforms.cursor::(); + for mut tab_edit in tab_edits.iter().cloned() { + tab_edit.old.start.0.column = 0; + tab_edit.old.end.0 += Point::new(1, 0); + tab_edit.new.start.0.column = 0; + tab_edit.new.end.0 += Point::new(1, 0); + + old_cursor.seek(&tab_edit.old.start, Bias::Right, &()); + let mut old_start = old_cursor.start().output.lines; + old_start += tab_edit.old.start.0 - old_cursor.start().input.lines; + + old_cursor.seek(&tab_edit.old.end, Bias::Right, &()); + let mut old_end = old_cursor.start().output.lines; + old_end += tab_edit.old.end.0 - old_cursor.start().input.lines; + + new_cursor.seek(&tab_edit.new.start, Bias::Right, &()); + let mut new_start = new_cursor.start().output.lines; + new_start += tab_edit.new.start.0 - new_cursor.start().input.lines; + + new_cursor.seek(&tab_edit.new.end, Bias::Right, &()); + let mut new_end = new_cursor.start().output.lines; + new_end += tab_edit.new.end.0 - new_cursor.start().input.lines; + + wrap_edits.push(WrapEdit { + old: old_start.row..old_end.row, + new: new_start.row..new_end.row, + }); + } + + consolidate_wrap_edits(&mut wrap_edits); + Patch::new(wrap_edits) + } + + pub fn chunks<'a>( + &'a self, + rows: Range, + language_aware: bool, + highlights: Highlights<'a>, + ) -> WrapChunks<'a> { + let output_start = WrapPoint::new(rows.start, 0); + let output_end = WrapPoint::new(rows.end, 0); + let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(); + transforms.seek(&output_start, Bias::Right, &()); + let mut input_start = TabPoint(transforms.start().1 .0); + if transforms.item().map_or(false, |t| t.is_isomorphic()) { + input_start.0 += output_start.0 - transforms.start().0 .0; + } + let input_end = self + .to_tab_point(output_end) + .min(self.tab_snapshot.max_point()); + WrapChunks { + input_chunks: self.tab_snapshot.chunks( + input_start..input_end, + language_aware, + highlights, + ), + input_chunk: Default::default(), + output_position: output_start, + max_output_row: rows.end, + transforms, + } + } + + pub fn max_point(&self) -> WrapPoint { + WrapPoint(self.transforms.summary().output.lines) + } + + pub fn line_len(&self, row: u32) -> u32 { + let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(); + cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Left, &()); + if cursor + .item() + .map_or(false, |transform| transform.is_isomorphic()) + { + let overshoot = row - cursor.start().0.row(); + let tab_row = cursor.start().1.row() + overshoot; + let tab_line_len = self.tab_snapshot.line_len(tab_row); + if overshoot == 0 { + cursor.start().0.column() + (tab_line_len - cursor.start().1.column()) + } else { + tab_line_len + } + } else { + cursor.start().0.column() + } + } + + pub fn soft_wrap_indent(&self, row: u32) -> Option { + let mut cursor = self.transforms.cursor::(); + cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right, &()); + cursor.item().and_then(|transform| { + if transform.is_isomorphic() { + None + } else { + Some(transform.summary.output.lines.column) + } + }) + } + + pub fn longest_row(&self) -> u32 { + self.transforms.summary().output.longest_row + } + + pub fn buffer_rows(&self, start_row: u32) -> WrapBufferRows { + let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(); + transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left, &()); + let mut input_row = transforms.start().1.row(); + if transforms.item().map_or(false, |t| t.is_isomorphic()) { + input_row += start_row - transforms.start().0.row(); + } + let soft_wrapped = transforms.item().map_or(false, |t| !t.is_isomorphic()); + let mut input_buffer_rows = self.tab_snapshot.buffer_rows(input_row); + let input_buffer_row = input_buffer_rows.next().unwrap(); + WrapBufferRows { + transforms, + input_buffer_row, + input_buffer_rows, + output_row: start_row, + soft_wrapped, + max_output_row: self.max_point().row(), + } + } + + pub fn to_tab_point(&self, point: WrapPoint) -> TabPoint { + let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(); + cursor.seek(&point, Bias::Right, &()); + let mut tab_point = cursor.start().1 .0; + if cursor.item().map_or(false, |t| t.is_isomorphic()) { + tab_point += point.0 - cursor.start().0 .0; + } + TabPoint(tab_point) + } + + pub fn to_point(&self, point: WrapPoint, bias: Bias) -> Point { + self.tab_snapshot.to_point(self.to_tab_point(point), bias) + } + + pub fn make_wrap_point(&self, point: Point, bias: Bias) -> WrapPoint { + self.tab_point_to_wrap_point(self.tab_snapshot.make_tab_point(point, bias)) + } + + pub fn tab_point_to_wrap_point(&self, point: TabPoint) -> WrapPoint { + let mut cursor = self.transforms.cursor::<(TabPoint, WrapPoint)>(); + cursor.seek(&point, Bias::Right, &()); + WrapPoint(cursor.start().1 .0 + (point.0 - cursor.start().0 .0)) + } + + pub fn clip_point(&self, mut point: WrapPoint, bias: Bias) -> WrapPoint { + if bias == Bias::Left { + let mut cursor = self.transforms.cursor::(); + cursor.seek(&point, Bias::Right, &()); + if cursor.item().map_or(false, |t| !t.is_isomorphic()) { + point = *cursor.start(); + *point.column_mut() -= 1; + } + } + + self.tab_point_to_wrap_point(self.tab_snapshot.clip_point(self.to_tab_point(point), bias)) + } + + pub fn prev_row_boundary(&self, mut point: WrapPoint) -> u32 { + if self.transforms.is_empty() { + return 0; + } + + *point.column_mut() = 0; + + let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(); + cursor.seek(&point, Bias::Right, &()); + if cursor.item().is_none() { + cursor.prev(&()); + } + + while let Some(transform) = cursor.item() { + if transform.is_isomorphic() && cursor.start().1.column() == 0 { + return cmp::min(cursor.end(&()).0.row(), point.row()); + } else { + cursor.prev(&()); + } + } + + unreachable!() + } + + pub fn next_row_boundary(&self, mut point: WrapPoint) -> Option { + point.0 += Point::new(1, 0); + + let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(); + cursor.seek(&point, Bias::Right, &()); + while let Some(transform) = cursor.item() { + if transform.is_isomorphic() && cursor.start().1.column() == 0 { + return Some(cmp::max(cursor.start().0.row(), point.row())); + } else { + cursor.next(&()); + } + } + + None + } + + fn check_invariants(&self) { + // todo!() + // #[cfg(test)] + // { + // assert_eq!( + // TabPoint::from(self.transforms.summary().input.lines), + // self.tab_snapshot.max_point() + // ); + + // { + // let mut transforms = self.transforms.cursor::<()>().peekable(); + // while let Some(transform) = transforms.next() { + // if let Some(next_transform) = transforms.peek() { + // assert!(transform.is_isomorphic() != next_transform.is_isomorphic()); + // } + // } + // } + + // let text = language::Rope::from(self.text().as_str()); + // let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0); + // let mut expected_buffer_rows = Vec::new(); + // let mut prev_tab_row = 0; + // for display_row in 0..=self.max_point().row() { + // let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0)); + // if tab_point.row() == prev_tab_row && display_row != 0 { + // expected_buffer_rows.push(None); + // } else { + // expected_buffer_rows.push(input_buffer_rows.next().unwrap()); + // } + + // prev_tab_row = tab_point.row(); + // assert_eq!(self.line_len(display_row), text.line_len(display_row)); + // } + + // for start_display_row in 0..expected_buffer_rows.len() { + // assert_eq!( + // self.buffer_rows(start_display_row as u32) + // .collect::>(), + // &expected_buffer_rows[start_display_row..], + // "invalid buffer_rows({}..)", + // start_display_row + // ); + // } + // } + } +} + +impl<'a> Iterator for WrapChunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + if self.output_position.row() >= self.max_output_row { + return None; + } + + let transform = self.transforms.item()?; + if let Some(display_text) = transform.display_text { + let mut start_ix = 0; + let mut end_ix = display_text.len(); + let mut summary = transform.summary.output.lines; + + if self.output_position > self.transforms.start().0 { + // Exclude newline starting prior to the desired row. + start_ix = 1; + summary.row = 0; + } else if self.output_position.row() + 1 >= self.max_output_row { + // Exclude soft indentation ending after the desired row. + end_ix = 1; + summary.column = 0; + } + + self.output_position.0 += summary; + self.transforms.next(&()); + return Some(Chunk { + text: &display_text[start_ix..end_ix], + ..self.input_chunk + }); + } + + if self.input_chunk.text.is_empty() { + self.input_chunk = self.input_chunks.next().unwrap(); + } + + let mut input_len = 0; + let transform_end = self.transforms.end(&()).0; + for c in self.input_chunk.text.chars() { + let char_len = c.len_utf8(); + input_len += char_len; + if c == '\n' { + *self.output_position.row_mut() += 1; + *self.output_position.column_mut() = 0; + } else { + *self.output_position.column_mut() += char_len as u32; + } + + if self.output_position >= transform_end { + self.transforms.next(&()); + break; + } + } + + let (prefix, suffix) = self.input_chunk.text.split_at(input_len); + self.input_chunk.text = suffix; + Some(Chunk { + text: prefix, + ..self.input_chunk + }) + } +} + +impl<'a> Iterator for WrapBufferRows<'a> { + type Item = Option; + + fn next(&mut self) -> Option { + if self.output_row > self.max_output_row { + return None; + } + + let buffer_row = self.input_buffer_row; + let soft_wrapped = self.soft_wrapped; + + self.output_row += 1; + self.transforms + .seek_forward(&WrapPoint::new(self.output_row, 0), Bias::Left, &()); + if self.transforms.item().map_or(false, |t| t.is_isomorphic()) { + self.input_buffer_row = self.input_buffer_rows.next().unwrap(); + self.soft_wrapped = false; + } else { + self.soft_wrapped = true; + } + + Some(if soft_wrapped { None } else { buffer_row }) + } +} + +impl Transform { + fn isomorphic(summary: TextSummary) -> Self { + #[cfg(test)] + assert!(!summary.lines.is_zero()); + + Self { + summary: TransformSummary { + input: summary.clone(), + output: summary, + }, + display_text: None, + } + } + + fn wrap(indent: u32) -> Self { + lazy_static! { + static ref WRAP_TEXT: String = { + let mut wrap_text = String::new(); + wrap_text.push('\n'); + wrap_text.extend((0..LineWrapper::MAX_INDENT as usize).map(|_| ' ')); + wrap_text + }; + } + + Self { + summary: TransformSummary { + input: TextSummary::default(), + output: TextSummary { + lines: Point::new(1, indent), + first_line_chars: 0, + last_line_chars: indent, + longest_row: 1, + longest_row_chars: indent, + }, + }, + display_text: Some(&WRAP_TEXT[..1 + indent as usize]), + } + } + + fn is_isomorphic(&self) -> bool { + self.display_text.is_none() + } +} + +impl sum_tree::Item for Transform { + type Summary = TransformSummary; + + fn summary(&self) -> Self::Summary { + self.summary.clone() + } +} + +fn push_isomorphic(transforms: &mut Vec, summary: TextSummary) { + if let Some(last_transform) = transforms.last_mut() { + if last_transform.is_isomorphic() { + last_transform.summary.input += &summary; + last_transform.summary.output += &summary; + return; + } + } + transforms.push(Transform::isomorphic(summary)); +} + +trait SumTreeExt { + fn push_or_extend(&mut self, transform: Transform); +} + +impl SumTreeExt for SumTree { + fn push_or_extend(&mut self, transform: Transform) { + let mut transform = Some(transform); + self.update_last( + |last_transform| { + if last_transform.is_isomorphic() && transform.as_ref().unwrap().is_isomorphic() { + let transform = transform.take().unwrap(); + last_transform.summary.input += &transform.summary.input; + last_transform.summary.output += &transform.summary.output; + } + }, + &(), + ); + + if let Some(transform) = transform { + self.push(transform, &()); + } + } +} + +impl WrapPoint { + pub fn new(row: u32, column: u32) -> Self { + Self(Point::new(row, column)) + } + + pub fn row(self) -> u32 { + self.0.row + } + + pub fn row_mut(&mut self) -> &mut u32 { + &mut self.0.row + } + + pub fn column(self) -> u32 { + self.0.column + } + + pub fn column_mut(&mut self) -> &mut u32 { + &mut self.0.column + } +} + +impl sum_tree::Summary for TransformSummary { + type Context = (); + + fn add_summary(&mut self, other: &Self, _: &()) { + self.input += &other.input; + self.output += &other.output; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += summary.input.lines; + } +} + +impl<'a> sum_tree::SeekTarget<'a, TransformSummary, TransformSummary> for TabPoint { + fn cmp(&self, cursor_location: &TransformSummary, _: &()) -> std::cmp::Ordering { + Ord::cmp(&self.0, &cursor_location.input.lines) + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += summary.output.lines; + } +} + +fn consolidate_wrap_edits(edits: &mut Vec) { + let mut i = 1; + while i < edits.len() { + let edit = edits[i].clone(); + let prev_edit = &mut edits[i - 1]; + if prev_edit.old.end >= edit.old.start { + prev_edit.old.end = edit.old.end; + prev_edit.new.end = edit.new.end; + edits.remove(i); + continue; + } + i += 1; + } +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::{ +// display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap}, +// MultiBuffer, +// }; +// use gpui::test::observe; +// use rand::prelude::*; +// use settings::SettingsStore; +// use smol::stream::StreamExt; +// use std::{cmp, env, num::NonZeroU32}; +// use text::Rope; + +// #[gpui::test(iterations = 100)] +// async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) { +// init_test(cx); + +// cx.foreground().set_block_on_ticks(0..=50); +// let operations = env::var("OPERATIONS") +// .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) +// .unwrap_or(10); + +// let font_cache = cx.font_cache().clone(); +// let font_system = cx.platform().fonts(); +// let mut wrap_width = if rng.gen_bool(0.1) { +// None +// } else { +// Some(rng.gen_range(0.0..=1000.0)) +// }; +// let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap(); +// let family_id = font_cache +// .load_family(&["Helvetica"], &Default::default()) +// .unwrap(); +// let font_id = font_cache +// .select_font(family_id, &Default::default()) +// .unwrap(); +// let font_size = 14.0; + +// log::info!("Tab size: {}", tab_size); +// log::info!("Wrap width: {:?}", wrap_width); + +// let buffer = cx.update(|cx| { +// if rng.gen() { +// MultiBuffer::build_random(&mut rng, cx) +// } else { +// let len = rng.gen_range(0..10); +// let text = util::RandomCharIter::new(&mut rng) +// .take(len) +// .collect::(); +// MultiBuffer::build_simple(&text, cx) +// } +// }); +// let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); +// log::info!("Buffer text: {:?}", buffer_snapshot.text()); +// let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); +// log::info!("InlayMap text: {:?}", inlay_snapshot.text()); +// let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone()); +// log::info!("FoldMap text: {:?}", fold_snapshot.text()); +// let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size); +// let tabs_snapshot = tab_map.set_max_expansion_column(32); +// log::info!("TabMap text: {:?}", tabs_snapshot.text()); + +// let mut line_wrapper = LineWrapper::new(font_id, font_size, font_system); +// let unwrapped_text = tabs_snapshot.text(); +// let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper); + +// let (wrap_map, _) = +// cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx)); +// let mut notifications = observe(&wrap_map, cx); + +// if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) { +// notifications.next().await.unwrap(); +// } + +// let (initial_snapshot, _) = wrap_map.update(cx, |map, cx| { +// assert!(!map.is_rewrapping()); +// map.sync(tabs_snapshot.clone(), Vec::new(), cx) +// }); + +// let actual_text = initial_snapshot.text(); +// assert_eq!( +// actual_text, expected_text, +// "unwrapped text is: {:?}", +// unwrapped_text +// ); +// log::info!("Wrapped text: {:?}", actual_text); + +// let mut next_inlay_id = 0; +// let mut edits = Vec::new(); +// for _i in 0..operations { +// log::info!("{} ==============================================", _i); + +// let mut buffer_edits = Vec::new(); +// match rng.gen_range(0..=100) { +// 0..=19 => { +// wrap_width = if rng.gen_bool(0.2) { +// None +// } else { +// Some(rng.gen_range(0.0..=1000.0)) +// }; +// log::info!("Setting wrap width to {:?}", wrap_width); +// wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); +// } +// 20..=39 => { +// for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { +// let (tabs_snapshot, tab_edits) = +// tab_map.sync(fold_snapshot, fold_edits, tab_size); +// let (mut snapshot, wrap_edits) = +// wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx)); +// snapshot.check_invariants(); +// snapshot.verify_chunks(&mut rng); +// edits.push((snapshot, wrap_edits)); +// } +// } +// 40..=59 => { +// let (inlay_snapshot, inlay_edits) = +// inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); +// let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); +// let (tabs_snapshot, tab_edits) = +// tab_map.sync(fold_snapshot, fold_edits, tab_size); +// let (mut snapshot, wrap_edits) = +// wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx)); +// snapshot.check_invariants(); +// snapshot.verify_chunks(&mut rng); +// edits.push((snapshot, wrap_edits)); +// } +// _ => { +// buffer.update(cx, |buffer, cx| { +// let subscription = buffer.subscribe(); +// let edit_count = rng.gen_range(1..=5); +// buffer.randomly_mutate(&mut rng, edit_count, cx); +// buffer_snapshot = buffer.snapshot(cx); +// buffer_edits.extend(subscription.consume()); +// }); +// } +// } + +// log::info!("Buffer text: {:?}", buffer_snapshot.text()); +// let (inlay_snapshot, inlay_edits) = +// inlay_map.sync(buffer_snapshot.clone(), buffer_edits); +// log::info!("InlayMap text: {:?}", inlay_snapshot.text()); +// let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); +// log::info!("FoldMap text: {:?}", fold_snapshot.text()); +// let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); +// log::info!("TabMap text: {:?}", tabs_snapshot.text()); + +// let unwrapped_text = tabs_snapshot.text(); +// let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper); +// let (mut snapshot, wrap_edits) = +// wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot.clone(), tab_edits, cx)); +// snapshot.check_invariants(); +// snapshot.verify_chunks(&mut rng); +// edits.push((snapshot, wrap_edits)); + +// if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) && rng.gen_bool(0.4) { +// log::info!("Waiting for wrapping to finish"); +// while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) { +// notifications.next().await.unwrap(); +// } +// wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty())); +// } + +// if !wrap_map.read_with(cx, |map, _| map.is_rewrapping()) { +// let (mut wrapped_snapshot, wrap_edits) = +// wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx)); +// let actual_text = wrapped_snapshot.text(); +// let actual_longest_row = wrapped_snapshot.longest_row(); +// log::info!("Wrapping finished: {:?}", actual_text); +// wrapped_snapshot.check_invariants(); +// wrapped_snapshot.verify_chunks(&mut rng); +// edits.push((wrapped_snapshot.clone(), wrap_edits)); +// assert_eq!( +// actual_text, expected_text, +// "unwrapped text is: {:?}", +// unwrapped_text +// ); + +// let mut summary = TextSummary::default(); +// for (ix, item) in wrapped_snapshot +// .transforms +// .items(&()) +// .into_iter() +// .enumerate() +// { +// summary += &item.summary.output; +// log::info!("{} summary: {:?}", ix, item.summary.output,); +// } + +// if tab_size.get() == 1 +// || !wrapped_snapshot +// .tab_snapshot +// .fold_snapshot +// .text() +// .contains('\t') +// { +// let mut expected_longest_rows = Vec::new(); +// let mut longest_line_len = -1; +// for (row, line) in expected_text.split('\n').enumerate() { +// let line_char_count = line.chars().count() as isize; +// if line_char_count > longest_line_len { +// expected_longest_rows.clear(); +// longest_line_len = line_char_count; +// } +// if line_char_count >= longest_line_len { +// expected_longest_rows.push(row as u32); +// } +// } + +// assert!( +// expected_longest_rows.contains(&actual_longest_row), +// "incorrect longest row {}. expected {:?} with length {}", +// actual_longest_row, +// expected_longest_rows, +// longest_line_len, +// ) +// } +// } +// } + +// let mut initial_text = Rope::from(initial_snapshot.text().as_str()); +// for (snapshot, patch) in edits { +// let snapshot_text = Rope::from(snapshot.text().as_str()); +// for edit in &patch { +// let old_start = initial_text.point_to_offset(Point::new(edit.new.start, 0)); +// let old_end = initial_text.point_to_offset(cmp::min( +// Point::new(edit.new.start + edit.old.len() as u32, 0), +// initial_text.max_point(), +// )); +// let new_start = snapshot_text.point_to_offset(Point::new(edit.new.start, 0)); +// let new_end = snapshot_text.point_to_offset(cmp::min( +// Point::new(edit.new.end, 0), +// snapshot_text.max_point(), +// )); +// let new_text = snapshot_text +// .chunks_in_range(new_start..new_end) +// .collect::(); + +// initial_text.replace(old_start..old_end, &new_text); +// } +// assert_eq!(initial_text.to_string(), snapshot_text.to_string()); +// } + +// if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) { +// log::info!("Waiting for wrapping to finish"); +// while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) { +// notifications.next().await.unwrap(); +// } +// } +// wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty())); +// } + +// fn init_test(cx: &mut gpui::TestAppContext) { +// cx.foreground().forbid_parking(); +// cx.update(|cx| { +// cx.set_global(SettingsStore::test(cx)); +// theme::init((), cx); +// }); +// } + +// fn wrap_text( +// unwrapped_text: &str, +// wrap_width: Option, +// line_wrapper: &mut LineWrapper, +// ) -> String { +// if let Some(wrap_width) = wrap_width { +// let mut wrapped_text = String::new(); +// for (row, line) in unwrapped_text.split('\n').enumerate() { +// if row > 0 { +// wrapped_text.push('\n') +// } + +// let mut prev_ix = 0; +// for boundary in line_wrapper.wrap_line(line, wrap_width) { +// wrapped_text.push_str(&line[prev_ix..boundary.ix]); +// wrapped_text.push('\n'); +// wrapped_text.push_str(&" ".repeat(boundary.next_indent as usize)); +// prev_ix = boundary.ix; +// } +// wrapped_text.push_str(&line[prev_ix..]); +// } +// wrapped_text +// } else { +// unwrapped_text.to_string() +// } +// } + +// impl WrapSnapshot { +// pub fn text(&self) -> String { +// self.text_chunks(0).collect() +// } + +// pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator { +// self.chunks( +// wrap_row..self.max_point().row() + 1, +// false, +// Highlights::default(), +// ) +// .map(|h| h.text) +// } + +// fn verify_chunks(&mut self, rng: &mut impl Rng) { +// for _ in 0..5 { +// let mut end_row = rng.gen_range(0..=self.max_point().row()); +// let start_row = rng.gen_range(0..=end_row); +// end_row += 1; + +// let mut expected_text = self.text_chunks(start_row).collect::(); +// if expected_text.ends_with('\n') { +// expected_text.push('\n'); +// } +// let mut expected_text = expected_text +// .lines() +// .take((end_row - start_row) as usize) +// .collect::>() +// .join("\n"); +// if end_row <= self.max_point().row() { +// expected_text.push('\n'); +// } + +// let actual_text = self +// .chunks(start_row..end_row, true, Highlights::default()) +// .map(|c| c.text) +// .collect::(); +// assert_eq!( +// expected_text, +// actual_text, +// "chunks != highlighted_chunks for rows {:?}", +// start_row..end_row +// ); +// } +// } +// } +// } diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs new file mode 100644 index 0000000000..ea747de5de --- /dev/null +++ b/crates/editor2/src/editor.rs @@ -0,0 +1,10120 @@ +#![allow(unused)] +mod blink_manager; +pub mod display_map; +mod editor_settings; +mod element; +mod inlay_hint_cache; + +mod git; +mod highlight_matching_bracket; +mod hover_popover; +pub mod items; +mod link_go_to_definition; +mod mouse_context_menu; +pub mod movement; +mod persistence; +pub mod scroll; +pub mod selections_collection; + +#[cfg(test)] +mod editor_tests; +#[cfg(any(test, feature = "test-support"))] +pub mod test; +use aho_corasick::AhoCorasick; +use anyhow::{Context as _, Result}; +use blink_manager::BlinkManager; +use client::{ClickhouseEvent, Client, Collaborator, ParticipantIndex, TelemetrySettings}; +use clock::ReplicaId; +use collections::{BTreeMap, HashMap, HashSet, VecDeque}; +use copilot::Copilot; +pub use display_map::DisplayPoint; +use display_map::*; +pub use editor_settings::EditorSettings; +pub use element::{ + Cursor, EditorElement, HighlightedRange, HighlightedRangeLine, LineWithInvisibles, +}; +use futures::FutureExt; +use fuzzy::{StringMatch, StringMatchCandidate}; +use gpui::{ + div, AnyElement, AppContext, BackgroundExecutor, Context, Div, Element, EventEmitter, + FocusHandle, Hsla, Model, Pixels, Render, Subscription, Task, TextStyle, View, ViewContext, + VisualContext, WeakView, WindowContext, +}; +use highlight_matching_bracket::refresh_matching_bracket_highlights; +use hover_popover::{hide_hover, HoverState}; +use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy}; +pub use items::MAX_TAB_TITLE_LEN; +use itertools::Itertools; +pub use language::{char_kind, CharKind}; +use language::{ + language_settings::{self, all_language_settings, InlayHintSettings}, + point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, Completion, CursorShape, + Diagnostic, Language, LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection, + SelectionGoal, TransactionId, +}; +use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState}; +use lsp::{Documentation, LanguageServerId}; +pub use multi_buffer::{ + Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset, + ToPoint, +}; +use ordered_float::OrderedFloat; +use parking_lot::RwLock; +use project::{FormatTrigger, Location, Project}; +use rpc::proto::*; +use scroll::{ + autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, +}; +use selections_collection::{MutableSelectionsCollection, SelectionsCollection}; +use serde::{Deserialize, Serialize}; +use settings::{Settings, SettingsStore}; +use std::{ + any::TypeId, + borrow::Cow, + cmp::{self, Reverse}, + ops::{ControlFlow, Deref, DerefMut, Range}, + path::Path, + sync::Arc, + time::{Duration, Instant}, +}; +pub use sum_tree::Bias; +use sum_tree::TreeMap; +use text::Rope; +use theme::ThemeColors; +use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; +use workspace::{ItemNavHistory, SplitDirection, ViewId, Workspace}; + +const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); +const MAX_LINE_LEN: usize = 1024; +const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10; +const MAX_SELECTION_HISTORY_LEN: usize = 1024; +const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75); +pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250); +pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75); + +pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); + +// pub fn render_parsed_markdown( +// parsed: &language::ParsedMarkdown, +// editor_style: &EditorStyle, +// workspace: Option>, +// cx: &mut ViewContext, +// ) -> Text { +// enum RenderedMarkdown {} + +// let parsed = parsed.clone(); +// let view_id = cx.view_id(); +// let code_span_background_color = editor_style.document_highlight_read_background; + +// let mut region_id = 0; + +// todo!() +// // Text::new(parsed.text, editor_style.text.clone()) +// // .with_highlights( +// // parsed +// // .highlights +// // .iter() +// // .filter_map(|(range, highlight)| { +// // let highlight = highlight.to_highlight_style(&editor_style.syntax)?; +// // Some((range.clone(), highlight)) +// // }) +// // .collect::>(), +// // ) +// // .with_custom_runs(parsed.region_ranges, move |ix, bounds, cx| { +// // region_id += 1; +// // let region = parsed.regions[ix].clone(); + +// // if let Some(link) = region.link { +// // cx.scene().push_cursor_region(CursorRegion { +// // bounds, +// // style: CursorStyle::PointingHand, +// // }); +// // cx.scene().push_mouse_region( +// // MouseRegion::new::<(RenderedMarkdown, Tag)>(view_id, region_id, bounds) +// // .on_down::(MouseButton::Left, move |_, _, cx| match &link { +// // markdown::Link::Web { url } => cx.platform().open_url(url), +// // markdown::Link::Path { path } => { +// // if let Some(workspace) = &workspace { +// // _ = workspace.update(cx, |workspace, cx| { +// // workspace.open_abs_path(path.clone(), false, cx).detach(); +// // }); +// // } +// // } +// // }), +// // ); +// // } + +// // if region.code { +// // cx.draw_quad(Quad { +// // bounds, +// // background: Some(code_span_background_color), +// // corner_radii: (2.0).into(), +// // order: todo!(), +// // content_mask: todo!(), +// // border_color: todo!(), +// // border_widths: todo!(), +// // }); +// // } +// // }) +// // .with_soft_wrap(true) +// } + +#[derive(Clone, Deserialize, PartialEq, Default)] +pub struct SelectNext { + #[serde(default)] + pub replace_newest: bool, +} + +#[derive(Clone, Deserialize, PartialEq, Default)] +pub struct SelectPrevious { + #[serde(default)] + pub replace_newest: bool, +} + +#[derive(Clone, Deserialize, PartialEq, Default)] +pub struct SelectAllMatches { + #[serde(default)] + pub replace_newest: bool, +} + +#[derive(Clone, Deserialize, PartialEq)] +pub struct SelectToBeginningOfLine { + #[serde(default)] + stop_at_soft_wraps: bool, +} + +#[derive(Clone, Default, Deserialize, PartialEq)] +pub struct MovePageUp { + #[serde(default)] + center_cursor: bool, +} + +#[derive(Clone, Default, Deserialize, PartialEq)] +pub struct MovePageDown { + #[serde(default)] + center_cursor: bool, +} + +#[derive(Clone, Deserialize, PartialEq)] +pub struct SelectToEndOfLine { + #[serde(default)] + stop_at_soft_wraps: bool, +} + +#[derive(Clone, Deserialize, PartialEq)] +pub struct ToggleCodeActions { + #[serde(default)] + pub deployed_from_indicator: bool, +} + +#[derive(Clone, Default, Deserialize, PartialEq)] +pub struct ConfirmCompletion { + #[serde(default)] + pub item_ix: Option, +} + +#[derive(Clone, Default, Deserialize, PartialEq)] +pub struct ConfirmCodeAction { + #[serde(default)] + pub item_ix: Option, +} + +#[derive(Clone, Default, Deserialize, PartialEq)] +pub struct ToggleComments { + #[serde(default)] + pub advance_downwards: bool, +} + +#[derive(Clone, Default, Deserialize, PartialEq)] +pub struct FoldAt { + pub buffer_row: u32, +} + +#[derive(Clone, Default, Deserialize, PartialEq)] +pub struct UnfoldAt { + pub buffer_row: u32, +} + +#[derive(Clone, Default, Deserialize, PartialEq)] +pub struct GutterHover { + pub hovered: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum InlayId { + Suggestion(usize), + Hint(usize), +} + +impl InlayId { + fn id(&self) -> usize { + match self { + Self::Suggestion(id) => *id, + Self::Hint(id) => *id, + } + } +} + +// actions!( +// editor, +// [ +// Cancel, +// Backspace, +// Delete, +// Newline, +// NewlineAbove, +// NewlineBelow, +// GoToDiagnostic, +// GoToPrevDiagnostic, +// GoToHunk, +// GoToPrevHunk, +// Indent, +// Outdent, +// DeleteLine, +// DeleteToPreviousWordStart, +// DeleteToPreviousSubwordStart, +// DeleteToNextWordEnd, +// DeleteToNextSubwordEnd, +// DeleteToBeginningOfLine, +// DeleteToEndOfLine, +// CutToEndOfLine, +// DuplicateLine, +// MoveLineUp, +// MoveLineDown, +// JoinLines, +// SortLinesCaseSensitive, +// SortLinesCaseInsensitive, +// ReverseLines, +// ShuffleLines, +// ConvertToUpperCase, +// ConvertToLowerCase, +// ConvertToTitleCase, +// ConvertToSnakeCase, +// ConvertToKebabCase, +// ConvertToUpperCamelCase, +// ConvertToLowerCamelCase, +// Transpose, +// Cut, +// Copy, +// Paste, +// Undo, +// Redo, +// MoveUp, +// PageUp, +// MoveDown, +// PageDown, +// MoveLeft, +// MoveRight, +// MoveToPreviousWordStart, +// MoveToPreviousSubwordStart, +// MoveToNextWordEnd, +// MoveToNextSubwordEnd, +// MoveToBeginningOfLine, +// MoveToEndOfLine, +// MoveToStartOfParagraph, +// MoveToEndOfParagraph, +// MoveToBeginning, +// MoveToEnd, +// SelectUp, +// SelectDown, +// SelectLeft, +// SelectRight, +// SelectToPreviousWordStart, +// SelectToPreviousSubwordStart, +// SelectToNextWordEnd, +// SelectToNextSubwordEnd, +// SelectToStartOfParagraph, +// SelectToEndOfParagraph, +// SelectToBeginning, +// SelectToEnd, +// SelectAll, +// SelectLine, +// SplitSelectionIntoLines, +// AddSelectionAbove, +// AddSelectionBelow, +// Tab, +// TabPrev, +// ShowCharacterPalette, +// SelectLargerSyntaxNode, +// SelectSmallerSyntaxNode, +// GoToDefinition, +// GoToDefinitionSplit, +// GoToTypeDefinition, +// GoToTypeDefinitionSplit, +// MoveToEnclosingBracket, +// UndoSelection, +// RedoSelection, +// FindAllReferences, +// Rename, +// ConfirmRename, +// Fold, +// UnfoldLines, +// FoldSelectedRanges, +// ShowCompletions, +// OpenExcerpts, +// RestartLanguageServer, +// Hover, +// Format, +// ToggleSoftWrap, +// ToggleInlayHints, +// RevealInFinder, +// CopyPath, +// CopyRelativePath, +// CopyHighlightJson, +// ContextMenuFirst, +// ContextMenuPrev, +// ContextMenuNext, +// ContextMenuLast, +// ] +// ); + +// impl_actions!( +// editor, +// [ +// SelectNext, +// SelectPrevious, +// SelectAllMatches, +// SelectToBeginningOfLine, +// SelectToEndOfLine, +// ToggleCodeActions, +// MovePageUp, +// MovePageDown, +// ConfirmCompletion, +// ConfirmCodeAction, +// ToggleComments, +// FoldAt, +// UnfoldAt, +// GutterHover +// ] +// ); + +// todo!(revisit these actions) +pub struct ShowCompletions; +pub struct Rename; +pub struct GoToDefinition; +pub struct GoToTypeDefinition; +pub struct GoToDefinitionSplit; +pub struct GoToTypeDefinitionSplit; + +enum DocumentHighlightRead {} +enum DocumentHighlightWrite {} +enum InputComposition {} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum Direction { + Prev, + Next, +} + +pub fn init_settings(cx: &mut AppContext) { + EditorSettings::register(cx); +} + +pub fn init(cx: &mut AppContext) { + init_settings(cx); + // cx.add_action(Editor::new_file); + // cx.add_action(Editor::new_file_in_direction); + // cx.add_action(Editor::cancel); + // cx.add_action(Editor::newline); + // cx.add_action(Editor::newline_above); + // cx.add_action(Editor::newline_below); + // cx.add_action(Editor::backspace); + // cx.add_action(Editor::delete); + // cx.add_action(Editor::tab); + // cx.add_action(Editor::tab_prev); + // cx.add_action(Editor::indent); + // cx.add_action(Editor::outdent); + // cx.add_action(Editor::delete_line); + // cx.add_action(Editor::join_lines); + // cx.add_action(Editor::sort_lines_case_sensitive); + // cx.add_action(Editor::sort_lines_case_insensitive); + // cx.add_action(Editor::reverse_lines); + // cx.add_action(Editor::shuffle_lines); + // cx.add_action(Editor::convert_to_upper_case); + // cx.add_action(Editor::convert_to_lower_case); + // cx.add_action(Editor::convert_to_title_case); + // cx.add_action(Editor::convert_to_snake_case); + // cx.add_action(Editor::convert_to_kebab_case); + // cx.add_action(Editor::convert_to_upper_camel_case); + // cx.add_action(Editor::convert_to_lower_camel_case); + // cx.add_action(Editor::delete_to_previous_word_start); + // cx.add_action(Editor::delete_to_previous_subword_start); + // cx.add_action(Editor::delete_to_next_word_end); + // cx.add_action(Editor::delete_to_next_subword_end); + // cx.add_action(Editor::delete_to_beginning_of_line); + // cx.add_action(Editor::delete_to_end_of_line); + // cx.add_action(Editor::cut_to_end_of_line); + // cx.add_action(Editor::duplicate_line); + // cx.add_action(Editor::move_line_up); + // cx.add_action(Editor::move_line_down); + // cx.add_action(Editor::transpose); + // cx.add_action(Editor::cut); + // cx.add_action(Editor::copy); + // cx.add_action(Editor::paste); + // cx.add_action(Editor::undo); + // cx.add_action(Editor::redo); + // cx.add_action(Editor::move_up); + // cx.add_action(Editor::move_page_up); + // cx.add_action(Editor::move_down); + // cx.add_action(Editor::move_page_down); + // cx.add_action(Editor::next_screen); + // cx.add_action(Editor::move_left); + // cx.add_action(Editor::move_right); + // cx.add_action(Editor::move_to_previous_word_start); + // cx.add_action(Editor::move_to_previous_subword_start); + // cx.add_action(Editor::move_to_next_word_end); + // cx.add_action(Editor::move_to_next_subword_end); + // cx.add_action(Editor::move_to_beginning_of_line); + // cx.add_action(Editor::move_to_end_of_line); + // cx.add_action(Editor::move_to_start_of_paragraph); + // cx.add_action(Editor::move_to_end_of_paragraph); + // cx.add_action(Editor::move_to_beginning); + // cx.add_action(Editor::move_to_end); + // cx.add_action(Editor::select_up); + // cx.add_action(Editor::select_down); + // cx.add_action(Editor::select_left); + // cx.add_action(Editor::select_right); + // cx.add_action(Editor::select_to_previous_word_start); + // cx.add_action(Editor::select_to_previous_subword_start); + // cx.add_action(Editor::select_to_next_word_end); + // cx.add_action(Editor::select_to_next_subword_end); + // cx.add_action(Editor::select_to_beginning_of_line); + // cx.add_action(Editor::select_to_end_of_line); + // cx.add_action(Editor::select_to_start_of_paragraph); + // cx.add_action(Editor::select_to_end_of_paragraph); + // cx.add_action(Editor::select_to_beginning); + // cx.add_action(Editor::select_to_end); + // cx.add_action(Editor::select_all); + // cx.add_action(Editor::select_all_matches); + // cx.add_action(Editor::select_line); + // 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::select_previous); + // cx.add_action(Editor::toggle_comments); + // cx.add_action(Editor::select_larger_syntax_node); + // cx.add_action(Editor::select_smaller_syntax_node); + // cx.add_action(Editor::move_to_enclosing_bracket); + // cx.add_action(Editor::undo_selection); + // cx.add_action(Editor::redo_selection); + // cx.add_action(Editor::go_to_diagnostic); + // cx.add_action(Editor::go_to_prev_diagnostic); + // cx.add_action(Editor::go_to_hunk); + // cx.add_action(Editor::go_to_prev_hunk); + // cx.add_action(Editor::go_to_definition); + // cx.add_action(Editor::go_to_definition_split); + // cx.add_action(Editor::go_to_type_definition); + // cx.add_action(Editor::go_to_type_definition_split); + // cx.add_action(Editor::fold); + // cx.add_action(Editor::fold_at); + // cx.add_action(Editor::unfold_lines); + // cx.add_action(Editor::unfold_at); + // cx.add_action(Editor::gutter_hover); + // cx.add_action(Editor::fold_selected_ranges); + // cx.add_action(Editor::show_completions); + // cx.add_action(Editor::toggle_code_actions); + // cx.add_action(Editor::open_excerpts); + // cx.add_action(Editor::toggle_soft_wrap); + // cx.add_action(Editor::toggle_inlay_hints); + // cx.add_action(Editor::reveal_in_finder); + // cx.add_action(Editor::copy_path); + // cx.add_action(Editor::copy_relative_path); + // cx.add_action(Editor::copy_highlight_json); + // cx.add_async_action(Editor::format); + // cx.add_action(Editor::restart_language_server); + // cx.add_action(Editor::show_character_palette); + // cx.add_async_action(Editor::confirm_completion); + // cx.add_async_action(Editor::confirm_code_action); + // cx.add_async_action(Editor::rename); + // cx.add_async_action(Editor::confirm_rename); + // cx.add_async_action(Editor::find_all_references); + // cx.add_action(Editor::next_copilot_suggestion); + // cx.add_action(Editor::previous_copilot_suggestion); + // cx.add_action(Editor::copilot_suggest); + // cx.add_action(Editor::context_menu_first); + // cx.add_action(Editor::context_menu_prev); + // cx.add_action(Editor::context_menu_next); + // cx.add_action(Editor::context_menu_last); + + hover_popover::init(cx); + scroll::actions::init(cx); + + workspace::register_project_item::(cx); + workspace::register_followable_item::(cx); + workspace::register_deserializable_item::(cx); +} + +trait InvalidationRegion { + fn ranges(&self) -> &[Range]; +} + +#[derive(Clone, Debug, PartialEq)] +pub enum SelectPhase { + Begin { + position: DisplayPoint, + add: bool, + click_count: usize, + }, + BeginColumnar { + position: DisplayPoint, + goal_column: u32, + }, + Extend { + position: DisplayPoint, + click_count: usize, + }, + Update { + position: DisplayPoint, + goal_column: u32, + scroll_position: gpui::Point, + }, + End, +} + +#[derive(Clone, Debug)] +pub enum SelectMode { + Character, + Word(Range), + Line(Range), + All, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum EditorMode { + SingleLine, + AutoHeight { max_lines: usize }, + Full, +} + +#[derive(Clone, Debug)] +pub enum SoftWrap { + None, + EditorWidth, + Column(u32), +} + +#[derive(Clone)] +pub struct EditorStyle { + pub text: TextStyle, + pub line_height_scalar: f32, + // pub placeholder_text: Option, + // pub theme: theme::Editor, + pub theme_id: usize, +} + +type CompletionId = usize; + +// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor; +// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option; + +type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Vec>); +type InlayBackgroundHighlight = (fn(&ThemeColors) -> Hsla, Vec); + +pub struct Editor { + handle: WeakView, + focus_handle: FocusHandle, + buffer: Model, + display_map: Model, + pub selections: SelectionsCollection, + pub scroll_manager: ScrollManager, + columnar_selection_tail: Option, + add_selections_state: Option, + select_next_state: Option, + select_prev_state: Option, + selection_history: SelectionHistory, + autoclose_regions: Vec, + snippet_stack: InvalidationStack, + select_larger_syntax_node_stack: Vec]>>, + ime_transaction: Option, + active_diagnostics: Option, + soft_wrap_mode_override: Option, + // get_field_editor_theme: Option>, + // override_text_style: Option>, + project: Option>, + collaboration_hub: Option>, + focused: bool, + blink_manager: Model, + pub show_local_selections: bool, + mode: EditorMode, + show_gutter: bool, + show_wrap_guides: Option, + placeholder_text: Option>, + highlighted_rows: Option>, + background_highlights: BTreeMap, + inlay_background_highlights: TreeMap, InlayBackgroundHighlight>, + nav_history: Option, + context_menu: RwLock>, + // mouse_context_menu: View, + completion_tasks: Vec<(CompletionId, Task>)>, + next_completion_id: CompletionId, + available_code_actions: Option<(Model, Arc<[CodeAction]>)>, + code_actions_task: Option>, + document_highlights_task: Option>, + pending_rename: Option, + searchable: bool, + cursor_shape: CursorShape, + collapse_matches: bool, + autoindent_mode: Option, + workspace: Option<(WeakView, i64)>, + // keymap_context_layers: BTreeMap, + input_enabled: bool, + read_only: bool, + leader_peer_id: Option, + remote_id: Option, + hover_state: HoverState, + gutter_hovered: bool, + link_go_to_definition_state: LinkGoToDefinitionState, + copilot_state: CopilotState, + inlay_hint_cache: InlayHintCache, + next_inlay_id: usize, + _subscriptions: Vec, + pixel_position_of_newest_cursor: Option>, +} + +pub struct EditorSnapshot { + pub mode: EditorMode, + pub show_gutter: bool, + pub display_snapshot: DisplaySnapshot, + pub placeholder_text: Option>, + is_focused: bool, + scroll_anchor: ScrollAnchor, + ongoing_scroll: OngoingScroll, +} + +pub struct RemoteSelection { + pub replica_id: ReplicaId, + pub selection: Selection, + pub cursor_shape: CursorShape, + pub peer_id: PeerId, + pub line_mode: bool, + pub participant_index: Option, +} + +#[derive(Clone, Debug)] +struct SelectionHistoryEntry { + selections: Arc<[Selection]>, + select_next_state: Option, + select_prev_state: Option, + add_selections_state: Option, +} + +enum SelectionHistoryMode { + Normal, + Undoing, + Redoing, +} + +impl Default for SelectionHistoryMode { + fn default() -> Self { + Self::Normal + } +} + +#[derive(Default)] +struct SelectionHistory { + #[allow(clippy::type_complexity)] + selections_by_transaction: + HashMap]>, Option]>>)>, + mode: SelectionHistoryMode, + undo_stack: VecDeque, + redo_stack: VecDeque, +} + +impl SelectionHistory { + fn insert_transaction( + &mut self, + transaction_id: TransactionId, + selections: Arc<[Selection]>, + ) { + self.selections_by_transaction + .insert(transaction_id, (selections, None)); + } + + #[allow(clippy::type_complexity)] + fn transaction( + &self, + transaction_id: TransactionId, + ) -> Option<&(Arc<[Selection]>, Option]>>)> { + self.selections_by_transaction.get(&transaction_id) + } + + #[allow(clippy::type_complexity)] + fn transaction_mut( + &mut self, + transaction_id: TransactionId, + ) -> Option<&mut (Arc<[Selection]>, Option]>>)> { + self.selections_by_transaction.get_mut(&transaction_id) + } + + fn push(&mut self, entry: SelectionHistoryEntry) { + if !entry.selections.is_empty() { + match self.mode { + SelectionHistoryMode::Normal => { + self.push_undo(entry); + self.redo_stack.clear(); + } + SelectionHistoryMode::Undoing => self.push_redo(entry), + SelectionHistoryMode::Redoing => self.push_undo(entry), + } + } + } + + fn push_undo(&mut self, entry: SelectionHistoryEntry) { + if self + .undo_stack + .back() + .map_or(true, |e| e.selections != entry.selections) + { + self.undo_stack.push_back(entry); + if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN { + self.undo_stack.pop_front(); + } + } + } + + fn push_redo(&mut self, entry: SelectionHistoryEntry) { + if self + .redo_stack + .back() + .map_or(true, |e| e.selections != entry.selections) + { + self.redo_stack.push_back(entry); + if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN { + self.redo_stack.pop_front(); + } + } + } +} + +#[derive(Clone, Debug)] +struct AddSelectionsState { + above: bool, + stack: Vec, +} + +#[derive(Clone)] +struct SelectNextState { + query: AhoCorasick, + wordwise: bool, + done: bool, +} + +impl std::fmt::Debug for SelectNextState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(std::any::type_name::()) + .field("wordwise", &self.wordwise) + .field("done", &self.done) + .finish() + } +} + +#[derive(Debug)] +struct AutocloseRegion { + selection_id: usize, + range: Range, + pair: BracketPair, +} + +#[derive(Debug)] +struct SnippetState { + ranges: Vec>>, + active_index: usize, +} + +pub struct RenameState { + pub range: Range, + pub old_name: Arc, + pub editor: View, + block_id: BlockId, +} + +struct InvalidationStack(Vec); + +enum ContextMenu { + Completions(CompletionsMenu), + CodeActions(CodeActionsMenu), +} + +impl ContextMenu { + fn select_first( + &mut self, + project: Option<&Model>, + cx: &mut ViewContext, + ) -> bool { + if self.visible() { + match self { + ContextMenu::Completions(menu) => menu.select_first(project, cx), + ContextMenu::CodeActions(menu) => menu.select_first(cx), + } + true + } else { + false + } + } + + fn select_prev( + &mut self, + project: Option<&Model>, + cx: &mut ViewContext, + ) -> bool { + if self.visible() { + match self { + ContextMenu::Completions(menu) => menu.select_prev(project, cx), + ContextMenu::CodeActions(menu) => menu.select_prev(cx), + } + true + } else { + false + } + } + + fn select_next( + &mut self, + project: Option<&Model>, + cx: &mut ViewContext, + ) -> bool { + if self.visible() { + match self { + ContextMenu::Completions(menu) => menu.select_next(project, cx), + ContextMenu::CodeActions(menu) => menu.select_next(cx), + } + true + } else { + false + } + } + + fn select_last( + &mut self, + project: Option<&Model>, + cx: &mut ViewContext, + ) -> bool { + if self.visible() { + match self { + ContextMenu::Completions(menu) => menu.select_last(project, cx), + ContextMenu::CodeActions(menu) => menu.select_last(cx), + } + true + } else { + false + } + } + + fn visible(&self) -> bool { + match self { + ContextMenu::Completions(menu) => menu.visible(), + ContextMenu::CodeActions(menu) => menu.visible(), + } + } + + fn render( + &self, + cursor_position: DisplayPoint, + style: EditorStyle, + workspace: Option>, + cx: &mut ViewContext, + ) -> (DisplayPoint, AnyElement) { + todo!() + // match self { + // ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)), + // ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx), + // } + } +} + +#[derive(Clone)] +struct CompletionsMenu { + id: CompletionId, + initial_position: Anchor, + buffer: Model, + completions: Arc>>, + match_candidates: Arc<[StringMatchCandidate]>, + matches: Arc<[StringMatch]>, + selected_item: usize, + list: UniformListState, +} + +// todo!(this is fake) +#[derive(Clone, Default)] +struct UniformListState; + +// todo!(this is fake) +impl UniformListState { + pub fn scroll_to(&mut self, target: ScrollTarget) {} +} + +// todo!(this is somewhat fake) +#[derive(Debug)] +pub enum ScrollTarget { + Show(usize), + Center(usize), +} + +impl CompletionsMenu { + fn select_first(&mut self, project: Option<&Model>, cx: &mut ViewContext) { + self.selected_item = 0; + self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.attempt_resolve_selected_completion_documentation(project, cx); + cx.notify(); + } + + fn select_prev(&mut self, project: Option<&Model>, cx: &mut ViewContext) { + if self.selected_item > 0 { + self.selected_item -= 1; + } else { + self.selected_item = self.matches.len() - 1; + } + self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.attempt_resolve_selected_completion_documentation(project, cx); + cx.notify(); + } + + fn select_next(&mut self, project: Option<&Model>, cx: &mut ViewContext) { + if self.selected_item + 1 < self.matches.len() { + self.selected_item += 1; + } else { + self.selected_item = 0; + } + self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.attempt_resolve_selected_completion_documentation(project, cx); + cx.notify(); + } + + fn select_last(&mut self, project: Option<&Model>, cx: &mut ViewContext) { + self.selected_item = self.matches.len() - 1; + self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.attempt_resolve_selected_completion_documentation(project, cx); + cx.notify(); + } + + fn pre_resolve_completion_documentation( + &self, + project: Option>, + cx: &mut ViewContext, + ) { + todo!("implementation below "); + } + // ) { + // let settings = EditorSettings::get_global(cx); + // if !settings.show_completion_documentation { + // return; + // } + + // let Some(project) = project else { + // return; + // }; + // let client = project.read(cx).client(); + // let language_registry = project.read(cx).languages().clone(); + + // let is_remote = project.read(cx).is_remote(); + // let project_id = project.read(cx).remote_id(); + + // let completions = self.completions.clone(); + // let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect(); + + // cx.spawn(move |this, mut cx| async move { + // if is_remote { + // let Some(project_id) = project_id else { + // log::error!("Remote project without remote_id"); + // return; + // }; + + // for completion_index in completion_indices { + // let completions_guard = completions.read(); + // let completion = &completions_guard[completion_index]; + // if completion.documentation.is_some() { + // continue; + // } + + // let server_id = completion.server_id; + // let completion = completion.lsp_completion.clone(); + // drop(completions_guard); + + // Self::resolve_completion_documentation_remote( + // project_id, + // server_id, + // completions.clone(), + // completion_index, + // completion, + // client.clone(), + // language_registry.clone(), + // ) + // .await; + + // _ = this.update(&mut cx, |_, cx| cx.notify()); + // } + // } else { + // for completion_index in completion_indices { + // let completions_guard = completions.read(); + // let completion = &completions_guard[completion_index]; + // if completion.documentation.is_some() { + // continue; + // } + + // let server_id = completion.server_id; + // let completion = completion.lsp_completion.clone(); + // drop(completions_guard); + + // let server = project.read_with(&mut cx, |project, _| { + // project.language_server_for_id(server_id) + // }); + // let Some(server) = server else { + // return; + // }; + + // Self::resolve_completion_documentation_local( + // server, + // completions.clone(), + // completion_index, + // completion, + // language_registry.clone(), + // ) + // .await; + + // _ = this.update(&mut cx, |_, cx| cx.notify()); + // } + // } + // }) + // .detach(); + // } + + fn attempt_resolve_selected_completion_documentation( + &mut self, + project: Option<&Model>, + cx: &mut ViewContext, + ) { + let settings = EditorSettings::get_global(cx); + if !settings.show_completion_documentation { + return; + } + + let completion_index = self.matches[self.selected_item].candidate_id; + let Some(project) = project else { + return; + }; + let language_registry = project.read(cx).languages().clone(); + + let completions = self.completions.clone(); + let completions_guard = completions.read(); + let completion = &completions_guard[completion_index]; + // todo!() + // if completion.documentation.is_some() { + // return; + // } + + let server_id = completion.server_id; + let completion = completion.lsp_completion.clone(); + drop(completions_guard); + + if project.read(cx).is_remote() { + let Some(project_id) = project.read(cx).remote_id() else { + log::error!("Remote project without remote_id"); + return; + }; + + let client = project.read(cx).client(); + + cx.spawn(move |this, mut cx| async move { + Self::resolve_completion_documentation_remote( + project_id, + server_id, + completions.clone(), + completion_index, + completion, + client, + language_registry.clone(), + ) + .await; + + _ = this.update(&mut cx, |_, cx| cx.notify()); + }) + .detach(); + } else { + let Some(server) = project.read(cx).language_server_for_id(server_id) else { + return; + }; + + cx.spawn(move |this, mut cx| async move { + Self::resolve_completion_documentation_local( + server, + completions, + completion_index, + completion, + language_registry, + ) + .await; + + _ = this.update(&mut cx, |_, cx| cx.notify()); + }) + .detach(); + } + } + + async fn resolve_completion_documentation_remote( + project_id: u64, + server_id: LanguageServerId, + completions: Arc>>, + completion_index: usize, + completion: lsp::CompletionItem, + client: Arc, + language_registry: Arc, + ) { + todo!() + // let request = proto::ResolveCompletionDocumentation { + // project_id, + // language_server_id: server_id.0 as u64, + // lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(), + // }; + + // let Some(response) = client + // .request(request) + // .await + // .context("completion documentation resolve proto request") + // .log_err() + // else { + // return; + // }; + + // if response.text.is_empty() { + // let mut completions = completions.write(); + // let completion = &mut completions[completion_index]; + // completion.documentation = Some(Documentation::Undocumented); + // } + + // let documentation = if response.is_markdown { + // Documentation::MultiLineMarkdown( + // markdown::parse_markdown(&response.text, &language_registry, None).await, + // ) + // } else if response.text.lines().count() <= 1 { + // Documentation::SingleLine(response.text) + // } else { + // Documentation::MultiLinePlainText(response.text) + // }; + + // let mut completions = completions.write(); + // let completion = &mut completions[completion_index]; + // completion.documentation = Some(documentation); + } + + async fn resolve_completion_documentation_local( + server: Arc, + completions: Arc>>, + completion_index: usize, + completion: lsp::CompletionItem, + language_registry: Arc, + ) { + todo!() + // let can_resolve = server + // .capabilities() + // .completion_provider + // .as_ref() + // .and_then(|options| options.resolve_provider) + // .unwrap_or(false); + // if !can_resolve { + // return; + // } + + // let request = server.request::(completion); + // let Some(completion_item) = request.await.log_err() else { + // return; + // }; + + // if let Some(lsp_documentation) = completion_item.documentation { + // let documentation = language::prepare_completion_documentation( + // &lsp_documentation, + // &language_registry, + // None, // TODO: Try to reasonably work out which language the completion is for + // ) + // .await; + + // let mut completions = completions.write(); + // let completion = &mut completions[completion_index]; + // completion.documentation = Some(documentation); + // } else { + // let mut completions = completions.write(); + // let completion = &mut completions[completion_index]; + // completion.documentation = Some(Documentation::Undocumented); + // } + } + + fn visible(&self) -> bool { + !self.matches.is_empty() + } + + fn render( + &self, + style: EditorStyle, + workspace: Option>, + cx: &mut ViewContext, + ) { + todo!("old implementation below") + } + // ) -> AnyElement { + // enum CompletionTag {} + + // let settings = EditorSettings>(cx); + // let show_completion_documentation = settings.show_completion_documentation; + + // let widest_completion_ix = self + // .matches + // .iter() + // .enumerate() + // .max_by_key(|(_, mat)| { + // let completions = self.completions.read(); + // let completion = &completions[mat.candidate_id]; + // let documentation = &completion.documentation; + + // let mut len = completion.label.text.chars().count(); + // if let Some(Documentation::SingleLine(text)) = documentation { + // if show_completion_documentation { + // len += text.chars().count(); + // } + // } + + // len + // }) + // .map(|(ix, _)| ix); + + // let completions = self.completions.clone(); + // let matches = self.matches.clone(); + // let selected_item = self.selected_item; + + // let list = UniformList::new(self.list.clone(), matches.len(), cx, { + // let style = style.clone(); + // move |_, range, items, cx| { + // let start_ix = range.start; + // let completions_guard = completions.read(); + + // for (ix, mat) in matches[range].iter().enumerate() { + // let item_ix = start_ix + ix; + // let candidate_id = mat.candidate_id; + // let completion = &completions_guard[candidate_id]; + + // let documentation = if show_completion_documentation { + // &completion.documentation + // } else { + // &None + // }; + + // items.push( + // MouseEventHandler::new::( + // mat.candidate_id, + // cx, + // |state, _| { + // let item_style = if item_ix == selected_item { + // style.autocomplete.selected_item + // } else if state.hovered() { + // style.autocomplete.hovered_item + // } else { + // style.autocomplete.item + // }; + + // let completion_label = + // Text::new(completion.label.text.clone(), style.text.clone()) + // .with_soft_wrap(false) + // .with_highlights( + // combine_syntax_and_fuzzy_match_highlights( + // &completion.label.text, + // style.text.color.into(), + // styled_runs_for_code_label( + // &completion.label, + // &style.syntax, + // ), + // &mat.positions, + // ), + // ); + + // if let Some(Documentation::SingleLine(text)) = documentation { + // Flex::row() + // .with_child(completion_label) + // .with_children((|| { + // let text_style = TextStyle { + // color: style.autocomplete.inline_docs_color, + // font_size: style.text.font_size + // * style.autocomplete.inline_docs_size_percent, + // ..style.text.clone() + // }; + + // let label = Text::new(text.clone(), text_style) + // .aligned() + // .constrained() + // .dynamically(move |constraint, _, _| { + // gpui::SizeConstraint { + // min: constraint.min, + // max: vec2f( + // constraint.max.x(), + // constraint.min.y(), + // ), + // } + // }); + + // if Some(item_ix) == widest_completion_ix { + // Some( + // label + // .contained() + // .with_style( + // style + // .autocomplete + // .inline_docs_container, + // ) + // .into_any(), + // ) + // } else { + // Some(label.flex_float().into_any()) + // } + // })()) + // .into_any() + // } else { + // completion_label.into_any() + // } + // .contained() + // .with_style(item_style) + // .constrained() + // .dynamically( + // move |constraint, _, _| { + // if Some(item_ix) == widest_completion_ix { + // constraint + // } else { + // gpui::SizeConstraint { + // min: constraint.min, + // max: constraint.min, + // } + // } + // }, + // ) + // }, + // ) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_down(MouseButton::Left, move |_, this, cx| { + // this.confirm_completion( + // &ConfirmCompletion { + // item_ix: Some(item_ix), + // }, + // cx, + // ) + // .map(|task| task.detach()); + // }) + // .constrained() + // .with_min_width(style.autocomplete.completion_min_width) + // .with_max_width(style.autocomplete.completion_max_width) + // .into_any(), + // ); + // } + // } + // }) + // .with_width_from_item(widest_completion_ix); + + // enum MultiLineDocumentation {} + + // Flex::row() + // .with_child(list.flex(1., false)) + // .with_children({ + // let mat = &self.matches[selected_item]; + // let completions = self.completions.read(); + // let completion = &completions[mat.candidate_id]; + // let documentation = &completion.documentation; + + // match documentation { + // Some(Documentation::MultiLinePlainText(text)) => Some( + // Flex::column() + // .scrollable::(0, None, cx) + // .with_child( + // Text::new(text.clone(), style.text.clone()).with_soft_wrap(true), + // ) + // .contained() + // .with_style(style.autocomplete.alongside_docs_container) + // .constrained() + // .with_max_width(style.autocomplete.alongside_docs_max_width) + // .flex(1., false), + // ), + + // Some(Documentation::MultiLineMarkdown(parsed)) => Some( + // Flex::column() + // .scrollable::(0, None, cx) + // .with_child(render_parsed_markdown::( + // parsed, &style, workspace, cx, + // )) + // .contained() + // .with_style(style.autocomplete.alongside_docs_container) + // .constrained() + // .with_max_width(style.autocomplete.alongside_docs_max_width) + // .flex(1., false), + // ), + + // _ => None, + // } + // }) + // .contained() + // .with_style(style.autocomplete.container) + // .into_any() + // } + + pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) { + let mut matches = if let Some(query) = query { + fuzzy::match_strings( + &self.match_candidates, + query, + query.chars().any(|c| c.is_uppercase()), + 100, + &Default::default(), + executor, + ) + .await + } else { + self.match_candidates + .iter() + .enumerate() + .map(|(candidate_id, candidate)| StringMatch { + candidate_id, + score: Default::default(), + positions: Default::default(), + string: candidate.string.clone(), + }) + .collect() + }; + + // Remove all candidates where the query's start does not match the start of any word in the candidate + if let Some(query) = query { + if let Some(query_start) = query.chars().next() { + matches.retain(|string_match| { + split_words(&string_match.string).any(|word| { + // Check that the first codepoint of the word as lowercase matches the first + // codepoint of the query as lowercase + word.chars() + .flat_map(|codepoint| codepoint.to_lowercase()) + .zip(query_start.to_lowercase()) + .all(|(word_cp, query_cp)| word_cp == query_cp) + }) + }); + } + } + + let completions = self.completions.read(); + matches.sort_unstable_by_key(|mat| { + let completion = &completions[mat.candidate_id]; + ( + completion.lsp_completion.sort_text.as_ref(), + Reverse(OrderedFloat(mat.score)), + completion.sort_key(), + ) + }); + drop(completions); + + for mat in &mut matches { + let completions = self.completions.read(); + let filter_start = completions[mat.candidate_id].label.filter_range.start; + for position in &mut mat.positions { + *position += filter_start; + } + } + + self.matches = matches.into(); + self.selected_item = 0; + } +} + +#[derive(Clone)] +struct CodeActionsMenu { + actions: Arc<[CodeAction]>, + buffer: Model, + selected_item: usize, + list: UniformListState, + deployed_from_indicator: bool, +} + +impl CodeActionsMenu { + fn select_first(&mut self, cx: &mut ViewContext) { + self.selected_item = 0; + self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + cx.notify() + } + + fn select_prev(&mut self, cx: &mut ViewContext) { + if self.selected_item > 0 { + self.selected_item -= 1; + } else { + self.selected_item = self.actions.len() - 1; + } + self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + cx.notify(); + } + + fn select_next(&mut self, cx: &mut ViewContext) { + if self.selected_item + 1 < self.actions.len() { + self.selected_item += 1; + } else { + self.selected_item = 0; + } + self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + cx.notify(); + } + + fn select_last(&mut self, cx: &mut ViewContext) { + self.selected_item = self.actions.len() - 1; + self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + cx.notify() + } + + fn visible(&self) -> bool { + !self.actions.is_empty() + } + + fn render( + &self, + mut cursor_position: DisplayPoint, + style: EditorStyle, + cx: &mut ViewContext, + ) -> (DisplayPoint, AnyElement) { + todo!("old version below") + } + // enum ActionTag {} + + // let container_style = style.autocomplete.container; + // let actions = self.actions.clone(); + // let selected_item = self.selected_item; + // let element = UniformList::new( + // self.list.clone(), + // actions.len(), + // cx, + // move |_, range, items, cx| { + // let start_ix = range.start; + // for (ix, action) in actions[range].iter().enumerate() { + // let item_ix = start_ix + ix; + // items.push( + // MouseEventHandler::new::(item_ix, cx, |state, _| { + // let item_style = if item_ix == selected_item { + // style.autocomplete.selected_item + // } else if state.hovered() { + // style.autocomplete.hovered_item + // } else { + // style.autocomplete.item + // }; + + // Text::new(action.lsp_action.title.clone(), style.text.clone()) + // .with_soft_wrap(false) + // .contained() + // .with_style(item_style) + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_down(MouseButton::Left, move |_, this, cx| { + // let workspace = this + // .workspace + // .as_ref() + // .and_then(|(workspace, _)| workspace.upgrade(cx)); + // cx.window_context().defer(move |cx| { + // if let Some(workspace) = workspace { + // workspace.update(cx, |workspace, cx| { + // if let Some(task) = Editor::confirm_code_action( + // workspace, + // &ConfirmCodeAction { + // item_ix: Some(item_ix), + // }, + // cx, + // ) { + // task.detach_and_log_err(cx); + // } + // }); + // } + // }); + // }) + // .into_any(), + // ); + // } + // }, + // ) + // .with_width_from_item( + // self.actions + // .iter() + // .enumerate() + // .max_by_key(|(_, action)| action.lsp_action.title.chars().count()) + // .map(|(ix, _)| ix), + // ) + // .contained() + // .with_style(container_style) + // .into_any(); + + // if self.deployed_from_indicator { + // *cursor_position.column_mut() = 0; + // } + + // (cursor_position, element) + // } +} + +pub struct CopilotState { + excerpt_id: Option, + pending_refresh: Task>, + pending_cycling_refresh: Task>, + cycled: bool, + completions: Vec, + active_completion_index: usize, + suggestion: Option, +} + +impl Default for CopilotState { + fn default() -> Self { + Self { + excerpt_id: None, + pending_cycling_refresh: Task::ready(Some(())), + pending_refresh: Task::ready(Some(())), + completions: Default::default(), + active_completion_index: 0, + cycled: false, + suggestion: None, + } + } +} + +impl CopilotState { + fn active_completion(&self) -> Option<&copilot::Completion> { + self.completions.get(self.active_completion_index) + } + + fn text_for_active_completion( + &self, + cursor: Anchor, + buffer: &MultiBufferSnapshot, + ) -> Option<&str> { + use language::ToOffset as _; + + let completion = self.active_completion()?; + let excerpt_id = self.excerpt_id?; + let completion_buffer = buffer.buffer_for_excerpt(excerpt_id)?; + if excerpt_id != cursor.excerpt_id + || !completion.range.start.is_valid(completion_buffer) + || !completion.range.end.is_valid(completion_buffer) + { + return None; + } + + let mut completion_range = completion.range.to_offset(&completion_buffer); + let prefix_len = Self::common_prefix( + completion_buffer.chars_for_range(completion_range.clone()), + completion.text.chars(), + ); + completion_range.start += prefix_len; + let suffix_len = Self::common_prefix( + completion_buffer.reversed_chars_for_range(completion_range.clone()), + completion.text[prefix_len..].chars().rev(), + ); + completion_range.end = completion_range.end.saturating_sub(suffix_len); + + if completion_range.is_empty() + && completion_range.start == cursor.text_anchor.to_offset(&completion_buffer) + { + Some(&completion.text[prefix_len..completion.text.len() - suffix_len]) + } else { + None + } + } + + fn cycle_completions(&mut self, direction: Direction) { + match direction { + Direction::Prev => { + self.active_completion_index = if self.active_completion_index == 0 { + self.completions.len().saturating_sub(1) + } else { + self.active_completion_index - 1 + }; + } + Direction::Next => { + if self.completions.len() == 0 { + self.active_completion_index = 0 + } else { + self.active_completion_index = + (self.active_completion_index + 1) % self.completions.len(); + } + } + } + } + + fn push_completion(&mut self, new_completion: copilot::Completion) { + for completion in &self.completions { + if completion.text == new_completion.text && completion.range == new_completion.range { + return; + } + } + self.completions.push(new_completion); + } + + fn common_prefix, T2: Iterator>(a: T1, b: T2) -> usize { + a.zip(b) + .take_while(|(a, b)| a == b) + .map(|(a, _)| a.len_utf8()) + .sum() + } +} + +#[derive(Debug)] +struct ActiveDiagnosticGroup { + primary_range: Range, + primary_message: String, + blocks: HashMap, + is_valid: bool, +} + +#[derive(Serialize, Deserialize)] +pub struct ClipboardSelection { + pub len: usize, + pub is_entire_line: bool, + pub first_line_indent: u32, +} + +#[derive(Debug)] +pub struct NavigationData { + cursor_anchor: Anchor, + cursor_position: Point, + scroll_anchor: ScrollAnchor, + scroll_top_row: u32, +} + +pub struct EditorCreated(pub View); + +enum GotoDefinitionKind { + Symbol, + Type, +} + +#[derive(Debug, Clone)] +enum InlayHintRefreshReason { + Toggle(bool), + SettingsChange(InlayHintSettings), + NewLinesShown, + BufferEdited(HashSet>), + RefreshRequested, + ExcerptsRemoved(Vec), +} +impl InlayHintRefreshReason { + fn description(&self) -> &'static str { + match self { + Self::Toggle(_) => "toggle", + Self::SettingsChange(_) => "settings change", + Self::NewLinesShown => "new lines shown", + Self::BufferEdited(_) => "buffer edited", + Self::RefreshRequested => "refresh requested", + Self::ExcerptsRemoved(_) => "excerpts removed", + } + } +} + +impl Editor { + // pub fn single_line( + // field_editor_style: Option>, + // cx: &mut ViewContext, + // ) -> Self { + // let buffer = cx.build_model(|cx| Buffer::new(0, cx.model_id() as u64, String::new())); + // let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx)); + // Self::new(EditorMode::SingleLine, buffer, None, field_editor_style, cx) + // } + + // pub fn multi_line( + // field_editor_style: Option>, + // cx: &mut ViewContext, + // ) -> Self { + // let buffer = cx.build_model(|cx| Buffer::new(0, cx.model_id() as u64, String::new())); + // let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx)); + // Self::new(EditorMode::Full, buffer, None, field_editor_style, cx) + // } + + // pub fn auto_height( + // max_lines: usize, + // field_editor_style: Option>, + // cx: &mut ViewContext, + // ) -> Self { + // let buffer = cx.build_model(|cx| Buffer::new(0, cx.model_id() as u64, String::new())); + // let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx)); + // Self::new( + // EditorMode::AutoHeight { max_lines }, + // buffer, + // None, + // field_editor_style, + // cx, + // ) + // } + + pub fn for_buffer( + buffer: Model, + project: Option>, + cx: &mut ViewContext, + ) -> Self { + let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx)); + Self::new(EditorMode::Full, buffer, project, cx) + } + + pub fn for_multibuffer( + buffer: Model, + project: Option>, + cx: &mut ViewContext, + ) -> Self { + Self::new(EditorMode::Full, buffer, project, cx) + } + + pub fn clone(&self, cx: &mut ViewContext) -> Self { + let mut clone = Self::new( + self.mode, + self.buffer.clone(), + self.project.clone(), + // todo! + // self.get_field_editor_theme.clone(), + cx, + ); + self.display_map.update(cx, |display_map, cx| { + let snapshot = display_map.snapshot(cx); + clone.display_map.update(cx, |display_map, cx| { + display_map.set_state(&snapshot, cx); + }); + }); + clone.selections.clone_state(&self.selections); + clone.scroll_manager.clone_state(&self.scroll_manager); + clone.searchable = self.searchable; + clone + } + + fn new( + mode: EditorMode, + buffer: Model, + project: Option>, + // todo!() + // get_field_editor_theme: Option>, + cx: &mut ViewContext, + ) -> Self { + // let editor_view_id = cx.view_id(); + let style = cx.text_style(); + let font_size = style.font_size * cx.rem_size(); + let display_map = cx.build_model(|cx| { + // todo!() + // let settings = settings::get::(cx); + // let style = build_style(settings, get_field_editor_theme.as_deref(), None, cx); + DisplayMap::new(buffer.clone(), style.font(), font_size, None, 2, 1, cx) + }); + + let selections = SelectionsCollection::new(display_map.clone(), buffer.clone()); + + let blink_manager = cx.build_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx)); + + let soft_wrap_mode_override = + (mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::None); + + let mut project_subscriptions = Vec::new(); + if mode == EditorMode::Full { + if let Some(project) = project.as_ref() { + if buffer.read(cx).is_singleton() { + project_subscriptions.push(cx.observe(project, |_, _, cx| { + cx.emit(Event::TitleChanged); + })); + } + project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| { + if let project::Event::RefreshInlayHints = event { + editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx); + }; + })); + } + } + + let inlay_hint_settings = inlay_hint_settings( + selections.newest_anchor().head(), + &buffer.read(cx).snapshot(cx), + cx, + ); + + let mut this = Self { + handle: cx.view().downgrade(), + focus_handle: cx.focus_handle(), + buffer: buffer.clone(), + display_map: display_map.clone(), + selections, + scroll_manager: ScrollManager::new(), + columnar_selection_tail: None, + add_selections_state: None, + select_next_state: None, + select_prev_state: None, + selection_history: Default::default(), + autoclose_regions: Default::default(), + snippet_stack: Default::default(), + select_larger_syntax_node_stack: Vec::new(), + ime_transaction: Default::default(), + active_diagnostics: None, + soft_wrap_mode_override, + // get_field_editor_theme, + collaboration_hub: project.clone().map(|project| Box::new(project) as _), + project, + focused: false, + blink_manager: blink_manager.clone(), + show_local_selections: true, + mode, + show_gutter: mode == EditorMode::Full, + show_wrap_guides: None, + placeholder_text: None, + highlighted_rows: None, + background_highlights: Default::default(), + inlay_background_highlights: Default::default(), + nav_history: None, + context_menu: RwLock::new(None), + // mouse_context_menu: cx + // .add_view(|cx| context_menu::ContextMenu::new(editor_view_id, cx)), + completion_tasks: Default::default(), + next_completion_id: 0, + next_inlay_id: 0, + available_code_actions: Default::default(), + code_actions_task: Default::default(), + document_highlights_task: Default::default(), + pending_rename: Default::default(), + searchable: true, + // override_text_style: None, + cursor_shape: Default::default(), + autoindent_mode: Some(AutoindentMode::EachLine), + collapse_matches: false, + workspace: None, + // keymap_context_layers: Default::default(), + input_enabled: true, + read_only: false, + leader_peer_id: None, + remote_id: None, + hover_state: Default::default(), + link_go_to_definition_state: Default::default(), + copilot_state: Default::default(), + inlay_hint_cache: InlayHintCache::new(inlay_hint_settings), + gutter_hovered: false, + pixel_position_of_newest_cursor: None, + _subscriptions: vec![ + cx.observe(&buffer, Self::on_buffer_changed), + cx.subscribe(&buffer, Self::on_buffer_event), + cx.observe(&display_map, Self::on_display_map_changed), + cx.observe(&blink_manager, |_, _, cx| cx.notify()), + cx.observe_global::(Self::settings_changed), + cx.observe_window_activation(|editor, cx| { + let active = cx.is_window_active(); + editor.blink_manager.update(cx, |blink_manager, cx| { + if active { + blink_manager.enable(cx); + } else { + blink_manager.show_cursor(cx); + blink_manager.disable(cx); + } + }); + }), + ], + }; + + this._subscriptions.extend(project_subscriptions); + + this.end_selection(cx); + this.scroll_manager.show_scrollbar(cx); + + // todo!("use a different mechanism") + // let editor_created_event = EditorCreated(cx.handle()); + // cx.emit_global(editor_created_event); + + if mode == EditorMode::Full { + let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars(); + cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars)); + } + + this.report_editor_event("open", None, cx); + this + } + + // pub fn new_file( + // workspace: &mut Workspace, + // _: &workspace::NewFile, + // cx: &mut ViewContext, + // ) { + // let project = workspace.project().clone(); + // if project.read(cx).is_remote() { + // cx.propagate_action(); + // } else if let Some(buffer) = project + // .update(cx, |project, cx| project.create_buffer("", None, cx)) + // .log_err() + // { + // workspace.add_item( + // Box::new(cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))), + // cx, + // ); + // } + // } + + // pub fn new_file_in_direction( + // workspace: &mut Workspace, + // action: &workspace::NewFileInDirection, + // cx: &mut ViewContext, + // ) { + // let project = workspace.project().clone(); + // if project.read(cx).is_remote() { + // cx.propagate_action(); + // } else if let Some(buffer) = project + // .update(cx, |project, cx| project.create_buffer("", None, cx)) + // .log_err() + // { + // workspace.split_item( + // action.0, + // Box::new(cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))), + // cx, + // ); + // } + // } + + pub fn replica_id(&self, cx: &AppContext) -> ReplicaId { + self.buffer.read(cx).replica_id() + } + + // pub fn leader_peer_id(&self) -> Option { + // self.leader_peer_id + // } + + pub fn buffer(&self) -> &Model { + &self.buffer + } + + fn workspace(&self) -> Option> { + self.workspace.as_ref()?.0.upgrade() + } + + pub fn title<'a>(&self, cx: &'a AppContext) -> Cow<'a, str> { + self.buffer().read(cx).title(cx) + } + + pub fn snapshot(&mut self, cx: &mut WindowContext) -> EditorSnapshot { + EditorSnapshot { + mode: self.mode, + show_gutter: self.show_gutter, + display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)), + scroll_anchor: self.scroll_manager.anchor(), + ongoing_scroll: self.scroll_manager.ongoing_scroll(), + placeholder_text: self.placeholder_text.clone(), + is_focused: self.focus_handle.is_focused(cx), + } + } + + // pub fn language_at<'a, T: ToOffset>( + // &self, + // point: T, + // cx: &'a AppContext, + // ) -> Option> { + // self.buffer.read(cx).language_at(point, cx) + // } + + // pub fn file_at<'a, T: ToOffset>(&self, point: T, cx: &'a AppContext) -> Option> { + // self.buffer.read(cx).read(cx).file_at(point).cloned() + // } + + // pub fn active_excerpt( + // &self, + // cx: &AppContext, + // ) -> Option<(ExcerptId, Model, Range)> { + // self.buffer + // .read(cx) + // .excerpt_containing(self.selections.newest_anchor().head(), cx) + // } + + // pub fn style(&self, cx: &AppContext) -> EditorStyle { + // build_style( + // settings::get::(cx), + // self.get_field_editor_theme.as_deref(), + // self.override_text_style.as_deref(), + // cx, + // ) + // } + + // pub fn mode(&self) -> EditorMode { + // self.mode + // } + + // pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> { + // self.collaboration_hub.as_deref() + // } + + // pub fn set_collaboration_hub(&mut self, hub: Box) { + // self.collaboration_hub = Some(hub); + // } + + // pub fn set_placeholder_text( + // &mut self, + // placeholder_text: impl Into>, + // cx: &mut ViewContext, + // ) { + // self.placeholder_text = Some(placeholder_text.into()); + // cx.notify(); + // } + + // pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext) { + // self.cursor_shape = cursor_shape; + // cx.notify(); + // } + + // pub fn set_collapse_matches(&mut self, collapse_matches: bool) { + // self.collapse_matches = collapse_matches; + // } + + pub fn range_for_match(&self, range: &Range) -> Range { + if self.collapse_matches { + return range.start..range.start; + } + range.clone() + } + + // pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext) { + // if self.display_map.read(cx).clip_at_line_ends != clip { + // self.display_map + // .update(cx, |map, _| map.clip_at_line_ends = clip); + // } + // } + + // pub fn set_keymap_context_layer( + // &mut self, + // context: KeymapContext, + // cx: &mut ViewContext, + // ) { + // self.keymap_context_layers + // .insert(TypeId::of::(), context); + // cx.notify(); + // } + + // pub fn remove_keymap_context_layer(&mut self, cx: &mut ViewContext) { + // self.keymap_context_layers.remove(&TypeId::of::()); + // cx.notify(); + // } + + // pub fn set_input_enabled(&mut self, input_enabled: bool) { + // self.input_enabled = input_enabled; + // } + + // pub fn set_autoindent(&mut self, autoindent: bool) { + // if autoindent { + // self.autoindent_mode = Some(AutoindentMode::EachLine); + // } else { + // self.autoindent_mode = None; + // } + // } + + // pub fn read_only(&self) -> bool { + // self.read_only + // } + + // pub fn set_read_only(&mut self, read_only: bool) { + // self.read_only = read_only; + // } + + // pub fn set_field_editor_style( + // &mut self, + // style: Option>, + // cx: &mut ViewContext, + // ) { + // self.get_field_editor_theme = style; + // cx.notify(); + // } + + fn selections_did_change( + &mut self, + local: bool, + old_cursor_position: &Anchor, + cx: &mut ViewContext, + ) { + if self.focused && self.leader_peer_id.is_none() { + self.buffer.update(cx, |buffer, cx| { + buffer.set_active_selections( + &self.selections.disjoint_anchors(), + self.selections.line_mode, + self.cursor_shape, + cx, + ) + }); + } + + let display_map = self + .display_map + .update(cx, |display_map, cx| display_map.snapshot(cx)); + let buffer = &display_map.buffer_snapshot; + self.add_selections_state = None; + self.select_next_state = None; + self.select_prev_state = None; + self.select_larger_syntax_node_stack.clear(); + self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer); + self.snippet_stack + .invalidate(&self.selections.disjoint_anchors(), buffer); + self.take_rename(false, cx); + + let new_cursor_position = self.selections.newest_anchor().head(); + + self.push_to_nav_history( + old_cursor_position.clone(), + Some(new_cursor_position.to_point(buffer)), + cx, + ); + + if local { + let new_cursor_position = self.selections.newest_anchor().head(); + let mut context_menu = self.context_menu.write(); + let completion_menu = match context_menu.as_ref() { + Some(ContextMenu::Completions(menu)) => Some(menu), + + _ => { + *context_menu = None; + None + } + }; + + if let Some(completion_menu) = completion_menu { + let cursor_position = new_cursor_position.to_offset(buffer); + let (word_range, kind) = + buffer.surrounding_word(completion_menu.initial_position.clone()); + if kind == Some(CharKind::Word) + && word_range.to_inclusive().contains(&cursor_position) + { + let mut completion_menu = completion_menu.clone(); + drop(context_menu); + + let query = Self::completion_query(buffer, cursor_position); + cx.spawn(move |this, mut cx| async move { + completion_menu + .filter(query.as_deref(), cx.background_executor().clone()) + .await; + + this.update(&mut cx, |this, cx| { + let mut context_menu = this.context_menu.write(); + let Some(ContextMenu::Completions(menu)) = context_menu.as_ref() else { + return; + }; + + if menu.id > completion_menu.id { + return; + } + + *context_menu = Some(ContextMenu::Completions(completion_menu)); + drop(context_menu); + cx.notify(); + }) + }) + .detach(); + + self.show_completions(&ShowCompletions, cx); + } else { + drop(context_menu); + self.hide_context_menu(cx); + } + } else { + drop(context_menu); + } + + hide_hover(self, cx); + + if old_cursor_position.to_display_point(&display_map).row() + != new_cursor_position.to_display_point(&display_map).row() + { + self.available_code_actions.take(); + } + self.refresh_code_actions(cx); + self.refresh_document_highlights(cx); + refresh_matching_bracket_highlights(self, cx); + self.discard_copilot_suggestion(cx); + } + + self.blink_manager.update(cx, BlinkManager::pause_blinking); + cx.emit(Event::SelectionsChanged { local }); + cx.notify(); + } + + pub fn change_selections( + &mut self, + autoscroll: Option, + cx: &mut ViewContext, + change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R, + ) -> R { + let old_cursor_position = self.selections.newest_anchor().head(); + self.push_to_selection_history(); + + let (changed, result) = self.selections.change_with(cx, change); + + if changed { + if let Some(autoscroll) = autoscroll { + self.request_autoscroll(autoscroll, cx); + } + self.selections_did_change(true, &old_cursor_position, cx); + } + + result + } + + pub fn edit(&mut self, edits: I, cx: &mut ViewContext) + where + I: IntoIterator, T)>, + S: ToOffset, + T: Into>, + { + if self.read_only { + return; + } + + self.buffer + .update(cx, |buffer, cx| buffer.edit(edits, None, cx)); + } + + // pub fn edit_with_autoindent(&mut self, edits: I, cx: &mut ViewContext) + // where + // I: IntoIterator, T)>, + // S: ToOffset, + // T: Into>, + // { + // if self.read_only { + // return; + // } + + // self.buffer.update(cx, |buffer, cx| { + // buffer.edit(edits, self.autoindent_mode.clone(), cx) + // }); + // } + + // pub fn edit_with_block_indent( + // &mut self, + // edits: I, + // original_indent_columns: Vec, + // cx: &mut ViewContext, + // ) where + // I: IntoIterator, T)>, + // S: ToOffset, + // T: Into>, + // { + // if self.read_only { + // return; + // } + + // self.buffer.update(cx, |buffer, cx| { + // buffer.edit( + // edits, + // Some(AutoindentMode::Block { + // original_indent_columns, + // }), + // cx, + // ) + // }); + // } + + fn select(&mut self, phase: SelectPhase, cx: &mut ViewContext) { + self.hide_context_menu(cx); + + match phase { + SelectPhase::Begin { + position, + add, + click_count, + } => self.begin_selection(position, add, click_count, cx), + SelectPhase::BeginColumnar { + position, + goal_column, + } => self.begin_columnar_selection(position, goal_column, cx), + SelectPhase::Extend { + position, + click_count, + } => self.extend_selection(position, click_count, cx), + SelectPhase::Update { + position, + goal_column, + scroll_position, + } => self.update_selection(position, goal_column, scroll_position, cx), + SelectPhase::End => self.end_selection(cx), + } + } + + fn extend_selection( + &mut self, + position: DisplayPoint, + click_count: usize, + cx: &mut ViewContext, + ) { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let tail = self.selections.newest::(cx).tail(); + self.begin_selection(position, false, click_count, cx); + + let position = position.to_offset(&display_map, Bias::Left); + let tail_anchor = display_map.buffer_snapshot.anchor_before(tail); + + let mut pending_selection = self + .selections + .pending_anchor() + .expect("extend_selection not called with pending selection"); + if position >= tail { + pending_selection.start = tail_anchor; + } else { + pending_selection.end = tail_anchor; + pending_selection.reversed = true; + } + + let mut pending_mode = self.selections.pending_mode().unwrap(); + match &mut pending_mode { + SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor, + _ => {} + } + + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.set_pending(pending_selection, pending_mode) + }); + } + + fn begin_selection( + &mut self, + position: DisplayPoint, + add: bool, + click_count: usize, + cx: &mut ViewContext, + ) { + if !self.focused { + cx.focus(&self.focus_handle); + } + + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let buffer = &display_map.buffer_snapshot; + let newest_selection = self.selections.newest_anchor().clone(); + let position = display_map.clip_point(position, Bias::Left); + + let start; + let end; + let mode; + let auto_scroll; + match click_count { + 1 => { + start = buffer.anchor_before(position.to_point(&display_map)); + end = start.clone(); + mode = SelectMode::Character; + auto_scroll = true; + } + 2 => { + let range = movement::surrounding_word(&display_map, position); + start = buffer.anchor_before(range.start.to_point(&display_map)); + end = buffer.anchor_before(range.end.to_point(&display_map)); + mode = SelectMode::Word(start.clone()..end.clone()); + auto_scroll = true; + } + 3 => { + let position = display_map + .clip_point(position, Bias::Left) + .to_point(&display_map); + let line_start = display_map.prev_line_boundary(position).0; + let next_line_start = buffer.clip_point( + display_map.next_line_boundary(position).0 + Point::new(1, 0), + Bias::Left, + ); + start = buffer.anchor_before(line_start); + end = buffer.anchor_before(next_line_start); + mode = SelectMode::Line(start.clone()..end.clone()); + auto_scroll = true; + } + _ => { + start = buffer.anchor_before(0); + end = buffer.anchor_before(buffer.len()); + mode = SelectMode::All; + auto_scroll = false; + } + } + + self.change_selections(auto_scroll.then(|| Autoscroll::newest()), cx, |s| { + if !add { + s.clear_disjoint(); + } else if click_count > 1 { + s.delete(newest_selection.id) + } + + s.set_pending_anchor_range(start..end, mode); + }); + } + + fn begin_columnar_selection( + &mut self, + position: DisplayPoint, + goal_column: u32, + cx: &mut ViewContext, + ) { + if !self.focused { + cx.focus(&self.focus_handle); + } + + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let tail = self.selections.newest::(cx).tail(); + self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail)); + + self.select_columns( + tail.to_display_point(&display_map), + position, + goal_column, + &display_map, + cx, + ); + } + + fn update_selection( + &mut self, + position: DisplayPoint, + goal_column: u32, + scroll_position: gpui::Point, + cx: &mut ViewContext, + ) { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + + if let Some(tail) = self.columnar_selection_tail.as_ref() { + let tail = tail.to_display_point(&display_map); + self.select_columns(tail, position, goal_column, &display_map, cx); + } else if let Some(mut pending) = self.selections.pending_anchor() { + let buffer = self.buffer.read(cx).snapshot(cx); + let head; + let tail; + let mode = self.selections.pending_mode().unwrap(); + match &mode { + SelectMode::Character => { + head = position.to_point(&display_map); + tail = pending.tail().to_point(&buffer); + } + SelectMode::Word(original_range) => { + let original_display_range = original_range.start.to_display_point(&display_map) + ..original_range.end.to_display_point(&display_map); + let original_buffer_range = original_display_range.start.to_point(&display_map) + ..original_display_range.end.to_point(&display_map); + if movement::is_inside_word(&display_map, position) + || original_display_range.contains(&position) + { + let word_range = movement::surrounding_word(&display_map, position); + if word_range.start < original_display_range.start { + head = word_range.start.to_point(&display_map); + } else { + head = word_range.end.to_point(&display_map); + } + } else { + head = position.to_point(&display_map); + } + + if head <= original_buffer_range.start { + tail = original_buffer_range.end; + } else { + tail = original_buffer_range.start; + } + } + SelectMode::Line(original_range) => { + let original_range = original_range.to_point(&display_map.buffer_snapshot); + + let position = display_map + .clip_point(position, Bias::Left) + .to_point(&display_map); + let line_start = display_map.prev_line_boundary(position).0; + let next_line_start = buffer.clip_point( + display_map.next_line_boundary(position).0 + Point::new(1, 0), + Bias::Left, + ); + + if line_start < original_range.start { + head = line_start + } else { + head = next_line_start + } + + if head <= original_range.start { + tail = original_range.end; + } else { + tail = original_range.start; + } + } + SelectMode::All => { + return; + } + }; + + if head < tail { + pending.start = buffer.anchor_before(head); + pending.end = buffer.anchor_before(tail); + pending.reversed = true; + } else { + pending.start = buffer.anchor_before(tail); + pending.end = buffer.anchor_before(head); + pending.reversed = false; + } + + self.change_selections(None, cx, |s| { + s.set_pending(pending, mode); + }); + } else { + log::error!("update_selection dispatched with no pending selection"); + return; + } + + self.set_scroll_position(scroll_position, cx); + cx.notify(); + } + + fn end_selection(&mut self, cx: &mut ViewContext) { + self.columnar_selection_tail.take(); + if self.selections.pending_anchor().is_some() { + let selections = self.selections.all::(cx); + self.change_selections(None, cx, |s| { + s.select(selections); + s.clear_pending(); + }); + } + } + + fn select_columns( + &mut self, + tail: DisplayPoint, + head: DisplayPoint, + goal_column: u32, + display_map: &DisplaySnapshot, + cx: &mut ViewContext, + ) { + let start_row = cmp::min(tail.row(), head.row()); + let end_row = cmp::max(tail.row(), head.row()); + let start_column = cmp::min(tail.column(), goal_column); + let end_column = cmp::max(tail.column(), goal_column); + let reversed = start_column < tail.column(); + + let selection_ranges = (start_row..=end_row) + .filter_map(|row| { + if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) { + let start = display_map + .clip_point(DisplayPoint::new(row, start_column), Bias::Left) + .to_point(display_map); + let end = display_map + .clip_point(DisplayPoint::new(row, end_column), Bias::Right) + .to_point(display_map); + if reversed { + Some(end..start) + } else { + Some(start..end) + } + } else { + None + } + }) + .collect::>(); + + self.change_selections(None, cx, |s| { + s.select_ranges(selection_ranges); + }); + cx.notify(); + } + + pub fn has_pending_nonempty_selection(&self) -> bool { + let pending_nonempty_selection = match self.selections.pending_anchor() { + Some(Selection { start, end, .. }) => start != end, + None => false, + }; + pending_nonempty_selection || self.columnar_selection_tail.is_some() + } + + pub fn has_pending_selection(&self) -> bool { + self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some() + } + + // pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { + // if self.take_rename(false, cx).is_some() { + // return; + // } + + // if hide_hover(self, cx) { + // return; + // } + + // if self.hide_context_menu(cx).is_some() { + // return; + // } + + // if self.discard_copilot_suggestion(cx) { + // return; + // } + + // if self.snippet_stack.pop().is_some() { + // return; + // } + + // if self.mode == EditorMode::Full { + // if self.active_diagnostics.is_some() { + // self.dismiss_diagnostics(cx); + // return; + // } + + // if self.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel()) { + // return; + // } + // } + + // cx.propagate_action(); + // } + + // pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext) { + // let text: Arc = text.into(); + + // if self.read_only { + // return; + // } + + // let selections = self.selections.all_adjusted(cx); + // let mut brace_inserted = false; + // let mut edits = Vec::new(); + // let mut new_selections = Vec::with_capacity(selections.len()); + // let mut new_autoclose_regions = Vec::new(); + // let snapshot = self.buffer.read(cx).read(cx); + + // for (selection, autoclose_region) in + // self.selections_with_autoclose_regions(selections, &snapshot) + // { + // if let Some(scope) = snapshot.language_scope_at(selection.head()) { + // // Determine if the inserted text matches the opening or closing + // // bracket of any of this language's bracket pairs. + // let mut bracket_pair = None; + // let mut is_bracket_pair_start = false; + // if !text.is_empty() { + // // `text` can be empty when an user is using IME (e.g. Chinese Wubi Simplified) + // // and they are removing the character that triggered IME popup. + // for (pair, enabled) in scope.brackets() { + // if enabled && pair.close && pair.start.ends_with(text.as_ref()) { + // bracket_pair = Some(pair.clone()); + // is_bracket_pair_start = true; + // break; + // } else if pair.end.as_str() == text.as_ref() { + // bracket_pair = Some(pair.clone()); + // break; + // } + // } + // } + + // if let Some(bracket_pair) = bracket_pair { + // if selection.is_empty() { + // if is_bracket_pair_start { + // let prefix_len = bracket_pair.start.len() - text.len(); + + // // If the inserted text is a suffix of an opening bracket and the + // // selection is preceded by the rest of the opening bracket, then + // // insert the closing bracket. + // let following_text_allows_autoclose = snapshot + // .chars_at(selection.start) + // .next() + // .map_or(true, |c| scope.should_autoclose_before(c)); + // let preceding_text_matches_prefix = prefix_len == 0 + // || (selection.start.column >= (prefix_len as u32) + // && snapshot.contains_str_at( + // Point::new( + // selection.start.row, + // selection.start.column - (prefix_len as u32), + // ), + // &bracket_pair.start[..prefix_len], + // )); + // if following_text_allows_autoclose && preceding_text_matches_prefix { + // let anchor = snapshot.anchor_before(selection.end); + // new_selections.push((selection.map(|_| anchor), text.len())); + // new_autoclose_regions.push(( + // anchor, + // text.len(), + // selection.id, + // bracket_pair.clone(), + // )); + // edits.push(( + // selection.range(), + // format!("{}{}", text, bracket_pair.end).into(), + // )); + // brace_inserted = true; + // continue; + // } + // } + + // if let Some(region) = autoclose_region { + // // If the selection is followed by an auto-inserted closing bracket, + // // then don't insert that closing bracket again; just move the selection + // // past the closing bracket. + // let should_skip = selection.end == region.range.end.to_point(&snapshot) + // && text.as_ref() == region.pair.end.as_str(); + // if should_skip { + // let anchor = snapshot.anchor_after(selection.end); + // new_selections + // .push((selection.map(|_| anchor), region.pair.end.len())); + // continue; + // } + // } + // } + // // If an opening bracket is 1 character long and is typed while + // // text is selected, then surround that text with the bracket pair. + // else if is_bracket_pair_start && bracket_pair.start.chars().count() == 1 { + // edits.push((selection.start..selection.start, text.clone())); + // edits.push(( + // selection.end..selection.end, + // bracket_pair.end.as_str().into(), + // )); + // brace_inserted = true; + // new_selections.push(( + // Selection { + // id: selection.id, + // start: snapshot.anchor_after(selection.start), + // end: snapshot.anchor_before(selection.end), + // reversed: selection.reversed, + // goal: selection.goal, + // }, + // 0, + // )); + // continue; + // } + // } + // } + + // // If not handling any auto-close operation, then just replace the selected + // // text with the given input and move the selection to the end of the + // // newly inserted text. + // let anchor = snapshot.anchor_after(selection.end); + // new_selections.push((selection.map(|_| anchor), 0)); + // edits.push((selection.start..selection.end, text.clone())); + // } + + // drop(snapshot); + // self.transact(cx, |this, cx| { + // this.buffer.update(cx, |buffer, cx| { + // buffer.edit(edits, this.autoindent_mode.clone(), cx); + // }); + + // let new_anchor_selections = new_selections.iter().map(|e| &e.0); + // let new_selection_deltas = new_selections.iter().map(|e| e.1); + // let snapshot = this.buffer.read(cx).read(cx); + // let new_selections = resolve_multiple::(new_anchor_selections, &snapshot) + // .zip(new_selection_deltas) + // .map(|(selection, delta)| Selection { + // id: selection.id, + // start: selection.start + delta, + // end: selection.end + delta, + // reversed: selection.reversed, + // goal: SelectionGoal::None, + // }) + // .collect::>(); + + // let mut i = 0; + // for (position, delta, selection_id, pair) in new_autoclose_regions { + // let position = position.to_offset(&snapshot) + delta; + // let start = snapshot.anchor_before(position); + // let end = snapshot.anchor_after(position); + // while let Some(existing_state) = this.autoclose_regions.get(i) { + // match existing_state.range.start.cmp(&start, &snapshot) { + // Ordering::Less => i += 1, + // Ordering::Greater => break, + // Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) { + // Ordering::Less => i += 1, + // Ordering::Equal => break, + // Ordering::Greater => break, + // }, + // } + // } + // this.autoclose_regions.insert( + // i, + // AutocloseRegion { + // selection_id, + // range: start..end, + // pair, + // }, + // ); + // } + + // drop(snapshot); + // let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx); + // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); + + // if !brace_inserted && EditorSettings>(cx).use_on_type_format { + // if let Some(on_type_format_task) = + // this.trigger_on_type_formatting(text.to_string(), cx) + // { + // on_type_format_task.detach_and_log_err(cx); + // } + // } + + // if had_active_copilot_suggestion { + // this.refresh_copilot_suggestions(true, cx); + // if !this.has_active_copilot_suggestion(cx) { + // this.trigger_completion_on_input(&text, cx); + // } + // } else { + // this.trigger_completion_on_input(&text, cx); + // this.refresh_copilot_suggestions(true, cx); + // } + // }); + // } + + // pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext) { + // self.transact(cx, |this, cx| { + // let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = { + // let selections = this.selections.all::(cx); + // let multi_buffer = this.buffer.read(cx); + // let buffer = multi_buffer.snapshot(cx); + // selections + // .iter() + // .map(|selection| { + // let start_point = selection.start.to_point(&buffer); + // let mut indent = buffer.indent_size_for_line(start_point.row); + // indent.len = cmp::min(indent.len, start_point.column); + // let start = selection.start; + // let end = selection.end; + // let is_cursor = start == end; + // let language_scope = buffer.language_scope_at(start); + // let (comment_delimiter, insert_extra_newline) = if let Some(language) = + // &language_scope + // { + // let leading_whitespace_len = buffer + // .reversed_chars_at(start) + // .take_while(|c| c.is_whitespace() && *c != '\n') + // .map(|c| c.len_utf8()) + // .sum::(); + + // let trailing_whitespace_len = buffer + // .chars_at(end) + // .take_while(|c| c.is_whitespace() && *c != '\n') + // .map(|c| c.len_utf8()) + // .sum::(); + + // let insert_extra_newline = + // language.brackets().any(|(pair, enabled)| { + // let pair_start = pair.start.trim_end(); + // let pair_end = pair.end.trim_start(); + + // enabled + // && pair.newline + // && buffer.contains_str_at( + // end + trailing_whitespace_len, + // pair_end, + // ) + // && buffer.contains_str_at( + // (start - leading_whitespace_len) + // .saturating_sub(pair_start.len()), + // pair_start, + // ) + // }); + // // Comment extension on newline is allowed only for cursor selections + // let comment_delimiter = language.line_comment_prefix().filter(|_| { + // let is_comment_extension_enabled = + // multi_buffer.settings_at(0, cx).extend_comment_on_newline; + // is_cursor && is_comment_extension_enabled + // }); + // let comment_delimiter = if let Some(delimiter) = comment_delimiter { + // buffer + // .buffer_line_for_row(start_point.row) + // .is_some_and(|(snapshot, range)| { + // let mut index_of_first_non_whitespace = 0; + // let line_starts_with_comment = snapshot + // .chars_for_range(range) + // .skip_while(|c| { + // let should_skip = c.is_whitespace(); + // if should_skip { + // index_of_first_non_whitespace += 1; + // } + // should_skip + // }) + // .take(delimiter.len()) + // .eq(delimiter.chars()); + // let cursor_is_placed_after_comment_marker = + // index_of_first_non_whitespace + delimiter.len() + // <= start_point.column as usize; + // line_starts_with_comment + // && cursor_is_placed_after_comment_marker + // }) + // .then(|| delimiter.clone()) + // } else { + // None + // }; + // (comment_delimiter, insert_extra_newline) + // } else { + // (None, false) + // }; + + // let capacity_for_delimiter = comment_delimiter + // .as_deref() + // .map(str::len) + // .unwrap_or_default(); + // let mut new_text = + // String::with_capacity(1 + capacity_for_delimiter + indent.len as usize); + // new_text.push_str("\n"); + // new_text.extend(indent.chars()); + // if let Some(delimiter) = &comment_delimiter { + // new_text.push_str(&delimiter); + // } + // if insert_extra_newline { + // new_text = new_text.repeat(2); + // } + + // let anchor = buffer.anchor_after(end); + // let new_selection = selection.map(|_| anchor); + // ( + // (start..end, new_text), + // (insert_extra_newline, new_selection), + // ) + // }) + // .unzip() + // }; + + // this.edit_with_autoindent(edits, cx); + // let buffer = this.buffer.read(cx).snapshot(cx); + // let new_selections = selection_fixup_info + // .into_iter() + // .map(|(extra_newline_inserted, new_selection)| { + // let mut cursor = new_selection.end.to_point(&buffer); + // if extra_newline_inserted { + // cursor.row -= 1; + // cursor.column = buffer.line_len(cursor.row); + // } + // new_selection.map(|_| cursor) + // }) + // .collect(); + + // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); + // this.refresh_copilot_suggestions(true, cx); + // }); + // } + + // pub fn newline_above(&mut self, _: &NewlineAbove, cx: &mut ViewContext) { + // let buffer = self.buffer.read(cx); + // let snapshot = buffer.snapshot(cx); + + // let mut edits = Vec::new(); + // let mut rows = Vec::new(); + // let mut rows_inserted = 0; + + // for selection in self.selections.all_adjusted(cx) { + // let cursor = selection.head(); + // let row = cursor.row; + + // let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left); + + // let newline = "\n".to_string(); + // edits.push((start_of_line..start_of_line, newline)); + + // rows.push(row + rows_inserted); + // rows_inserted += 1; + // } + + // self.transact(cx, |editor, cx| { + // editor.edit(edits, cx); + + // editor.change_selections(Some(Autoscroll::fit()), cx, |s| { + // let mut index = 0; + // s.move_cursors_with(|map, _, _| { + // let row = rows[index]; + // index += 1; + + // let point = Point::new(row, 0); + // let boundary = map.next_line_boundary(point).1; + // let clipped = map.clip_point(boundary, Bias::Left); + + // (clipped, SelectionGoal::None) + // }); + // }); + + // let mut indent_edits = Vec::new(); + // let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx); + // for row in rows { + // let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx); + // for (row, indent) in indents { + // if indent.len == 0 { + // continue; + // } + + // let text = match indent.kind { + // IndentKind::Space => " ".repeat(indent.len as usize), + // IndentKind::Tab => "\t".repeat(indent.len as usize), + // }; + // let point = Point::new(row, 0); + // indent_edits.push((point..point, text)); + // } + // } + // editor.edit(indent_edits, cx); + // }); + // } + + // pub fn newline_below(&mut self, _: &NewlineBelow, cx: &mut ViewContext) { + // let buffer = self.buffer.read(cx); + // let snapshot = buffer.snapshot(cx); + + // let mut edits = Vec::new(); + // let mut rows = Vec::new(); + // let mut rows_inserted = 0; + + // for selection in self.selections.all_adjusted(cx) { + // let cursor = selection.head(); + // let row = cursor.row; + + // let point = Point::new(row + 1, 0); + // let start_of_line = snapshot.clip_point(point, Bias::Left); + + // let newline = "\n".to_string(); + // edits.push((start_of_line..start_of_line, newline)); + + // rows_inserted += 1; + // rows.push(row + rows_inserted); + // } + + // self.transact(cx, |editor, cx| { + // editor.edit(edits, cx); + + // editor.change_selections(Some(Autoscroll::fit()), cx, |s| { + // let mut index = 0; + // s.move_cursors_with(|map, _, _| { + // let row = rows[index]; + // index += 1; + + // let point = Point::new(row, 0); + // let boundary = map.next_line_boundary(point).1; + // let clipped = map.clip_point(boundary, Bias::Left); + + // (clipped, SelectionGoal::None) + // }); + // }); + + // let mut indent_edits = Vec::new(); + // let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx); + // for row in rows { + // let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx); + // for (row, indent) in indents { + // if indent.len == 0 { + // continue; + // } + + // let text = match indent.kind { + // IndentKind::Space => " ".repeat(indent.len as usize), + // IndentKind::Tab => "\t".repeat(indent.len as usize), + // }; + // let point = Point::new(row, 0); + // indent_edits.push((point..point, text)); + // } + // } + // editor.edit(indent_edits, cx); + // }); + // } + + // pub fn insert(&mut self, text: &str, cx: &mut ViewContext) { + // self.insert_with_autoindent_mode( + // text, + // Some(AutoindentMode::Block { + // original_indent_columns: Vec::new(), + // }), + // cx, + // ); + // } + + fn insert_with_autoindent_mode( + &mut self, + text: &str, + autoindent_mode: Option, + cx: &mut ViewContext, + ) { + if self.read_only { + return; + } + + let text: Arc = text.into(); + self.transact(cx, |this, cx| { + let old_selections = this.selections.all_adjusted(cx); + let selection_anchors = this.buffer.update(cx, |buffer, cx| { + let anchors = { + let snapshot = buffer.read(cx); + old_selections + .iter() + .map(|s| { + let anchor = snapshot.anchor_after(s.head()); + s.map(|_| anchor) + }) + .collect::>() + }; + buffer.edit( + old_selections + .iter() + .map(|s| (s.start..s.end, text.clone())), + autoindent_mode, + cx, + ); + anchors + }); + + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_anchors(selection_anchors); + }) + }); + } + + // fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext) { + // if !EditorSettings>(cx).show_completions_on_input { + // return; + // } + + // let selection = self.selections.newest_anchor(); + // if self + // .buffer + // .read(cx) + // .is_completion_trigger(selection.head(), text, cx) + // { + // self.show_completions(&ShowCompletions, cx); + // } else { + // self.hide_context_menu(cx); + // } + // } + + // /// If any empty selections is touching the start of its innermost containing autoclose + // /// region, expand it to select the brackets. + // fn select_autoclose_pair(&mut self, cx: &mut ViewContext) { + // let selections = self.selections.all::(cx); + // let buffer = self.buffer.read(cx).read(cx); + // let mut new_selections = Vec::new(); + // for (mut selection, region) in self.selections_with_autoclose_regions(selections, &buffer) { + // if let (Some(region), true) = (region, selection.is_empty()) { + // let mut range = region.range.to_offset(&buffer); + // if selection.start == range.start { + // if range.start >= region.pair.start.len() { + // range.start -= region.pair.start.len(); + // if buffer.contains_str_at(range.start, ®ion.pair.start) { + // if buffer.contains_str_at(range.end, ®ion.pair.end) { + // range.end += region.pair.end.len(); + // selection.start = range.start; + // selection.end = range.end; + // } + // } + // } + // } + // } + // new_selections.push(selection); + // } + + // drop(buffer); + // self.change_selections(None, cx, |selections| selections.select(new_selections)); + // } + + // /// Iterate the given selections, and for each one, find the smallest surrounding + // /// autoclose region. This uses the ordering of the selections and the autoclose + // /// regions to avoid repeated comparisons. + // fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>( + // &'a self, + // selections: impl IntoIterator>, + // buffer: &'a MultiBufferSnapshot, + // ) -> impl Iterator, Option<&'a AutocloseRegion>)> { + // let mut i = 0; + // let mut regions = self.autoclose_regions.as_slice(); + // selections.into_iter().map(move |selection| { + // let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer); + + // let mut enclosing = None; + // while let Some(pair_state) = regions.get(i) { + // if pair_state.range.end.to_offset(buffer) < range.start { + // regions = ®ions[i + 1..]; + // i = 0; + // } else if pair_state.range.start.to_offset(buffer) > range.end { + // break; + // } else { + // if pair_state.selection_id == selection.id { + // enclosing = Some(pair_state); + // } + // i += 1; + // } + // } + + // (selection.clone(), enclosing) + // }) + // } + + /// Remove any autoclose regions that no longer contain their selection. + fn invalidate_autoclose_regions( + &mut self, + mut selections: &[Selection], + buffer: &MultiBufferSnapshot, + ) { + self.autoclose_regions.retain(|state| { + let mut i = 0; + while let Some(selection) = selections.get(i) { + if selection.end.cmp(&state.range.start, buffer).is_lt() { + selections = &selections[1..]; + continue; + } + if selection.start.cmp(&state.range.end, buffer).is_gt() { + break; + } + if selection.id == state.selection_id { + return true; + } else { + i += 1; + } + } + false + }); + } + + fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option { + let offset = position.to_offset(buffer); + let (word_range, kind) = buffer.surrounding_word(offset); + if offset > word_range.start && kind == Some(CharKind::Word) { + Some( + buffer + .text_for_range(word_range.start..offset) + .collect::(), + ) + } else { + None + } + } + + // pub fn toggle_inlay_hints(&mut self, _: &ToggleInlayHints, cx: &mut ViewContext) { + // todo!(); + // // self.refresh_inlay_hints( + // // InlayHintRefreshReason::Toggle(!self.inlay_hint_cache.enabled), + // // cx, + // // ); + // } + + // pub fn inlay_hints_enabled(&self) -> bool { + // todo!(); + // self.inlay_hint_cache.enabled + // } + + fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut ViewContext) { + if self.project.is_none() || self.mode != EditorMode::Full { + return; + } + + let reason_description = reason.description(); + let (invalidate_cache, required_languages) = match reason { + InlayHintRefreshReason::Toggle(enabled) => { + self.inlay_hint_cache.enabled = enabled; + if enabled { + (InvalidationStrategy::RefreshRequested, None) + } else { + self.inlay_hint_cache.clear(); + self.splice_inlay_hints( + self.visible_inlay_hints(cx) + .iter() + .map(|inlay| inlay.id) + .collect(), + Vec::new(), + cx, + ); + return; + } + } + InlayHintRefreshReason::SettingsChange(new_settings) => { + match self.inlay_hint_cache.update_settings( + &self.buffer, + new_settings, + self.visible_inlay_hints(cx), + cx, + ) { + ControlFlow::Break(Some(InlaySplice { + to_remove, + to_insert, + })) => { + self.splice_inlay_hints(to_remove, to_insert, cx); + return; + } + ControlFlow::Break(None) => return, + ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None), + } + } + InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => { + if let Some(InlaySplice { + to_remove, + to_insert, + }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed) + { + self.splice_inlay_hints(to_remove, to_insert, cx); + } + return; + } + InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None), + InlayHintRefreshReason::BufferEdited(buffer_languages) => { + (InvalidationStrategy::BufferEdited, Some(buffer_languages)) + } + InlayHintRefreshReason::RefreshRequested => { + (InvalidationStrategy::RefreshRequested, None) + } + }; + + if let Some(InlaySplice { + to_remove, + to_insert, + }) = self.inlay_hint_cache.spawn_hint_refresh( + reason_description, + self.excerpt_visible_offsets(required_languages.as_ref(), cx), + invalidate_cache, + cx, + ) { + self.splice_inlay_hints(to_remove, to_insert, cx); + } + } + + fn visible_inlay_hints(&self, cx: &ViewContext<'_, Editor>) -> Vec { + self.display_map + .read(cx) + .current_inlays() + .filter(move |inlay| { + Some(inlay.id) != self.copilot_state.suggestion.as_ref().map(|h| h.id) + }) + .cloned() + .collect() + } + + pub fn excerpt_visible_offsets( + &self, + restrict_to_languages: Option<&HashSet>>, + cx: &mut ViewContext, + ) -> HashMap, clock::Global, Range)> { + let multi_buffer = self.buffer().read(cx); + let multi_buffer_snapshot = multi_buffer.snapshot(cx); + let multi_buffer_visible_start = self + .scroll_manager + .anchor() + .anchor + .to_point(&multi_buffer_snapshot); + let multi_buffer_visible_end = multi_buffer_snapshot.clip_point( + multi_buffer_visible_start + + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0), + Bias::Left, + ); + let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end; + multi_buffer + .range_to_buffer_ranges(multi_buffer_visible_range, cx) + .into_iter() + .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty()) + .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| { + let buffer = buffer_handle.read(cx); + let language = buffer.language()?; + if let Some(restrict_to_languages) = restrict_to_languages { + if !restrict_to_languages.contains(language) { + return None; + } + } + Some(( + excerpt_id, + ( + buffer_handle, + buffer.version().clone(), + excerpt_visible_range, + ), + )) + }) + .collect() + } + + // pub fn text_layout_details(&self, cx: &WindowContext) -> TextLayoutDetails { + // TextLayoutDetails { + // font_cache: cx.font_cache().clone(), + // text_layout_cache: cx.text_layout_cache().clone(), + // editor_style: self.style(cx), + // } + // } + + fn splice_inlay_hints( + &self, + to_remove: Vec, + to_insert: Vec, + cx: &mut ViewContext, + ) { + self.display_map.update(cx, |display_map, cx| { + display_map.splice_inlays(to_remove, to_insert, cx); + }); + cx.notify(); + } + + // fn trigger_on_type_formatting( + // &self, + // input: String, + // cx: &mut ViewContext, + // ) -> Option>> { + // if input.len() != 1 { + // return None; + // } + + // let project = self.project.as_ref()?; + // let position = self.selections.newest_anchor().head(); + // let (buffer, buffer_position) = self + // .buffer + // .read(cx) + // .text_anchor_for_position(position.clone(), cx)?; + + // // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances, + // // hence we do LSP request & edit on host side only — add formats to host's history. + // let push_to_lsp_host_history = true; + // // If this is not the host, append its history with new edits. + // let push_to_client_history = project.read(cx).is_remote(); + + // let on_type_formatting = project.update(cx, |project, cx| { + // project.on_type_format( + // buffer.clone(), + // buffer_position, + // input, + // push_to_lsp_host_history, + // cx, + // ) + // }); + // Some(cx.spawn(|editor, mut cx| async move { + // if let Some(transaction) = on_type_formatting.await? { + // if push_to_client_history { + // buffer.update(&mut cx, |buffer, _| { + // buffer.push_transaction(transaction, Instant::now()); + // }); + // } + // editor.update(&mut cx, |editor, cx| { + // editor.refresh_document_highlights(cx); + // })?; + // } + // Ok(()) + // })) + // } + + fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext) { + if self.pending_rename.is_some() { + return; + } + + let project = if let Some(project) = self.project.clone() { + project + } else { + return; + }; + + let position = self.selections.newest_anchor().head(); + let (buffer, buffer_position) = if let Some(output) = self + .buffer + .read(cx) + .text_anchor_for_position(position.clone(), cx) + { + output + } else { + return; + }; + + let query = Self::completion_query(&self.buffer.read(cx).read(cx), position.clone()); + let completions = project.update(cx, |project, cx| { + project.completions(&buffer, buffer_position, cx) + }); + + let id = post_inc(&mut self.next_completion_id); + let task = cx.spawn(|this, mut cx| { + async move { + let menu = if let Some(completions) = completions.await.log_err() { + let mut menu = CompletionsMenu { + id, + initial_position: position, + match_candidates: completions + .iter() + .enumerate() + .map(|(id, completion)| { + StringMatchCandidate::new( + id, + completion.label.text[completion.label.filter_range.clone()] + .into(), + ) + }) + .collect(), + buffer, + completions: Arc::new(RwLock::new(completions.into())), + matches: Vec::new().into(), + selected_item: 0, + list: Default::default(), + }; + menu.filter(query.as_deref(), cx.background_executor().clone()) + .await; + if menu.matches.is_empty() { + None + } else { + _ = this.update(&mut cx, |editor, cx| { + menu.pre_resolve_completion_documentation(editor.project.clone(), cx); + }); + Some(menu) + } + } else { + None + }; + + this.update(&mut cx, |this, cx| { + this.completion_tasks.retain(|(task_id, _)| *task_id > id); + + let mut context_menu = this.context_menu.write(); + match context_menu.as_ref() { + None => {} + + Some(ContextMenu::Completions(prev_menu)) => { + if prev_menu.id > id { + return; + } + } + + _ => return, + } + + if this.focused && menu.is_some() { + let menu = menu.unwrap(); + *context_menu = Some(ContextMenu::Completions(menu)); + drop(context_menu); + this.discard_copilot_suggestion(cx); + cx.notify(); + } else if this.completion_tasks.is_empty() { + // If there are no more completion tasks and the last menu was + // empty, we should hide it. If it was already hidden, we should + // also show the copilot suggestion when available. + drop(context_menu); + if this.hide_context_menu(cx).is_none() { + this.update_visible_copilot_suggestion(cx); + } + } + })?; + + Ok::<_, anyhow::Error>(()) + } + .log_err() + }); + self.completion_tasks.push((id, task)); + } + + // pub fn confirm_completion( + // &mut self, + // action: &ConfirmCompletion, + // cx: &mut ViewContext, + // ) -> Option>> { + // use language::ToOffset as _; + + // let completions_menu = if let ContextMenu::Completions(menu) = self.hide_context_menu(cx)? { + // menu + // } else { + // return None; + // }; + + // let mat = completions_menu + // .matches + // .get(action.item_ix.unwrap_or(completions_menu.selected_item))?; + // let buffer_handle = completions_menu.buffer; + // let completions = completions_menu.completions.read(); + // let completion = completions.get(mat.candidate_id)?; + + // let snippet; + // let text; + // if completion.is_snippet() { + // snippet = Some(Snippet::parse(&completion.new_text).log_err()?); + // text = snippet.as_ref().unwrap().text.clone(); + // } else { + // snippet = None; + // text = completion.new_text.clone(); + // }; + // let selections = self.selections.all::(cx); + // let buffer = buffer_handle.read(cx); + // let old_range = completion.old_range.to_offset(buffer); + // let old_text = buffer.text_for_range(old_range.clone()).collect::(); + + // let newest_selection = self.selections.newest_anchor(); + // if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) { + // return None; + // } + + // let lookbehind = newest_selection + // .start + // .text_anchor + // .to_offset(buffer) + // .saturating_sub(old_range.start); + // let lookahead = old_range + // .end + // .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer)); + // let mut common_prefix_len = old_text + // .bytes() + // .zip(text.bytes()) + // .take_while(|(a, b)| a == b) + // .count(); + + // let snapshot = self.buffer.read(cx).snapshot(cx); + // let mut range_to_replace: Option> = None; + // let mut ranges = Vec::new(); + // for selection in &selections { + // if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) { + // let start = selection.start.saturating_sub(lookbehind); + // let end = selection.end + lookahead; + // if selection.id == newest_selection.id { + // range_to_replace = Some( + // ((start + common_prefix_len) as isize - selection.start as isize) + // ..(end as isize - selection.start as isize), + // ); + // } + // ranges.push(start + common_prefix_len..end); + // } else { + // common_prefix_len = 0; + // ranges.clear(); + // ranges.extend(selections.iter().map(|s| { + // if s.id == newest_selection.id { + // range_to_replace = Some( + // old_range.start.to_offset_utf16(&snapshot).0 as isize + // - selection.start as isize + // ..old_range.end.to_offset_utf16(&snapshot).0 as isize + // - selection.start as isize, + // ); + // old_range.clone() + // } else { + // s.start..s.end + // } + // })); + // break; + // } + // } + // let text = &text[common_prefix_len..]; + + // cx.emit(Event::InputHandled { + // utf16_range_to_replace: range_to_replace, + // text: text.into(), + // }); + + // self.transact(cx, |this, cx| { + // if let Some(mut snippet) = snippet { + // snippet.text = text.to_string(); + // for tabstop in snippet.tabstops.iter_mut().flatten() { + // tabstop.start -= common_prefix_len as isize; + // tabstop.end -= common_prefix_len as isize; + // } + + // this.insert_snippet(&ranges, snippet, cx).log_err(); + // } else { + // this.buffer.update(cx, |buffer, cx| { + // buffer.edit( + // ranges.iter().map(|range| (range.clone(), text)), + // this.autoindent_mode.clone(), + // cx, + // ); + // }); + // } + + // this.refresh_copilot_suggestions(true, cx); + // }); + + // let project = self.project.clone()?; + // let apply_edits = project.update(cx, |project, cx| { + // project.apply_additional_edits_for_completion( + // buffer_handle, + // completion.clone(), + // true, + // cx, + // ) + // }); + // Some(cx.foreground().spawn(async move { + // apply_edits.await?; + // Ok(()) + // })) + // } + + // pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext) { + // let mut context_menu = self.context_menu.write(); + // if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) { + // *context_menu = None; + // cx.notify(); + // return; + // } + // drop(context_menu); + + // let deployed_from_indicator = action.deployed_from_indicator; + // let mut task = self.code_actions_task.take(); + // cx.spawn(|this, mut cx| async move { + // while let Some(prev_task) = task { + // prev_task.await; + // task = this.update(&mut cx, |this, _| this.code_actions_task.take())?; + // } + + // this.update(&mut cx, |this, cx| { + // if this.focused { + // if let Some((buffer, actions)) = this.available_code_actions.clone() { + // this.completion_tasks.clear(); + // this.discard_copilot_suggestion(cx); + // *this.context_menu.write() = + // Some(ContextMenu::CodeActions(CodeActionsMenu { + // buffer, + // actions, + // selected_item: Default::default(), + // list: Default::default(), + // deployed_from_indicator, + // })); + // } + // } + // })?; + + // Ok::<_, anyhow::Error>(()) + // }) + // .detach_and_log_err(cx); + // } + + // pub fn confirm_code_action( + // workspace: &mut Workspace, + // action: &ConfirmCodeAction, + // cx: &mut ViewContext, + // ) -> Option>> { + // let editor = workspace.active_item(cx)?.act_as::(cx)?; + // let actions_menu = if let ContextMenu::CodeActions(menu) = + // editor.update(cx, |editor, cx| editor.hide_context_menu(cx))? + // { + // menu + // } else { + // return None; + // }; + // let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item); + // let action = actions_menu.actions.get(action_ix)?.clone(); + // let title = action.lsp_action.title.clone(); + // let buffer = actions_menu.buffer; + + // let apply_code_actions = workspace.project().clone().update(cx, |project, cx| { + // project.apply_code_action(buffer, action, true, cx) + // }); + // let editor = editor.downgrade(); + // Some(cx.spawn(|workspace, cx| async move { + // let project_transaction = apply_code_actions.await?; + // Self::open_project_transaction(&editor, workspace, project_transaction, title, cx).await + // })) + // } + + // async fn open_project_transaction( + // this: &WeakViewHandle Result<()> { + // let replica_id = this.read_with(&cx, |this, cx| this.replica_id(cx))?; + + // let mut entries = transaction.0.into_iter().collect::>(); + // entries.sort_unstable_by_key(|(buffer, _)| { + // buffer.read_with(&cx, |buffer, _| buffer.file().map(|f| f.path().clone())) + // }); + + // // If the project transaction's edits are all contained within this editor, then + // // avoid opening a new editor to display them. + + // if let Some((buffer, transaction)) = entries.first() { + // if entries.len() == 1 { + // let excerpt = this.read_with(&cx, |editor, cx| { + // editor + // .buffer() + // .read(cx) + // .excerpt_containing(editor.selections.newest_anchor().head(), cx) + // })?; + // if let Some((_, excerpted_buffer, excerpt_range)) = excerpt { + // if excerpted_buffer == *buffer { + // let all_edits_within_excerpt = buffer.read_with(&cx, |buffer, _| { + // let excerpt_range = excerpt_range.to_offset(buffer); + // buffer + // .edited_ranges_for_transaction::(transaction) + // .all(|range| { + // excerpt_range.start <= range.start + // && excerpt_range.end >= range.end + // }) + // }); + + // if all_edits_within_excerpt { + // return Ok(()); + // } + // } + // } + // } + // } else { + // return Ok(()); + // } + + // let mut ranges_to_highlight = Vec::new(); + // let excerpt_buffer = cx.build_model(|cx| { + // let mut multibuffer = MultiBuffer::new(replica_id).with_title(title); + // for (buffer_handle, transaction) in &entries { + // let buffer = buffer_handle.read(cx); + // ranges_to_highlight.extend( + // multibuffer.push_excerpts_with_context_lines( + // buffer_handle.clone(), + // buffer + // .edited_ranges_for_transaction::(transaction) + // .collect(), + // 1, + // cx, + // ), + // ); + // } + // multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx); + // multibuffer + // }); + + // workspace.update(&mut cx, |workspace, cx| { + // let project = workspace.project().clone(); + // let editor = + // cx.add_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), cx)); + // workspace.add_item(Box::new(editor.clone()), cx); + // editor.update(cx, |editor, cx| { + // editor.highlight_background::( + // ranges_to_highlight, + // |theme| theme.editor.highlighted_line_background, + // cx, + // ); + // }); + // })?; + + // Ok(()) + // } + + fn refresh_code_actions(&mut self, cx: &mut ViewContext) -> Option<()> { + let project = self.project.clone()?; + let buffer = self.buffer.read(cx); + let newest_selection = self.selections.newest_anchor().clone(); + let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?; + let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?; + if start_buffer != end_buffer { + return None; + } + + self.code_actions_task = Some(cx.spawn(|this, mut cx| async move { + cx.background_executor() + .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT) + .await; + + let actions = if let Ok(code_actions) = project.update(&mut cx, |project, cx| { + project.code_actions(&start_buffer, start..end, cx) + }) { + code_actions.await.log_err() + } else { + None + }; + + this.update(&mut cx, |this, cx| { + this.available_code_actions = actions.and_then(|actions| { + if actions.is_empty() { + None + } else { + Some((start_buffer, actions.into())) + } + }); + cx.notify(); + }) + .log_err(); + })); + None + } + + fn refresh_document_highlights(&mut self, cx: &mut ViewContext) -> Option<()> { + if self.pending_rename.is_some() { + return None; + } + + let project = self.project.clone()?; + let buffer = self.buffer.read(cx); + let newest_selection = self.selections.newest_anchor().clone(); + let cursor_position = newest_selection.head(); + let (cursor_buffer, cursor_buffer_position) = + buffer.text_anchor_for_position(cursor_position.clone(), cx)?; + let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?; + if cursor_buffer != tail_buffer { + return None; + } + + self.document_highlights_task = Some(cx.spawn(|this, mut cx| async move { + cx.background_executor() + .timer(DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT) + .await; + + let highlights = if let Some(highlights) = project + .update(&mut cx, |project, cx| { + project.document_highlights(&cursor_buffer, cursor_buffer_position, cx) + }) + .log_err() + { + highlights.await.log_err() + } else { + None + }; + + if let Some(highlights) = highlights { + this.update(&mut cx, |this, cx| { + if this.pending_rename.is_some() { + return; + } + + let buffer_id = cursor_position.buffer_id; + let buffer = this.buffer.read(cx); + if !buffer + .text_anchor_for_position(cursor_position, cx) + .map_or(false, |(buffer, _)| buffer == cursor_buffer) + { + return; + } + + let cursor_buffer_snapshot = cursor_buffer.read(cx); + let mut write_ranges = Vec::new(); + let mut read_ranges = Vec::new(); + for highlight in highlights { + for (excerpt_id, excerpt_range) in + buffer.excerpts_for_buffer(&cursor_buffer, cx) + { + let start = highlight + .range + .start + .max(&excerpt_range.context.start, cursor_buffer_snapshot); + let end = highlight + .range + .end + .min(&excerpt_range.context.end, cursor_buffer_snapshot); + if start.cmp(&end, cursor_buffer_snapshot).is_ge() { + continue; + } + + let range = Anchor { + buffer_id, + excerpt_id: excerpt_id.clone(), + text_anchor: start, + }..Anchor { + buffer_id, + excerpt_id, + text_anchor: end, + }; + if highlight.kind == lsp::DocumentHighlightKind::WRITE { + write_ranges.push(range); + } else { + read_ranges.push(range); + } + } + } + + this.highlight_background::( + read_ranges, + |theme| todo!("theme.editor.document_highlight_read_background"), + cx, + ); + this.highlight_background::( + write_ranges, + |theme| todo!("theme.editor.document_highlight_write_background"), + cx, + ); + cx.notify(); + }) + .log_err(); + } + })); + None + } + + fn refresh_copilot_suggestions( + &mut self, + debounce: bool, + cx: &mut ViewContext, + ) -> Option<()> { + let copilot = Copilot::global(cx)?; + if self.mode != EditorMode::Full || !copilot.read(cx).status().is_authorized() { + self.clear_copilot_suggestions(cx); + return None; + } + self.update_visible_copilot_suggestion(cx); + + let snapshot = self.buffer.read(cx).snapshot(cx); + let cursor = self.selections.newest_anchor().head(); + if !self.is_copilot_enabled_at(cursor, &snapshot, cx) { + self.clear_copilot_suggestions(cx); + return None; + } + + let (buffer, buffer_position) = + self.buffer.read(cx).text_anchor_for_position(cursor, cx)?; + self.copilot_state.pending_refresh = cx.spawn(|this, mut cx| async move { + if debounce { + cx.background_executor() + .timer(COPILOT_DEBOUNCE_TIMEOUT) + .await; + } + + let completions = copilot + .update(&mut cx, |copilot, cx| { + copilot.completions(&buffer, buffer_position, cx) + }) + .log_err() + .unwrap_or(Task::ready(Ok(Vec::new()))) + .await + .log_err() + .into_iter() + .flatten() + .collect_vec(); + + this.update(&mut cx, |this, cx| { + if !completions.is_empty() { + this.copilot_state.cycled = false; + this.copilot_state.pending_cycling_refresh = Task::ready(None); + this.copilot_state.completions.clear(); + this.copilot_state.active_completion_index = 0; + this.copilot_state.excerpt_id = Some(cursor.excerpt_id); + for completion in completions { + this.copilot_state.push_completion(completion); + } + this.update_visible_copilot_suggestion(cx); + } + }) + .log_err()?; + Some(()) + }); + + Some(()) + } + + fn cycle_copilot_suggestions( + &mut self, + direction: Direction, + cx: &mut ViewContext, + ) -> Option<()> { + let copilot = Copilot::global(cx)?; + if self.mode != EditorMode::Full || !copilot.read(cx).status().is_authorized() { + return None; + } + + if self.copilot_state.cycled { + self.copilot_state.cycle_completions(direction); + self.update_visible_copilot_suggestion(cx); + } else { + let cursor = self.selections.newest_anchor().head(); + let (buffer, buffer_position) = + self.buffer.read(cx).text_anchor_for_position(cursor, cx)?; + self.copilot_state.pending_cycling_refresh = cx.spawn(|this, mut cx| async move { + let completions = copilot + .update(&mut cx, |copilot, cx| { + copilot.completions_cycling(&buffer, buffer_position, cx) + }) + .log_err()? + .await; + + this.update(&mut cx, |this, cx| { + this.copilot_state.cycled = true; + for completion in completions.log_err().into_iter().flatten() { + this.copilot_state.push_completion(completion); + } + this.copilot_state.cycle_completions(direction); + this.update_visible_copilot_suggestion(cx); + }) + .log_err()?; + + Some(()) + }); + } + + Some(()) + } + + fn copilot_suggest(&mut self, _: &copilot::Suggest, cx: &mut ViewContext) { + if !self.has_active_copilot_suggestion(cx) { + self.refresh_copilot_suggestions(false, cx); + return; + } + + self.update_visible_copilot_suggestion(cx); + } + + fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext) { + if self.has_active_copilot_suggestion(cx) { + self.cycle_copilot_suggestions(Direction::Next, cx); + } else { + let is_copilot_disabled = self.refresh_copilot_suggestions(false, cx).is_none(); + if is_copilot_disabled { + todo!(); + // cx.propagate(); + } + } + } + + fn previous_copilot_suggestion( + &mut self, + _: &copilot::PreviousSuggestion, + cx: &mut ViewContext, + ) { + if self.has_active_copilot_suggestion(cx) { + self.cycle_copilot_suggestions(Direction::Prev, cx); + } else { + let is_copilot_disabled = self.refresh_copilot_suggestions(false, cx).is_none(); + if is_copilot_disabled { + todo!(); + // cx.propagate_action(); + } + } + } + + fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext) -> bool { + if let Some(suggestion) = self.take_active_copilot_suggestion(cx) { + if let Some((copilot, completion)) = + Copilot::global(cx).zip(self.copilot_state.active_completion()) + { + copilot + .update(cx, |copilot, cx| copilot.accept_completion(completion, cx)) + .detach_and_log_err(cx); + + self.report_copilot_event(Some(completion.uuid.clone()), true, cx) + } + cx.emit(Event::InputHandled { + utf16_range_to_replace: None, + text: suggestion.text.to_string().into(), + }); + self.insert_with_autoindent_mode(&suggestion.text.to_string(), None, cx); + cx.notify(); + true + } else { + false + } + } + + fn discard_copilot_suggestion(&mut self, cx: &mut ViewContext) -> bool { + if let Some(suggestion) = self.take_active_copilot_suggestion(cx) { + if let Some(copilot) = Copilot::global(cx) { + copilot + .update(cx, |copilot, cx| { + copilot.discard_completions(&self.copilot_state.completions, cx) + }) + .detach_and_log_err(cx); + + self.report_copilot_event(None, false, cx) + } + + self.display_map.update(cx, |map, cx| { + map.splice_inlays(vec![suggestion.id], Vec::new(), cx) + }); + cx.notify(); + true + } else { + false + } + } + + fn is_copilot_enabled_at( + &self, + location: Anchor, + snapshot: &MultiBufferSnapshot, + cx: &mut ViewContext, + ) -> bool { + let file = snapshot.file_at(location); + let language = snapshot.language_at(location); + let settings = all_language_settings(file, cx); + settings.copilot_enabled(language, file.map(|f| f.path().as_ref())) + } + + fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool { + if let Some(suggestion) = self.copilot_state.suggestion.as_ref() { + let buffer = self.buffer.read(cx).read(cx); + suggestion.position.is_valid(&buffer) + } else { + false + } + } + + fn take_active_copilot_suggestion(&mut self, cx: &mut ViewContext) -> Option { + let suggestion = self.copilot_state.suggestion.take()?; + self.display_map.update(cx, |map, cx| { + map.splice_inlays(vec![suggestion.id], Default::default(), cx); + }); + let buffer = self.buffer.read(cx).read(cx); + + if suggestion.position.is_valid(&buffer) { + Some(suggestion) + } else { + None + } + } + + fn update_visible_copilot_suggestion(&mut self, cx: &mut ViewContext) { + let snapshot = self.buffer.read(cx).snapshot(cx); + let selection = self.selections.newest_anchor(); + let cursor = selection.head(); + + if self.context_menu.read().is_some() + || !self.completion_tasks.is_empty() + || selection.start != selection.end + { + self.discard_copilot_suggestion(cx); + } else if let Some(text) = self + .copilot_state + .text_for_active_completion(cursor, &snapshot) + { + let text = Rope::from(text); + let mut to_remove = Vec::new(); + if let Some(suggestion) = self.copilot_state.suggestion.take() { + to_remove.push(suggestion.id); + } + + let suggestion_inlay = + Inlay::suggestion(post_inc(&mut self.next_inlay_id), cursor, text); + self.copilot_state.suggestion = Some(suggestion_inlay.clone()); + self.display_map.update(cx, move |map, cx| { + map.splice_inlays(to_remove, vec![suggestion_inlay], cx) + }); + cx.notify(); + } else { + self.discard_copilot_suggestion(cx); + } + } + + fn clear_copilot_suggestions(&mut self, cx: &mut ViewContext) { + self.copilot_state = Default::default(); + self.discard_copilot_suggestion(cx); + } + + // pub fn render_code_actions_indicator( + // &self, + // style: &EditorStyle, + // is_active: bool, + // cx: &mut ViewContext, + // ) -> Option> { + // if self.available_code_actions.is_some() { + // enum CodeActions {} + // Some( + // MouseEventHandler::new::(0, cx, |state, _| { + // Svg::new("icons/bolt.svg").with_color( + // style + // .code_actions + // .indicator + // .in_state(is_active) + // .style_for(state) + // .color, + // ) + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .with_padding(Padding::uniform(3.)) + // .on_down(MouseButton::Left, |_, this, cx| { + // this.toggle_code_actions( + // &ToggleCodeActions { + // deployed_from_indicator: true, + // }, + // cx, + // ); + // }) + // .into_any(), + // ) + // } else { + // None + // } + // } + + // pub fn render_fold_indicators( + // &self, + // fold_data: Vec>, + // style: &EditorStyle, + // gutter_hovered: bool, + // line_height: f32, + // gutter_margin: f32, + // cx: &mut ViewContext, + // ) -> Vec>> { + // enum FoldIndicators {} + + // let style = style.folds.clone(); + + // fold_data + // .iter() + // .enumerate() + // .map(|(ix, fold_data)| { + // fold_data + // .map(|(fold_status, buffer_row, active)| { + // (active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| { + // MouseEventHandler::new::( + // ix as usize, + // cx, + // |mouse_state, _| { + // Svg::new(match fold_status { + // FoldStatus::Folded => style.folded_icon.clone(), + // FoldStatus::Foldable => style.foldable_icon.clone(), + // }) + // .with_color( + // style + // .indicator + // .in_state(fold_status == FoldStatus::Folded) + // .style_for(mouse_state) + // .color, + // ) + // .constrained() + // .with_width(gutter_margin * style.icon_margin_scale) + // .aligned() + // .constrained() + // .with_height(line_height) + // .with_width(gutter_margin) + // .aligned() + // }, + // ) + // .with_cursor_style(CursorStyle::PointingHand) + // .with_padding(Padding::uniform(3.)) + // .on_click(MouseButton::Left, { + // move |_, editor, cx| match fold_status { + // FoldStatus::Folded => { + // editor.unfold_at(&UnfoldAt { buffer_row }, cx); + // } + // FoldStatus::Foldable => { + // editor.fold_at(&FoldAt { buffer_row }, cx); + // } + // } + // }) + // .into_any() + // }) + // }) + // .flatten() + // }) + // .collect() + // } + + // pub fn context_menu_visible(&self) -> bool { + // self.context_menu + // .read() + // .as_ref() + // .map_or(false, |menu| menu.visible()) + // } + + // pub fn render_context_menu( + // &self, + // cursor_position: DisplayPoint, + // style: EditorStyle, + // cx: &mut ViewContext, + // ) -> Option<(DisplayPoint, AnyElement)> { + // self.context_menu.read().as_ref().map(|menu| { + // menu.render( + // cursor_position, + // style, + // self.workspace.as_ref().map(|(w, _)| w.clone()), + // cx, + // ) + // }) + // } + + fn hide_context_menu(&mut self, cx: &mut ViewContext) -> Option { + cx.notify(); + self.completion_tasks.clear(); + let context_menu = self.context_menu.write().take(); + if context_menu.is_some() { + self.update_visible_copilot_suggestion(cx); + } + context_menu + } + + // pub fn insert_snippet( + // &mut self, + // insertion_ranges: &[Range], + // snippet: Snippet, + // cx: &mut ViewContext, + // ) -> Result<()> { + // let tabstops = self.buffer.update(cx, |buffer, cx| { + // let snippet_text: Arc = snippet.text.clone().into(); + // buffer.edit( + // insertion_ranges + // .iter() + // .cloned() + // .map(|range| (range, snippet_text.clone())), + // Some(AutoindentMode::EachLine), + // cx, + // ); + + // let snapshot = &*buffer.read(cx); + // let snippet = &snippet; + // snippet + // .tabstops + // .iter() + // .map(|tabstop| { + // let mut tabstop_ranges = tabstop + // .iter() + // .flat_map(|tabstop_range| { + // let mut delta = 0_isize; + // insertion_ranges.iter().map(move |insertion_range| { + // let insertion_start = insertion_range.start as isize + delta; + // delta += + // snippet.text.len() as isize - insertion_range.len() as isize; + + // let start = snapshot.anchor_before( + // (insertion_start + tabstop_range.start) as usize, + // ); + // let end = snapshot + // .anchor_after((insertion_start + tabstop_range.end) as usize); + // start..end + // }) + // }) + // .collect::>(); + // tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot)); + // tabstop_ranges + // }) + // .collect::>() + // }); + + // if let Some(tabstop) = tabstops.first() { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select_ranges(tabstop.iter().cloned()); + // }); + // self.snippet_stack.push(SnippetState { + // active_index: 0, + // ranges: tabstops, + // }); + // } + + // Ok(()) + // } + + // pub fn move_to_next_snippet_tabstop(&mut self, cx: &mut ViewContext) -> bool { + // self.move_to_snippet_tabstop(Bias::Right, cx) + // } + + // pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext) -> bool { + // self.move_to_snippet_tabstop(Bias::Left, cx) + // } + + // pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext) -> bool { + // if let Some(mut snippet) = self.snippet_stack.pop() { + // match bias { + // Bias::Left => { + // if snippet.active_index > 0 { + // snippet.active_index -= 1; + // } else { + // self.snippet_stack.push(snippet); + // return false; + // } + // } + // Bias::Right => { + // if snippet.active_index + 1 < snippet.ranges.len() { + // snippet.active_index += 1; + // } else { + // self.snippet_stack.push(snippet); + // return false; + // } + // } + // } + // if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select_anchor_ranges(current_ranges.iter().cloned()) + // }); + // // If snippet state is not at the last tabstop, push it back on the stack + // if snippet.active_index + 1 < snippet.ranges.len() { + // self.snippet_stack.push(snippet); + // } + // return true; + // } + // } + + // false + // } + + // pub fn clear(&mut self, cx: &mut ViewContext) { + // self.transact(cx, |this, cx| { + // this.select_all(&SelectAll, cx); + // this.insert("", cx); + // }); + // } + + // pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext) { + // self.transact(cx, |this, cx| { + // this.select_autoclose_pair(cx); + // let mut selections = this.selections.all::(cx); + // if !this.selections.line_mode { + // let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx)); + // for selection in &mut selections { + // if selection.is_empty() { + // let old_head = selection.head(); + // let mut new_head = + // movement::left(&display_map, old_head.to_display_point(&display_map)) + // .to_point(&display_map); + // if let Some((buffer, line_buffer_range)) = display_map + // .buffer_snapshot + // .buffer_line_for_row(old_head.row) + // { + // let indent_size = + // buffer.indent_size_for_line(line_buffer_range.start.row); + // let indent_len = match indent_size.kind { + // IndentKind::Space => { + // buffer.settings_at(line_buffer_range.start, cx).tab_size + // } + // IndentKind::Tab => NonZeroU32::new(1).unwrap(), + // }; + // if old_head.column <= indent_size.len && old_head.column > 0 { + // let indent_len = indent_len.get(); + // new_head = cmp::min( + // new_head, + // Point::new( + // old_head.row, + // ((old_head.column - 1) / indent_len) * indent_len, + // ), + // ); + // } + // } + + // selection.set_head(new_head, SelectionGoal::None); + // } + // } + // } + + // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); + // this.insert("", cx); + // this.refresh_copilot_suggestions(true, cx); + // }); + // } + + // pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext) { + // self.transact(cx, |this, cx| { + // this.change_selections(Some(Autoscroll::fit()), cx, |s| { + // let line_mode = s.line_mode; + // s.move_with(|map, selection| { + // if selection.is_empty() && !line_mode { + // let cursor = movement::right(map, selection.head()); + // selection.end = cursor; + // selection.reversed = true; + // selection.goal = SelectionGoal::None; + // } + // }) + // }); + // this.insert("", cx); + // this.refresh_copilot_suggestions(true, cx); + // }); + // } + + // pub fn tab_prev(&mut self, _: &TabPrev, cx: &mut ViewContext) { + // if self.move_to_prev_snippet_tabstop(cx) { + // return; + // } + + // self.outdent(&Outdent, cx); + // } + + // pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext) { + // if self.move_to_next_snippet_tabstop(cx) { + // return; + // } + + // let mut selections = self.selections.all_adjusted(cx); + // let buffer = self.buffer.read(cx); + // let snapshot = buffer.snapshot(cx); + // let rows_iter = selections.iter().map(|s| s.head().row); + // let suggested_indents = snapshot.suggested_indents(rows_iter, cx); + + // let mut edits = Vec::new(); + // let mut prev_edited_row = 0; + // let mut row_delta = 0; + // for selection in &mut selections { + // if selection.start.row != prev_edited_row { + // row_delta = 0; + // } + // prev_edited_row = selection.end.row; + + // // If the selection is non-empty, then increase the indentation of the selected lines. + // if !selection.is_empty() { + // row_delta = + // Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx); + // continue; + // } + + // // If the selection is empty and the cursor is in the leading whitespace before the + // // suggested indentation, then auto-indent the line. + // let cursor = selection.head(); + // let current_indent = snapshot.indent_size_for_line(cursor.row); + // if let Some(suggested_indent) = suggested_indents.get(&cursor.row).copied() { + // if cursor.column < suggested_indent.len + // && cursor.column <= current_indent.len + // && current_indent.len <= suggested_indent.len + // { + // selection.start = Point::new(cursor.row, suggested_indent.len); + // selection.end = selection.start; + // if row_delta == 0 { + // edits.extend(Buffer::edit_for_indent_size_adjustment( + // cursor.row, + // current_indent, + // suggested_indent, + // )); + // row_delta = suggested_indent.len - current_indent.len; + // } + // continue; + // } + // } + + // // Accept copilot suggestion if there is only one selection and the cursor is not + // // in the leading whitespace. + // if self.selections.count() == 1 + // && cursor.column >= current_indent.len + // && self.has_active_copilot_suggestion(cx) + // { + // self.accept_copilot_suggestion(cx); + // return; + // } + + // // Otherwise, insert a hard or soft tab. + // let settings = buffer.settings_at(cursor, cx); + // let tab_size = if settings.hard_tabs { + // IndentSize::tab() + // } else { + // let tab_size = settings.tab_size.get(); + // let char_column = snapshot + // .text_for_range(Point::new(cursor.row, 0)..cursor) + // .flat_map(str::chars) + // .count() + // + row_delta as usize; + // let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size); + // IndentSize::spaces(chars_to_next_tab_stop) + // }; + // selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len); + // selection.end = selection.start; + // edits.push((cursor..cursor, tab_size.chars().collect::())); + // row_delta += tab_size.len; + // } + + // self.transact(cx, |this, cx| { + // this.buffer.update(cx, |b, cx| b.edit(edits, None, cx)); + // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); + // this.refresh_copilot_suggestions(true, cx); + // }); + // } + + // pub fn indent(&mut self, _: &Indent, cx: &mut ViewContext) { + // let mut selections = self.selections.all::(cx); + // let mut prev_edited_row = 0; + // let mut row_delta = 0; + // let mut edits = Vec::new(); + // let buffer = self.buffer.read(cx); + // let snapshot = buffer.snapshot(cx); + // for selection in &mut selections { + // if selection.start.row != prev_edited_row { + // row_delta = 0; + // } + // prev_edited_row = selection.end.row; + + // row_delta = + // Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx); + // } + + // self.transact(cx, |this, cx| { + // this.buffer.update(cx, |b, cx| b.edit(edits, None, cx)); + // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); + // }); + // } + + // fn indent_selection( + // buffer: &MultiBuffer, + // snapshot: &MultiBufferSnapshot, + // selection: &mut Selection, + // edits: &mut Vec<(Range, String)>, + // delta_for_start_row: u32, + // cx: &AppContext, + // ) -> u32 { + // let settings = buffer.settings_at(selection.start, cx); + // let tab_size = settings.tab_size.get(); + // let indent_kind = if settings.hard_tabs { + // IndentKind::Tab + // } else { + // IndentKind::Space + // }; + // let mut start_row = selection.start.row; + // let mut end_row = selection.end.row + 1; + + // // If a selection ends at the beginning of a line, don't indent + // // that last line. + // if selection.end.column == 0 { + // end_row -= 1; + // } + + // // Avoid re-indenting a row that has already been indented by a + // // previous selection, but still update this selection's column + // // to reflect that indentation. + // if delta_for_start_row > 0 { + // start_row += 1; + // selection.start.column += delta_for_start_row; + // if selection.end.row == selection.start.row { + // selection.end.column += delta_for_start_row; + // } + // } + + // let mut delta_for_end_row = 0; + // for row in start_row..end_row { + // let current_indent = snapshot.indent_size_for_line(row); + // let indent_delta = match (current_indent.kind, indent_kind) { + // (IndentKind::Space, IndentKind::Space) => { + // let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size); + // IndentSize::spaces(columns_to_next_tab_stop) + // } + // (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size), + // (_, IndentKind::Tab) => IndentSize::tab(), + // }; + + // let row_start = Point::new(row, 0); + // edits.push(( + // row_start..row_start, + // indent_delta.chars().collect::(), + // )); + + // // Update this selection's endpoints to reflect the indentation. + // if row == selection.start.row { + // selection.start.column += indent_delta.len; + // } + // if row == selection.end.row { + // selection.end.column += indent_delta.len; + // delta_for_end_row = indent_delta.len; + // } + // } + + // if selection.start.row == selection.end.row { + // delta_for_start_row + delta_for_end_row + // } else { + // delta_for_end_row + // } + // } + + // pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext) { + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + // let selections = self.selections.all::(cx); + // let mut deletion_ranges = Vec::new(); + // let mut last_outdent = None; + // { + // let buffer = self.buffer.read(cx); + // let snapshot = buffer.snapshot(cx); + // for selection in &selections { + // let settings = buffer.settings_at(selection.start, cx); + // let tab_size = settings.tab_size.get(); + // let mut rows = selection.spanned_rows(false, &display_map); + + // // Avoid re-outdenting a row that has already been outdented by a + // // previous selection. + // if let Some(last_row) = last_outdent { + // if last_row == rows.start { + // rows.start += 1; + // } + // } + + // for row in rows { + // let indent_size = snapshot.indent_size_for_line(row); + // if indent_size.len > 0 { + // let deletion_len = match indent_size.kind { + // IndentKind::Space => { + // let columns_to_prev_tab_stop = indent_size.len % tab_size; + // if columns_to_prev_tab_stop == 0 { + // tab_size + // } else { + // columns_to_prev_tab_stop + // } + // } + // IndentKind::Tab => 1, + // }; + // deletion_ranges.push(Point::new(row, 0)..Point::new(row, deletion_len)); + // last_outdent = Some(row); + // } + // } + // } + // } + + // self.transact(cx, |this, cx| { + // this.buffer.update(cx, |buffer, cx| { + // let empty_str: Arc = "".into(); + // buffer.edit( + // deletion_ranges + // .into_iter() + // .map(|range| (range, empty_str.clone())), + // None, + // cx, + // ); + // }); + // let selections = this.selections.all::(cx); + // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); + // }); + // } + + // pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext) { + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + // let selections = self.selections.all::(cx); + + // let mut new_cursors = Vec::new(); + // let mut edit_ranges = Vec::new(); + // let mut selections = selections.iter().peekable(); + // while let Some(selection) = selections.next() { + // let mut rows = selection.spanned_rows(false, &display_map); + // let goal_display_column = selection.head().to_display_point(&display_map).column(); + + // // Accumulate contiguous regions of rows that we want to delete. + // while let Some(next_selection) = selections.peek() { + // let next_rows = next_selection.spanned_rows(false, &display_map); + // if next_rows.start <= rows.end { + // rows.end = next_rows.end; + // selections.next().unwrap(); + // } else { + // break; + // } + // } + + // let buffer = &display_map.buffer_snapshot; + // let mut edit_start = Point::new(rows.start, 0).to_offset(buffer); + // let edit_end; + // let cursor_buffer_row; + // if buffer.max_point().row >= rows.end { + // // If there's a line after the range, delete the \n from the end of the row range + // // and position the cursor on the next line. + // edit_end = Point::new(rows.end, 0).to_offset(buffer); + // cursor_buffer_row = rows.end; + // } else { + // // If there isn't a line after the range, delete the \n from the line before the + // // start of the row range and position the cursor there. + // edit_start = edit_start.saturating_sub(1); + // edit_end = buffer.len(); + // cursor_buffer_row = rows.start.saturating_sub(1); + // } + + // let mut cursor = Point::new(cursor_buffer_row, 0).to_display_point(&display_map); + // *cursor.column_mut() = + // cmp::min(goal_display_column, display_map.line_len(cursor.row())); + + // new_cursors.push(( + // selection.id, + // buffer.anchor_after(cursor.to_point(&display_map)), + // )); + // edit_ranges.push(edit_start..edit_end); + // } + + // self.transact(cx, |this, cx| { + // let buffer = this.buffer.update(cx, |buffer, cx| { + // let empty_str: Arc = "".into(); + // buffer.edit( + // edit_ranges + // .into_iter() + // .map(|range| (range, empty_str.clone())), + // None, + // cx, + // ); + // buffer.snapshot(cx) + // }); + // let new_selections = new_cursors + // .into_iter() + // .map(|(id, cursor)| { + // let cursor = cursor.to_point(&buffer); + // Selection { + // id, + // start: cursor, + // end: cursor, + // reversed: false, + // goal: SelectionGoal::None, + // } + // }) + // .collect(); + + // this.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select(new_selections); + // }); + // }); + // } + + // pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext) { + // let mut row_ranges = Vec::>::new(); + // for selection in self.selections.all::(cx) { + // let start = selection.start.row; + // let end = if selection.start.row == selection.end.row { + // selection.start.row + 1 + // } else { + // selection.end.row + // }; + + // if let Some(last_row_range) = row_ranges.last_mut() { + // if start <= last_row_range.end { + // last_row_range.end = end; + // continue; + // } + // } + // row_ranges.push(start..end); + // } + + // let snapshot = self.buffer.read(cx).snapshot(cx); + // let mut cursor_positions = Vec::new(); + // for row_range in &row_ranges { + // let anchor = snapshot.anchor_before(Point::new( + // row_range.end - 1, + // snapshot.line_len(row_range.end - 1), + // )); + // cursor_positions.push(anchor.clone()..anchor); + // } + + // self.transact(cx, |this, cx| { + // for row_range in row_ranges.into_iter().rev() { + // for row in row_range.rev() { + // let end_of_line = Point::new(row, snapshot.line_len(row)); + // let indent = snapshot.indent_size_for_line(row + 1); + // let start_of_next_line = Point::new(row + 1, indent.len); + + // let replace = if snapshot.line_len(row + 1) > indent.len { + // " " + // } else { + // "" + // }; + + // this.buffer.update(cx, |buffer, cx| { + // buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx) + // }); + // } + // } + + // this.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select_anchor_ranges(cursor_positions) + // }); + // }); + // } + + // pub fn sort_lines_case_sensitive( + // &mut self, + // _: &SortLinesCaseSensitive, + // cx: &mut ViewContext, + // ) { + // self.manipulate_lines(cx, |lines| lines.sort()) + // } + + // pub fn sort_lines_case_insensitive( + // &mut self, + // _: &SortLinesCaseInsensitive, + // cx: &mut ViewContext, + // ) { + // self.manipulate_lines(cx, |lines| lines.sort_by_key(|line| line.to_lowercase())) + // } + + // pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext) { + // self.manipulate_lines(cx, |lines| lines.reverse()) + // } + + // pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext) { + // self.manipulate_lines(cx, |lines| lines.shuffle(&mut thread_rng())) + // } + + // fn manipulate_lines(&mut self, cx: &mut ViewContext, mut callback: Fn) + // where + // Fn: FnMut(&mut [&str]), + // { + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + // let buffer = self.buffer.read(cx).snapshot(cx); + + // let mut edits = Vec::new(); + + // let selections = self.selections.all::(cx); + // let mut selections = selections.iter().peekable(); + // let mut contiguous_row_selections = Vec::new(); + // let mut new_selections = Vec::new(); + + // while let Some(selection) = selections.next() { + // let (start_row, end_row) = consume_contiguous_rows( + // &mut contiguous_row_selections, + // selection, + // &display_map, + // &mut selections, + // ); + + // let start_point = Point::new(start_row, 0); + // let end_point = Point::new(end_row - 1, buffer.line_len(end_row - 1)); + // let text = buffer + // .text_for_range(start_point..end_point) + // .collect::(); + // let mut lines = text.split("\n").collect_vec(); + + // let lines_len = lines.len(); + // callback(&mut lines); + + // // This is a current limitation with selections. + // // If we wanted to support removing or adding lines, we'd need to fix the logic associated with selections. + // debug_assert!( + // lines.len() == lines_len, + // "callback should not change the number of lines" + // ); + + // edits.push((start_point..end_point, lines.join("\n"))); + // let start_anchor = buffer.anchor_after(start_point); + // let end_anchor = buffer.anchor_before(end_point); + + // // Make selection and push + // new_selections.push(Selection { + // id: selection.id, + // start: start_anchor.to_offset(&buffer), + // end: end_anchor.to_offset(&buffer), + // goal: SelectionGoal::None, + // reversed: selection.reversed, + // }); + // } + + // self.transact(cx, |this, cx| { + // this.buffer.update(cx, |buffer, cx| { + // buffer.edit(edits, None, cx); + // }); + + // this.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select(new_selections); + // }); + + // this.request_autoscroll(Autoscroll::fit(), cx); + // }); + // } + + // pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext) { + // self.manipulate_text(cx, |text| text.to_uppercase()) + // } + + // pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext) { + // self.manipulate_text(cx, |text| text.to_lowercase()) + // } + + // pub fn convert_to_title_case(&mut self, _: &ConvertToTitleCase, cx: &mut ViewContext) { + // self.manipulate_text(cx, |text| { + // // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary + // // https://github.com/rutrum/convert-case/issues/16 + // text.split("\n") + // .map(|line| line.to_case(Case::Title)) + // .join("\n") + // }) + // } + + // pub fn convert_to_snake_case(&mut self, _: &ConvertToSnakeCase, cx: &mut ViewContext) { + // self.manipulate_text(cx, |text| text.to_case(Case::Snake)) + // } + + // pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext) { + // self.manipulate_text(cx, |text| text.to_case(Case::Kebab)) + // } + + // pub fn convert_to_upper_camel_case( + // &mut self, + // _: &ConvertToUpperCamelCase, + // cx: &mut ViewContext, + // ) { + // self.manipulate_text(cx, |text| { + // // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary + // // https://github.com/rutrum/convert-case/issues/16 + // text.split("\n") + // .map(|line| line.to_case(Case::UpperCamel)) + // .join("\n") + // }) + // } + + // pub fn convert_to_lower_camel_case( + // &mut self, + // _: &ConvertToLowerCamelCase, + // cx: &mut ViewContext, + // ) { + // self.manipulate_text(cx, |text| text.to_case(Case::Camel)) + // } + + // fn manipulate_text(&mut self, cx: &mut ViewContext, mut callback: Fn) + // where + // Fn: FnMut(&str) -> String, + // { + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + // let buffer = self.buffer.read(cx).snapshot(cx); + + // let mut new_selections = Vec::new(); + // let mut edits = Vec::new(); + // let mut selection_adjustment = 0i32; + + // for selection in self.selections.all::(cx) { + // let selection_is_empty = selection.is_empty(); + + // let (start, end) = if selection_is_empty { + // let word_range = movement::surrounding_word( + // &display_map, + // selection.start.to_display_point(&display_map), + // ); + // let start = word_range.start.to_offset(&display_map, Bias::Left); + // let end = word_range.end.to_offset(&display_map, Bias::Left); + // (start, end) + // } else { + // (selection.start, selection.end) + // }; + + // let text = buffer.text_for_range(start..end).collect::(); + // let old_length = text.len() as i32; + // let text = callback(&text); + + // new_selections.push(Selection { + // start: (start as i32 - selection_adjustment) as usize, + // end: ((start + text.len()) as i32 - selection_adjustment) as usize, + // goal: SelectionGoal::None, + // ..selection + // }); + + // selection_adjustment += old_length - text.len() as i32; + + // edits.push((start..end, text)); + // } + + // self.transact(cx, |this, cx| { + // this.buffer.update(cx, |buffer, cx| { + // buffer.edit(edits, None, cx); + // }); + + // this.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select(new_selections); + // }); + + // this.request_autoscroll(Autoscroll::fit(), cx); + // }); + // } + + // pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext) { + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + // let buffer = &display_map.buffer_snapshot; + // let selections = self.selections.all::(cx); + + // let mut edits = Vec::new(); + // let mut selections_iter = selections.iter().peekable(); + // while let Some(selection) = selections_iter.next() { + // // Avoid duplicating the same lines twice. + // let mut rows = selection.spanned_rows(false, &display_map); + + // while let Some(next_selection) = selections_iter.peek() { + // let next_rows = next_selection.spanned_rows(false, &display_map); + // if next_rows.start < rows.end { + // rows.end = next_rows.end; + // selections_iter.next().unwrap(); + // } else { + // break; + // } + // } + + // // Copy the text from the selected row region and splice it at the start of the region. + // let start = Point::new(rows.start, 0); + // let end = Point::new(rows.end - 1, buffer.line_len(rows.end - 1)); + // let text = buffer + // .text_for_range(start..end) + // .chain(Some("\n")) + // .collect::(); + // edits.push((start..start, text)); + // } + + // self.transact(cx, |this, cx| { + // this.buffer.update(cx, |buffer, cx| { + // buffer.edit(edits, None, cx); + // }); + + // this.request_autoscroll(Autoscroll::fit(), cx); + // }); + // } + + // pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext) { + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + // let buffer = self.buffer.read(cx).snapshot(cx); + + // let mut edits = Vec::new(); + // let mut unfold_ranges = Vec::new(); + // let mut refold_ranges = Vec::new(); + + // let selections = self.selections.all::(cx); + // let mut selections = selections.iter().peekable(); + // let mut contiguous_row_selections = Vec::new(); + // let mut new_selections = Vec::new(); + + // while let Some(selection) = selections.next() { + // // Find all the selections that span a contiguous row range + // let (start_row, end_row) = consume_contiguous_rows( + // &mut contiguous_row_selections, + // selection, + // &display_map, + // &mut selections, + // ); + + // // Move the text spanned by the row range to be before the line preceding the row range + // if start_row > 0 { + // let range_to_move = Point::new(start_row - 1, buffer.line_len(start_row - 1)) + // ..Point::new(end_row - 1, buffer.line_len(end_row - 1)); + // let insertion_point = display_map + // .prev_line_boundary(Point::new(start_row - 1, 0)) + // .0; + + // // Don't move lines across excerpts + // if buffer + // .excerpt_boundaries_in_range(( + // Bound::Excluded(insertion_point), + // Bound::Included(range_to_move.end), + // )) + // .next() + // .is_none() + // { + // let text = buffer + // .text_for_range(range_to_move.clone()) + // .flat_map(|s| s.chars()) + // .skip(1) + // .chain(['\n']) + // .collect::(); + + // edits.push(( + // buffer.anchor_after(range_to_move.start) + // ..buffer.anchor_before(range_to_move.end), + // String::new(), + // )); + // let insertion_anchor = buffer.anchor_after(insertion_point); + // edits.push((insertion_anchor..insertion_anchor, text)); + + // let row_delta = range_to_move.start.row - insertion_point.row + 1; + + // // Move selections up + // new_selections.extend(contiguous_row_selections.drain(..).map( + // |mut selection| { + // selection.start.row -= row_delta; + // selection.end.row -= row_delta; + // selection + // }, + // )); + + // // Move folds up + // unfold_ranges.push(range_to_move.clone()); + // for fold in display_map.folds_in_range( + // buffer.anchor_before(range_to_move.start) + // ..buffer.anchor_after(range_to_move.end), + // ) { + // let mut start = fold.start.to_point(&buffer); + // let mut end = fold.end.to_point(&buffer); + // start.row -= row_delta; + // end.row -= row_delta; + // refold_ranges.push(start..end); + // } + // } + // } + + // // If we didn't move line(s), preserve the existing selections + // new_selections.append(&mut contiguous_row_selections); + // } + + // self.transact(cx, |this, cx| { + // this.unfold_ranges(unfold_ranges, true, true, cx); + // this.buffer.update(cx, |buffer, cx| { + // for (range, text) in edits { + // buffer.edit([(range, text)], None, cx); + // } + // }); + // this.fold_ranges(refold_ranges, true, cx); + // this.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select(new_selections); + // }) + // }); + // } + + // pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext) { + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + // let buffer = self.buffer.read(cx).snapshot(cx); + + // let mut edits = Vec::new(); + // let mut unfold_ranges = Vec::new(); + // let mut refold_ranges = Vec::new(); + + // let selections = self.selections.all::(cx); + // let mut selections = selections.iter().peekable(); + // let mut contiguous_row_selections = Vec::new(); + // let mut new_selections = Vec::new(); + + // while let Some(selection) = selections.next() { + // // Find all the selections that span a contiguous row range + // let (start_row, end_row) = consume_contiguous_rows( + // &mut contiguous_row_selections, + // selection, + // &display_map, + // &mut selections, + // ); + + // // Move the text spanned by the row range to be after the last line of the row range + // if end_row <= buffer.max_point().row { + // let range_to_move = Point::new(start_row, 0)..Point::new(end_row, 0); + // let insertion_point = display_map.next_line_boundary(Point::new(end_row, 0)).0; + + // // Don't move lines across excerpt boundaries + // if buffer + // .excerpt_boundaries_in_range(( + // Bound::Excluded(range_to_move.start), + // Bound::Included(insertion_point), + // )) + // .next() + // .is_none() + // { + // let mut text = String::from("\n"); + // text.extend(buffer.text_for_range(range_to_move.clone())); + // text.pop(); // Drop trailing newline + // edits.push(( + // buffer.anchor_after(range_to_move.start) + // ..buffer.anchor_before(range_to_move.end), + // String::new(), + // )); + // let insertion_anchor = buffer.anchor_after(insertion_point); + // edits.push((insertion_anchor..insertion_anchor, text)); + + // let row_delta = insertion_point.row - range_to_move.end.row + 1; + + // // Move selections down + // new_selections.extend(contiguous_row_selections.drain(..).map( + // |mut selection| { + // selection.start.row += row_delta; + // selection.end.row += row_delta; + // selection + // }, + // )); + + // // Move folds down + // unfold_ranges.push(range_to_move.clone()); + // for fold in display_map.folds_in_range( + // buffer.anchor_before(range_to_move.start) + // ..buffer.anchor_after(range_to_move.end), + // ) { + // let mut start = fold.start.to_point(&buffer); + // let mut end = fold.end.to_point(&buffer); + // start.row += row_delta; + // end.row += row_delta; + // refold_ranges.push(start..end); + // } + // } + // } + + // // If we didn't move line(s), preserve the existing selections + // new_selections.append(&mut contiguous_row_selections); + // } + + // self.transact(cx, |this, cx| { + // this.unfold_ranges(unfold_ranges, true, true, cx); + // this.buffer.update(cx, |buffer, cx| { + // for (range, text) in edits { + // buffer.edit([(range, text)], None, cx); + // } + // }); + // this.fold_ranges(refold_ranges, true, cx); + // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); + // }); + // } + + // pub fn transpose(&mut self, _: &Transpose, cx: &mut ViewContext) { + // let text_layout_details = &self.text_layout_details(cx); + // self.transact(cx, |this, cx| { + // let edits = this.change_selections(Some(Autoscroll::fit()), cx, |s| { + // let mut edits: Vec<(Range, String)> = Default::default(); + // let line_mode = s.line_mode; + // s.move_with(|display_map, selection| { + // if !selection.is_empty() || line_mode { + // return; + // } + + // let mut head = selection.head(); + // let mut transpose_offset = head.to_offset(display_map, Bias::Right); + // if head.column() == display_map.line_len(head.row()) { + // transpose_offset = display_map + // .buffer_snapshot + // .clip_offset(transpose_offset.saturating_sub(1), Bias::Left); + // } + + // if transpose_offset == 0 { + // return; + // } + + // *head.column_mut() += 1; + // head = display_map.clip_point(head, Bias::Right); + // let goal = SelectionGoal::HorizontalPosition( + // display_map.x_for_point(head, &text_layout_details), + // ); + // selection.collapse_to(head, goal); + + // let transpose_start = display_map + // .buffer_snapshot + // .clip_offset(transpose_offset.saturating_sub(1), Bias::Left); + // if edits.last().map_or(true, |e| e.0.end <= transpose_start) { + // let transpose_end = display_map + // .buffer_snapshot + // .clip_offset(transpose_offset + 1, Bias::Right); + // if let Some(ch) = + // display_map.buffer_snapshot.chars_at(transpose_start).next() + // { + // edits.push((transpose_start..transpose_offset, String::new())); + // edits.push((transpose_end..transpose_end, ch.to_string())); + // } + // } + // }); + // edits + // }); + // this.buffer + // .update(cx, |buffer, cx| buffer.edit(edits, None, cx)); + // let selections = this.selections.all::(cx); + // this.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select(selections); + // }); + // }); + // } + + // pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext) { + // let mut text = String::new(); + // let buffer = self.buffer.read(cx).snapshot(cx); + // let mut selections = self.selections.all::(cx); + // let mut clipboard_selections = Vec::with_capacity(selections.len()); + // { + // let max_point = buffer.max_point(); + // let mut is_first = true; + // for selection in &mut selections { + // let is_entire_line = selection.is_empty() || self.selections.line_mode; + // if is_entire_line { + // selection.start = Point::new(selection.start.row, 0); + // selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0)); + // selection.goal = SelectionGoal::None; + // } + // if is_first { + // is_first = false; + // } else { + // text += "\n"; + // } + // let mut len = 0; + // for chunk in buffer.text_for_range(selection.start..selection.end) { + // text.push_str(chunk); + // len += chunk.len(); + // } + // clipboard_selections.push(ClipboardSelection { + // len, + // is_entire_line, + // first_line_indent: buffer.indent_size_for_line(selection.start.row).len, + // }); + // } + // } + + // self.transact(cx, |this, cx| { + // this.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select(selections); + // }); + // this.insert("", cx); + // cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections)); + // }); + // } + + // pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { + // let selections = self.selections.all::(cx); + // let buffer = self.buffer.read(cx).read(cx); + // let mut text = String::new(); + + // let mut clipboard_selections = Vec::with_capacity(selections.len()); + // { + // let max_point = buffer.max_point(); + // let mut is_first = true; + // for selection in selections.iter() { + // let mut start = selection.start; + // let mut end = selection.end; + // let is_entire_line = selection.is_empty() || self.selections.line_mode; + // if is_entire_line { + // start = Point::new(start.row, 0); + // end = cmp::min(max_point, Point::new(end.row + 1, 0)); + // } + // if is_first { + // is_first = false; + // } else { + // text += "\n"; + // } + // let mut len = 0; + // for chunk in buffer.text_for_range(start..end) { + // text.push_str(chunk); + // len += chunk.len(); + // } + // clipboard_selections.push(ClipboardSelection { + // len, + // is_entire_line, + // first_line_indent: buffer.indent_size_for_line(start.row).len, + // }); + // } + // } + + // cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections)); + // } + + // pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { + // self.transact(cx, |this, cx| { + // if let Some(item) = cx.read_from_clipboard() { + // let clipboard_text = Cow::Borrowed(item.text()); + // if let Some(mut clipboard_selections) = item.metadata::>() { + // let old_selections = this.selections.all::(cx); + // let all_selections_were_entire_line = + // clipboard_selections.iter().all(|s| s.is_entire_line); + // let first_selection_indent_column = + // clipboard_selections.first().map(|s| s.first_line_indent); + // if clipboard_selections.len() != old_selections.len() { + // clipboard_selections.drain(..); + // } + + // this.buffer.update(cx, |buffer, cx| { + // let snapshot = buffer.read(cx); + // let mut start_offset = 0; + // let mut edits = Vec::new(); + // let mut original_indent_columns = Vec::new(); + // let line_mode = this.selections.line_mode; + // for (ix, selection) in old_selections.iter().enumerate() { + // let to_insert; + // let entire_line; + // let original_indent_column; + // if let Some(clipboard_selection) = clipboard_selections.get(ix) { + // let end_offset = start_offset + clipboard_selection.len; + // to_insert = &clipboard_text[start_offset..end_offset]; + // entire_line = clipboard_selection.is_entire_line; + // start_offset = end_offset + 1; + // original_indent_column = + // Some(clipboard_selection.first_line_indent); + // } else { + // to_insert = clipboard_text.as_str(); + // entire_line = all_selections_were_entire_line; + // original_indent_column = first_selection_indent_column + // } + + // // If the corresponding selection was empty when this slice of the + // // clipboard text was written, then the entire line containing the + // // selection was copied. If this selection is also currently empty, + // // then paste the line before the current line of the buffer. + // let range = if selection.is_empty() && !line_mode && entire_line { + // let column = selection.start.to_point(&snapshot).column as usize; + // let line_start = selection.start - column; + // line_start..line_start + // } else { + // selection.range() + // }; + + // edits.push((range, to_insert)); + // original_indent_columns.extend(original_indent_column); + // } + // drop(snapshot); + + // buffer.edit( + // edits, + // Some(AutoindentMode::Block { + // original_indent_columns, + // }), + // cx, + // ); + // }); + + // let selections = this.selections.all::(cx); + // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); + // } else { + // this.insert(&clipboard_text, cx); + // } + // } + // }); + // } + + // pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext) { + // if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) { + // if let Some((selections, _)) = self.selection_history.transaction(tx_id).cloned() { + // self.change_selections(None, cx, |s| { + // s.select_anchors(selections.to_vec()); + // }); + // } + // self.request_autoscroll(Autoscroll::fit(), cx); + // self.unmark_text(cx); + // self.refresh_copilot_suggestions(true, cx); + // cx.emit(Event::Edited); + // } + // } + + // pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext) { + // if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) { + // if let Some((_, Some(selections))) = self.selection_history.transaction(tx_id).cloned() + // { + // self.change_selections(None, cx, |s| { + // s.select_anchors(selections.to_vec()); + // }); + // } + // self.request_autoscroll(Autoscroll::fit(), cx); + // self.unmark_text(cx); + // self.refresh_copilot_suggestions(true, cx); + // cx.emit(Event::Edited); + // } + // } + + // pub fn finalize_last_transaction(&mut self, cx: &mut ViewContext) { + // self.buffer + // .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx)); + // } + + // pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // let line_mode = s.line_mode; + // s.move_with(|map, selection| { + // let cursor = if selection.is_empty() && !line_mode { + // movement::left(map, selection.start) + // } else { + // selection.start + // }; + // selection.collapse_to(cursor, SelectionGoal::None); + // }); + // }) + // } + + // pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None)); + // }) + // } + + // pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // let line_mode = s.line_mode; + // s.move_with(|map, selection| { + // let cursor = if selection.is_empty() && !line_mode { + // movement::right(map, selection.end) + // } else { + // selection.end + // }; + // selection.collapse_to(cursor, SelectionGoal::None) + // }); + // }) + // } + + // pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None)); + // }) + // } + + // pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext) { + // if self.take_rename(true, cx).is_some() { + // return; + // } + + // if matches!(self.mode, EditorMode::SingleLine) { + // cx.propagate_action(); + // return; + // } + + // let text_layout_details = &self.text_layout_details(cx); + + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // let line_mode = s.line_mode; + // s.move_with(|map, selection| { + // if !selection.is_empty() && !line_mode { + // selection.goal = SelectionGoal::None; + // } + // let (cursor, goal) = movement::up( + // map, + // selection.start, + // selection.goal, + // false, + // &text_layout_details, + // ); + // selection.collapse_to(cursor, goal); + // }); + // }) + // } + + // pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext) { + // if self.take_rename(true, cx).is_some() { + // return; + // } + + // if matches!(self.mode, EditorMode::SingleLine) { + // cx.propagate_action(); + // return; + // } + + // let row_count = if let Some(row_count) = self.visible_line_count() { + // row_count as u32 - 1 + // } else { + // return; + // }; + + // let autoscroll = if action.center_cursor { + // Autoscroll::center() + // } else { + // Autoscroll::fit() + // }; + + // let text_layout_details = &self.text_layout_details(cx); + + // self.change_selections(Some(autoscroll), cx, |s| { + // let line_mode = s.line_mode; + // s.move_with(|map, selection| { + // if !selection.is_empty() && !line_mode { + // selection.goal = SelectionGoal::None; + // } + // let (cursor, goal) = movement::up_by_rows( + // map, + // selection.end, + // row_count, + // selection.goal, + // false, + // &text_layout_details, + // ); + // selection.collapse_to(cursor, goal); + // }); + // }); + // } + + // pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext) { + // let text_layout_details = &self.text_layout_details(cx); + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_heads_with(|map, head, goal| { + // movement::up(map, head, goal, false, &text_layout_details) + // }) + // }) + // } + + // pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext) { + // self.take_rename(true, cx); + + // if self.mode == EditorMode::SingleLine { + // cx.propagate_action(); + // return; + // } + + // let text_layout_details = &self.text_layout_details(cx); + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // let line_mode = s.line_mode; + // s.move_with(|map, selection| { + // if !selection.is_empty() && !line_mode { + // selection.goal = SelectionGoal::None; + // } + // let (cursor, goal) = movement::down( + // map, + // selection.end, + // selection.goal, + // false, + // &text_layout_details, + // ); + // selection.collapse_to(cursor, goal); + // }); + // }); + // } + + // pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext) { + // if self.take_rename(true, cx).is_some() { + // return; + // } + + // if self + // .context_menu + // .write() + // .as_mut() + // .map(|menu| menu.select_last(self.project.as_ref(), cx)) + // .unwrap_or(false) + // { + // return; + // } + + // if matches!(self.mode, EditorMode::SingleLine) { + // cx.propagate_action(); + // return; + // } + + // let row_count = if let Some(row_count) = self.visible_line_count() { + // row_count as u32 - 1 + // } else { + // return; + // }; + + // let autoscroll = if action.center_cursor { + // Autoscroll::center() + // } else { + // Autoscroll::fit() + // }; + + // let text_layout_details = &self.text_layout_details(cx); + // self.change_selections(Some(autoscroll), cx, |s| { + // let line_mode = s.line_mode; + // s.move_with(|map, selection| { + // if !selection.is_empty() && !line_mode { + // selection.goal = SelectionGoal::None; + // } + // let (cursor, goal) = movement::down_by_rows( + // map, + // selection.end, + // row_count, + // selection.goal, + // false, + // &text_layout_details, + // ); + // selection.collapse_to(cursor, goal); + // }); + // }); + // } + + // pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext) { + // let text_layout_details = &self.text_layout_details(cx); + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_heads_with(|map, head, goal| { + // movement::down(map, head, goal, false, &text_layout_details) + // }) + // }); + // } + + // pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext) { + // if let Some(context_menu) = self.context_menu.write().as_mut() { + // context_menu.select_first(self.project.as_ref(), cx); + // } + // } + + // pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext) { + // if let Some(context_menu) = self.context_menu.write().as_mut() { + // context_menu.select_prev(self.project.as_ref(), cx); + // } + // } + + // pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext) { + // if let Some(context_menu) = self.context_menu.write().as_mut() { + // context_menu.select_next(self.project.as_ref(), cx); + // } + // } + + // pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext) { + // if let Some(context_menu) = self.context_menu.write().as_mut() { + // context_menu.select_last(self.project.as_ref(), cx); + // } + // } + + // pub fn move_to_previous_word_start( + // &mut self, + // _: &MoveToPreviousWordStart, + // cx: &mut ViewContext, + // ) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_cursors_with(|map, head, _| { + // ( + // movement::previous_word_start(map, head), + // SelectionGoal::None, + // ) + // }); + // }) + // } + + // pub fn move_to_previous_subword_start( + // &mut self, + // _: &MoveToPreviousSubwordStart, + // cx: &mut ViewContext, + // ) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_cursors_with(|map, head, _| { + // ( + // movement::previous_subword_start(map, head), + // SelectionGoal::None, + // ) + // }); + // }) + // } + + // pub fn select_to_previous_word_start( + // &mut self, + // _: &SelectToPreviousWordStart, + // cx: &mut ViewContext, + // ) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_heads_with(|map, head, _| { + // ( + // movement::previous_word_start(map, head), + // SelectionGoal::None, + // ) + // }); + // }) + // } + + // pub fn select_to_previous_subword_start( + // &mut self, + // _: &SelectToPreviousSubwordStart, + // cx: &mut ViewContext, + // ) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_heads_with(|map, head, _| { + // ( + // movement::previous_subword_start(map, head), + // SelectionGoal::None, + // ) + // }); + // }) + // } + + // pub fn delete_to_previous_word_start( + // &mut self, + // _: &DeleteToPreviousWordStart, + // cx: &mut ViewContext, + // ) { + // self.transact(cx, |this, cx| { + // this.select_autoclose_pair(cx); + // this.change_selections(Some(Autoscroll::fit()), cx, |s| { + // let line_mode = s.line_mode; + // s.move_with(|map, selection| { + // if selection.is_empty() && !line_mode { + // let cursor = movement::previous_word_start(map, selection.head()); + // selection.set_head(cursor, SelectionGoal::None); + // } + // }); + // }); + // this.insert("", cx); + // }); + // } + + // pub fn delete_to_previous_subword_start( + // &mut self, + // _: &DeleteToPreviousSubwordStart, + // cx: &mut ViewContext, + // ) { + // self.transact(cx, |this, cx| { + // this.select_autoclose_pair(cx); + // this.change_selections(Some(Autoscroll::fit()), cx, |s| { + // let line_mode = s.line_mode; + // s.move_with(|map, selection| { + // if selection.is_empty() && !line_mode { + // let cursor = movement::previous_subword_start(map, selection.head()); + // selection.set_head(cursor, SelectionGoal::None); + // } + // }); + // }); + // this.insert("", cx); + // }); + // } + + // pub fn move_to_next_word_end(&mut self, _: &MoveToNextWordEnd, cx: &mut ViewContext) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_cursors_with(|map, head, _| { + // (movement::next_word_end(map, head), SelectionGoal::None) + // }); + // }) + // } + + // pub fn move_to_next_subword_end( + // &mut self, + // _: &MoveToNextSubwordEnd, + // cx: &mut ViewContext, + // ) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_cursors_with(|map, head, _| { + // (movement::next_subword_end(map, head), SelectionGoal::None) + // }); + // }) + // } + + // pub fn select_to_next_word_end(&mut self, _: &SelectToNextWordEnd, cx: &mut ViewContext) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_heads_with(|map, head, _| { + // (movement::next_word_end(map, head), SelectionGoal::None) + // }); + // }) + // } + + // pub fn select_to_next_subword_end( + // &mut self, + // _: &SelectToNextSubwordEnd, + // cx: &mut ViewContext, + // ) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_heads_with(|map, head, _| { + // (movement::next_subword_end(map, head), SelectionGoal::None) + // }); + // }) + // } + + // pub fn delete_to_next_word_end(&mut self, _: &DeleteToNextWordEnd, cx: &mut ViewContext) { + // self.transact(cx, |this, cx| { + // this.change_selections(Some(Autoscroll::fit()), cx, |s| { + // let line_mode = s.line_mode; + // s.move_with(|map, selection| { + // if selection.is_empty() && !line_mode { + // let cursor = movement::next_word_end(map, selection.head()); + // selection.set_head(cursor, SelectionGoal::None); + // } + // }); + // }); + // this.insert("", cx); + // }); + // } + + // pub fn delete_to_next_subword_end( + // &mut self, + // _: &DeleteToNextSubwordEnd, + // cx: &mut ViewContext, + // ) { + // self.transact(cx, |this, cx| { + // this.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_with(|map, selection| { + // if selection.is_empty() { + // let cursor = movement::next_subword_end(map, selection.head()); + // selection.set_head(cursor, SelectionGoal::None); + // } + // }); + // }); + // this.insert("", cx); + // }); + // } + + // pub fn move_to_beginning_of_line( + // &mut self, + // _: &MoveToBeginningOfLine, + // cx: &mut ViewContext, + // ) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_cursors_with(|map, head, _| { + // ( + // movement::indented_line_beginning(map, head, true), + // SelectionGoal::None, + // ) + // }); + // }) + // } + + // pub fn select_to_beginning_of_line( + // &mut self, + // action: &SelectToBeginningOfLine, + // cx: &mut ViewContext, + // ) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_heads_with(|map, head, _| { + // ( + // movement::indented_line_beginning(map, head, action.stop_at_soft_wraps), + // SelectionGoal::None, + // ) + // }); + // }); + // } + + // pub fn delete_to_beginning_of_line( + // &mut self, + // _: &DeleteToBeginningOfLine, + // cx: &mut ViewContext, + // ) { + // self.transact(cx, |this, cx| { + // this.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_with(|_, selection| { + // selection.reversed = true; + // }); + // }); + + // this.select_to_beginning_of_line( + // &SelectToBeginningOfLine { + // stop_at_soft_wraps: false, + // }, + // cx, + // ); + // this.backspace(&Backspace, cx); + // }); + // } + + // pub fn move_to_end_of_line(&mut self, _: &MoveToEndOfLine, cx: &mut ViewContext) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_cursors_with(|map, head, _| { + // (movement::line_end(map, head, true), SelectionGoal::None) + // }); + // }) + // } + + // pub fn select_to_end_of_line( + // &mut self, + // action: &SelectToEndOfLine, + // cx: &mut ViewContext, + // ) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_heads_with(|map, head, _| { + // ( + // movement::line_end(map, head, action.stop_at_soft_wraps), + // SelectionGoal::None, + // ) + // }); + // }) + // } + + // pub fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext) { + // self.transact(cx, |this, cx| { + // this.select_to_end_of_line( + // &SelectToEndOfLine { + // stop_at_soft_wraps: false, + // }, + // cx, + // ); + // this.delete(&Delete, cx); + // }); + // } + + // pub fn cut_to_end_of_line(&mut self, _: &CutToEndOfLine, cx: &mut ViewContext) { + // self.transact(cx, |this, cx| { + // this.select_to_end_of_line( + // &SelectToEndOfLine { + // stop_at_soft_wraps: false, + // }, + // cx, + // ); + // this.cut(&Cut, cx); + // }); + // } + + // pub fn move_to_start_of_paragraph( + // &mut self, + // _: &MoveToStartOfParagraph, + // cx: &mut ViewContext, + // ) { + // if matches!(self.mode, EditorMode::SingleLine) { + // cx.propagate_action(); + // return; + // } + + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_with(|map, selection| { + // selection.collapse_to( + // movement::start_of_paragraph(map, selection.head(), 1), + // SelectionGoal::None, + // ) + // }); + // }) + // } + + // pub fn move_to_end_of_paragraph( + // &mut self, + // _: &MoveToEndOfParagraph, + // cx: &mut ViewContext, + // ) { + // if matches!(self.mode, EditorMode::SingleLine) { + // cx.propagate_action(); + // return; + // } + + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_with(|map, selection| { + // selection.collapse_to( + // movement::end_of_paragraph(map, selection.head(), 1), + // SelectionGoal::None, + // ) + // }); + // }) + // } + + // pub fn select_to_start_of_paragraph( + // &mut self, + // _: &SelectToStartOfParagraph, + // cx: &mut ViewContext, + // ) { + // if matches!(self.mode, EditorMode::SingleLine) { + // cx.propagate_action(); + // return; + // } + + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_heads_with(|map, head, _| { + // ( + // movement::start_of_paragraph(map, head, 1), + // SelectionGoal::None, + // ) + // }); + // }) + // } + + // pub fn select_to_end_of_paragraph( + // &mut self, + // _: &SelectToEndOfParagraph, + // cx: &mut ViewContext, + // ) { + // if matches!(self.mode, EditorMode::SingleLine) { + // cx.propagate_action(); + // return; + // } + + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_heads_with(|map, head, _| { + // ( + // movement::end_of_paragraph(map, head, 1), + // SelectionGoal::None, + // ) + // }); + // }) + // } + + // pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext) { + // if matches!(self.mode, EditorMode::SingleLine) { + // cx.propagate_action(); + // return; + // } + + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select_ranges(vec![0..0]); + // }); + // } + + // pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext) { + // let mut selection = self.selections.last::(cx); + // selection.set_head(Point::zero(), SelectionGoal::None); + + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select(vec![selection]); + // }); + // } + + // pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext) { + // if matches!(self.mode, EditorMode::SingleLine) { + // cx.propagate_action(); + // return; + // } + + // let cursor = self.buffer.read(cx).read(cx).len(); + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select_ranges(vec![cursor..cursor]) + // }); + // } + + // pub fn set_nav_history(&mut self, nav_history: Option) { + // self.nav_history = nav_history; + // } + + // pub fn nav_history(&self) -> Option<&ItemNavHistory> { + // self.nav_history.as_ref() + // } + + fn push_to_nav_history( + &mut self, + cursor_anchor: Anchor, + new_position: Option, + cx: &mut ViewContext, + ) { + if let Some(nav_history) = self.nav_history.as_mut() { + let buffer = self.buffer.read(cx).read(cx); + let cursor_position = cursor_anchor.to_point(&buffer); + let scroll_state = self.scroll_manager.anchor(); + let scroll_top_row = scroll_state.top_row(&buffer); + drop(buffer); + + if let Some(new_position) = new_position { + let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs(); + if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA { + return; + } + } + + nav_history.push( + Some(NavigationData { + cursor_anchor, + cursor_position, + scroll_anchor: scroll_state, + scroll_top_row, + }), + cx, + ); + } + } + + // pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext) { + // let buffer = self.buffer.read(cx).snapshot(cx); + // let mut selection = self.selections.first::(cx); + // selection.set_head(buffer.len(), SelectionGoal::None); + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select(vec![selection]); + // }); + // } + + // pub fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext) { + // let end = self.buffer.read(cx).read(cx).len(); + // self.change_selections(None, cx, |s| { + // s.select_ranges(vec![0..end]); + // }); + // } + + // pub fn select_line(&mut self, _: &SelectLine, cx: &mut ViewContext) { + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + // let mut selections = self.selections.all::(cx); + // let max_point = display_map.buffer_snapshot.max_point(); + // for selection in &mut selections { + // let rows = selection.spanned_rows(true, &display_map); + // selection.start = Point::new(rows.start, 0); + // selection.end = cmp::min(max_point, Point::new(rows.end, 0)); + // selection.reversed = false; + // } + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select(selections); + // }); + // } + + // pub fn split_selection_into_lines( + // &mut self, + // _: &SplitSelectionIntoLines, + // cx: &mut ViewContext, + // ) { + // let mut to_unfold = Vec::new(); + // let mut new_selection_ranges = Vec::new(); + // { + // let selections = self.selections.all::(cx); + // let buffer = self.buffer.read(cx).read(cx); + // for selection in selections { + // for row in selection.start.row..selection.end.row { + // let cursor = Point::new(row, buffer.line_len(row)); + // new_selection_ranges.push(cursor..cursor); + // } + // new_selection_ranges.push(selection.end..selection.end); + // to_unfold.push(selection.start..selection.end); + // } + // } + // self.unfold_ranges(to_unfold, true, true, cx); + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select_ranges(new_selection_ranges); + // }); + // } + + // pub fn add_selection_above(&mut self, _: &AddSelectionAbove, cx: &mut ViewContext) { + // self.add_selection(true, cx); + // } + + // pub fn add_selection_below(&mut self, _: &AddSelectionBelow, cx: &mut ViewContext) { + // self.add_selection(false, cx); + // } + + // fn add_selection(&mut self, above: bool, cx: &mut ViewContext) { + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + // let mut selections = self.selections.all::(cx); + // let text_layout_details = self.text_layout_details(cx); + // let mut state = self.add_selections_state.take().unwrap_or_else(|| { + // let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone(); + // let range = oldest_selection.display_range(&display_map).sorted(); + + // let start_x = display_map.x_for_point(range.start, &text_layout_details); + // let end_x = display_map.x_for_point(range.end, &text_layout_details); + // let positions = start_x.min(end_x)..start_x.max(end_x); + + // selections.clear(); + // let mut stack = Vec::new(); + // for row in range.start.row()..=range.end.row() { + // if let Some(selection) = self.selections.build_columnar_selection( + // &display_map, + // row, + // &positions, + // oldest_selection.reversed, + // &text_layout_details, + // ) { + // stack.push(selection.id); + // selections.push(selection); + // } + // } + + // if above { + // stack.reverse(); + // } + + // AddSelectionsState { above, stack } + // }); + + // let last_added_selection = *state.stack.last().unwrap(); + // let mut new_selections = Vec::new(); + // if above == state.above { + // let end_row = if above { + // 0 + // } else { + // display_map.max_point().row() + // }; + + // 'outer: for selection in selections { + // if selection.id == last_added_selection { + // let range = selection.display_range(&display_map).sorted(); + // debug_assert_eq!(range.start.row(), range.end.row()); + // let mut row = range.start.row(); + // let positions = if let SelectionGoal::HorizontalRange { start, end } = + // selection.goal + // { + // start..end + // } else { + // let start_x = display_map.x_for_point(range.start, &text_layout_details); + // let end_x = display_map.x_for_point(range.end, &text_layout_details); + + // start_x.min(end_x)..start_x.max(end_x) + // }; + + // while row != end_row { + // if above { + // row -= 1; + // } else { + // row += 1; + // } + + // if let Some(new_selection) = self.selections.build_columnar_selection( + // &display_map, + // row, + // &positions, + // selection.reversed, + // &text_layout_details, + // ) { + // state.stack.push(new_selection.id); + // if above { + // new_selections.push(new_selection); + // new_selections.push(selection); + // } else { + // new_selections.push(selection); + // new_selections.push(new_selection); + // } + + // continue 'outer; + // } + // } + // } + + // new_selections.push(selection); + // } + // } else { + // new_selections = selections; + // new_selections.retain(|s| s.id != last_added_selection); + // state.stack.pop(); + // } + + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select(new_selections); + // }); + // if state.stack.len() > 1 { + // self.add_selections_state = Some(state); + // } + // } + + // pub fn select_next_match_internal( + // &mut self, + // display_map: &DisplaySnapshot, + // replace_newest: bool, + // autoscroll: Option, + // cx: &mut ViewContext, + // ) -> Result<()> { + // fn select_next_match_ranges( + // this: &mut Editor, + // range: Range, + // replace_newest: bool, + // auto_scroll: Option, + // cx: &mut ViewContext, + // ) { + // this.unfold_ranges([range.clone()], false, true, cx); + // this.change_selections(auto_scroll, cx, |s| { + // if replace_newest { + // s.delete(s.newest_anchor().id); + // } + // s.insert_range(range.clone()); + // }); + // } + + // let buffer = &display_map.buffer_snapshot; + // let mut selections = self.selections.all::(cx); + // 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)) + // { + // if selections + // .iter() + // .find(|selection| selection.range().overlaps(&offset_range)) + // .is_none() + // { + // next_selected_range = Some(offset_range); + // break; + // } + // } + // } + + // if let Some(next_selected_range) = next_selected_range { + // select_next_match_ranges( + // self, + // next_selected_range, + // replace_newest, + // autoscroll, + // 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::(); + + // let is_empty = query.is_empty(); + // let select_state = SelectNextState { + // query: AhoCorasick::new(&[query])?, + // wordwise: true, + // done: is_empty, + // }; + // select_next_match_ranges( + // self, + // selection.start..selection.end, + // replace_newest, + // autoscroll, + // cx, + // ); + // self.select_next_state = Some(select_state); + // } else { + // let query = buffer + // .text_for_range(selection.start..selection.end) + // .collect::(); + // self.select_next_state = Some(SelectNextState { + // query: AhoCorasick::new(&[query])?, + // wordwise: false, + // done: false, + // }); + // self.select_next_match_internal(display_map, replace_newest, autoscroll, cx)?; + // } + // } + // Ok(()) + // } + + // pub fn select_all_matches( + // &mut self, + // action: &SelectAllMatches, + // cx: &mut ViewContext, + // ) -> Result<()> { + // self.push_to_selection_history(); + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + + // loop { + // self.select_next_match_internal(&display_map, action.replace_newest, None, cx)?; + + // if self + // .select_next_state + // .as_ref() + // .map(|selection_state| selection_state.done) + // .unwrap_or(true) + // { + // break; + // } + // } + + // Ok(()) + // } + + // pub fn select_next(&mut self, action: &SelectNext, cx: &mut ViewContext) -> Result<()> { + // self.push_to_selection_history(); + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + // self.select_next_match_internal( + // &display_map, + // action.replace_newest, + // Some(Autoscroll::newest()), + // cx, + // )?; + // Ok(()) + // } + + // pub fn select_previous( + // &mut self, + // action: &SelectPrevious, + // cx: &mut ViewContext, + // ) -> Result<()> { + // self.push_to_selection_history(); + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + // let buffer = &display_map.buffer_snapshot; + // let mut selections = self.selections.all::(cx); + // if let Some(mut select_prev_state) = self.select_prev_state.take() { + // let query = &select_prev_state.query; + // if !select_prev_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; + // // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer. + // let bytes_before_last_selection = + // buffer.reversed_bytes_in_range(0..last_selection.start); + // let bytes_after_first_selection = + // buffer.reversed_bytes_in_range(first_selection.end..buffer.len()); + // let query_matches = query + // .stream_find_iter(bytes_before_last_selection) + // .map(|result| (last_selection.start, result)) + // .chain( + // query + // .stream_find_iter(bytes_after_first_selection) + // .map(|result| (buffer.len(), result)), + // ); + // for (end_offset, query_match) in query_matches { + // let query_match = query_match.unwrap(); // can only fail due to I/O + // let offset_range = + // end_offset - query_match.end()..end_offset - query_match.start(); + // let display_range = offset_range.start.to_display_point(&display_map) + // ..offset_range.end.to_display_point(&display_map); + + // if !select_prev_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 { + // self.unfold_ranges([next_selected_range.clone()], false, true, cx); + // self.change_selections(Some(Autoscroll::newest()), cx, |s| { + // if action.replace_newest { + // s.delete(s.newest_anchor().id); + // } + // s.insert_range(next_selected_range); + // }); + // } else { + // select_prev_state.done = true; + // } + // } + + // self.select_prev_state = Some(select_prev_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::(); + // let query = query.chars().rev().collect::(); + // let select_state = SelectNextState { + // query: AhoCorasick::new(&[query])?, + // wordwise: true, + // done: false, + // }; + // self.unfold_ranges([selection.start..selection.end], false, true, cx); + // self.change_selections(Some(Autoscroll::newest()), cx, |s| { + // s.select(selections); + // }); + // self.select_prev_state = Some(select_state); + // } else { + // let query = buffer + // .text_for_range(selection.start..selection.end) + // .collect::(); + // let query = query.chars().rev().collect::(); + // self.select_prev_state = Some(SelectNextState { + // query: AhoCorasick::new(&[query])?, + // wordwise: false, + // done: false, + // }); + // self.select_previous(action, cx)?; + // } + // } + // Ok(()) + // } + + // pub fn toggle_comments(&mut self, action: &ToggleComments, cx: &mut ViewContext) { + // let text_layout_details = &self.text_layout_details(cx); + // self.transact(cx, |this, cx| { + // let mut selections = this.selections.all::(cx); + // let mut edits = Vec::new(); + // let mut selection_edit_ranges = Vec::new(); + // let mut last_toggled_row = None; + // let snapshot = this.buffer.read(cx).read(cx); + // let empty_str: Arc = "".into(); + // let mut suffixes_inserted = Vec::new(); + + // fn comment_prefix_range( + // snapshot: &MultiBufferSnapshot, + // row: u32, + // comment_prefix: &str, + // comment_prefix_whitespace: &str, + // ) -> Range { + // let start = Point::new(row, snapshot.indent_size_for_line(row).len); + + // let mut line_bytes = snapshot + // .bytes_in_range(start..snapshot.max_point()) + // .flatten() + // .copied(); + + // // If this line currently begins with the line comment prefix, then record + // // the range containing the prefix. + // if line_bytes + // .by_ref() + // .take(comment_prefix.len()) + // .eq(comment_prefix.bytes()) + // { + // // Include any whitespace that matches the comment prefix. + // let matching_whitespace_len = line_bytes + // .zip(comment_prefix_whitespace.bytes()) + // .take_while(|(a, b)| a == b) + // .count() as u32; + // let end = Point::new( + // start.row, + // start.column + comment_prefix.len() as u32 + matching_whitespace_len, + // ); + // start..end + // } else { + // start..start + // } + // } + + // fn comment_suffix_range( + // snapshot: &MultiBufferSnapshot, + // row: u32, + // comment_suffix: &str, + // comment_suffix_has_leading_space: bool, + // ) -> Range { + // let end = Point::new(row, snapshot.line_len(row)); + // let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32); + + // let mut line_end_bytes = snapshot + // .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end) + // .flatten() + // .copied(); + + // let leading_space_len = if suffix_start_column > 0 + // && line_end_bytes.next() == Some(b' ') + // && comment_suffix_has_leading_space + // { + // 1 + // } else { + // 0 + // }; + + // // If this line currently begins with the line comment prefix, then record + // // the range containing the prefix. + // if line_end_bytes.by_ref().eq(comment_suffix.bytes()) { + // let start = Point::new(end.row, suffix_start_column - leading_space_len); + // start..end + // } else { + // end..end + // } + // } + + // // TODO: Handle selections that cross excerpts + // for selection in &mut selections { + // let start_column = snapshot.indent_size_for_line(selection.start.row).len; + // let language = if let Some(language) = + // snapshot.language_scope_at(Point::new(selection.start.row, start_column)) + // { + // language + // } else { + // continue; + // }; + + // selection_edit_ranges.clear(); + + // // If multiple selections contain a given row, avoid processing that + // // row more than once. + // let mut start_row = selection.start.row; + // if last_toggled_row == Some(start_row) { + // start_row += 1; + // } + // let end_row = + // if selection.end.row > selection.start.row && selection.end.column == 0 { + // selection.end.row - 1 + // } else { + // selection.end.row + // }; + // last_toggled_row = Some(end_row); + + // if start_row > end_row { + // continue; + // } + + // // If the language has line comments, toggle those. + // if let Some(full_comment_prefix) = language.line_comment_prefix() { + // // Split the comment prefix's trailing whitespace into a separate string, + // // as that portion won't be used for detecting if a line is a comment. + // let comment_prefix = full_comment_prefix.trim_end_matches(' '); + // let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..]; + // let mut all_selection_lines_are_comments = true; + + // for row in start_row..=end_row { + // if snapshot.is_line_blank(row) && start_row < end_row { + // continue; + // } + + // let prefix_range = comment_prefix_range( + // snapshot.deref(), + // row, + // comment_prefix, + // comment_prefix_whitespace, + // ); + // if prefix_range.is_empty() { + // all_selection_lines_are_comments = false; + // } + // selection_edit_ranges.push(prefix_range); + // } + + // if all_selection_lines_are_comments { + // edits.extend( + // selection_edit_ranges + // .iter() + // .cloned() + // .map(|range| (range, empty_str.clone())), + // ); + // } else { + // let min_column = selection_edit_ranges + // .iter() + // .map(|r| r.start.column) + // .min() + // .unwrap_or(0); + // edits.extend(selection_edit_ranges.iter().map(|range| { + // let position = Point::new(range.start.row, min_column); + // (position..position, full_comment_prefix.clone()) + // })); + // } + // } else if let Some((full_comment_prefix, comment_suffix)) = + // language.block_comment_delimiters() + // { + // let comment_prefix = full_comment_prefix.trim_end_matches(' '); + // let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..]; + // let prefix_range = comment_prefix_range( + // snapshot.deref(), + // start_row, + // comment_prefix, + // comment_prefix_whitespace, + // ); + // let suffix_range = comment_suffix_range( + // snapshot.deref(), + // end_row, + // comment_suffix.trim_start_matches(' '), + // comment_suffix.starts_with(' '), + // ); + + // if prefix_range.is_empty() || suffix_range.is_empty() { + // edits.push(( + // prefix_range.start..prefix_range.start, + // full_comment_prefix.clone(), + // )); + // edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone())); + // suffixes_inserted.push((end_row, comment_suffix.len())); + // } else { + // edits.push((prefix_range, empty_str.clone())); + // edits.push((suffix_range, empty_str.clone())); + // } + // } else { + // continue; + // } + // } + + // drop(snapshot); + // this.buffer.update(cx, |buffer, cx| { + // buffer.edit(edits, None, cx); + // }); + + // // Adjust selections so that they end before any comment suffixes that + // // were inserted. + // let mut suffixes_inserted = suffixes_inserted.into_iter().peekable(); + // let mut selections = this.selections.all::(cx); + // let snapshot = this.buffer.read(cx).read(cx); + // for selection in &mut selections { + // while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() { + // match row.cmp(&selection.end.row) { + // Ordering::Less => { + // suffixes_inserted.next(); + // continue; + // } + // Ordering::Greater => break, + // Ordering::Equal => { + // if selection.end.column == snapshot.line_len(row) { + // if selection.is_empty() { + // selection.start.column -= suffix_len as u32; + // } + // selection.end.column -= suffix_len as u32; + // } + // break; + // } + // } + // } + // } + + // drop(snapshot); + // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); + + // let selections = this.selections.all::(cx); + // let selections_on_single_row = selections.windows(2).all(|selections| { + // selections[0].start.row == selections[1].start.row + // && selections[0].end.row == selections[1].end.row + // && selections[0].start.row == selections[0].end.row + // }); + // let selections_selecting = selections + // .iter() + // .any(|selection| selection.start != selection.end); + // let advance_downwards = action.advance_downwards + // && selections_on_single_row + // && !selections_selecting + // && this.mode != EditorMode::SingleLine; + + // if advance_downwards { + // let snapshot = this.buffer.read(cx).snapshot(cx); + + // this.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_cursors_with(|display_snapshot, display_point, _| { + // let mut point = display_point.to_point(display_snapshot); + // point.row += 1; + // point = snapshot.clip_point(point, Bias::Left); + // let display_point = point.to_display_point(display_snapshot); + // let goal = SelectionGoal::HorizontalPosition( + // display_snapshot.x_for_point(display_point, &text_layout_details), + // ); + // (display_point, goal) + // }) + // }); + // } + // }); + // } + + // pub fn select_larger_syntax_node( + // &mut self, + // _: &SelectLargerSyntaxNode, + // cx: &mut ViewContext, + // ) { + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + // let buffer = self.buffer.read(cx).snapshot(cx); + // let old_selections = self.selections.all::(cx).into_boxed_slice(); + + // let mut stack = mem::take(&mut self.select_larger_syntax_node_stack); + // let mut selected_larger_node = false; + // let new_selections = old_selections + // .iter() + // .map(|selection| { + // let old_range = selection.start..selection.end; + // let mut new_range = old_range.clone(); + // while let Some(containing_range) = + // buffer.range_for_syntax_ancestor(new_range.clone()) + // { + // new_range = containing_range; + // if !display_map.intersects_fold(new_range.start) + // && !display_map.intersects_fold(new_range.end) + // { + // break; + // } + // } + + // selected_larger_node |= new_range != old_range; + // Selection { + // id: selection.id, + // start: new_range.start, + // end: new_range.end, + // goal: SelectionGoal::None, + // reversed: selection.reversed, + // } + // }) + // .collect::>(); + + // if selected_larger_node { + // stack.push(old_selections); + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select(new_selections); + // }); + // } + // self.select_larger_syntax_node_stack = stack; + // } + + // pub fn select_smaller_syntax_node( + // &mut self, + // _: &SelectSmallerSyntaxNode, + // cx: &mut ViewContext, + // ) { + // let mut stack = mem::take(&mut self.select_larger_syntax_node_stack); + // if let Some(selections) = stack.pop() { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select(selections.to_vec()); + // }); + // } + // self.select_larger_syntax_node_stack = stack; + // } + + // pub fn move_to_enclosing_bracket( + // &mut self, + // _: &MoveToEnclosingBracket, + // cx: &mut ViewContext, + // ) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.move_offsets_with(|snapshot, selection| { + // let Some(enclosing_bracket_ranges) = + // snapshot.enclosing_bracket_ranges(selection.start..selection.end) + // else { + // return; + // }; + + // let mut best_length = usize::MAX; + // let mut best_inside = false; + // let mut best_in_bracket_range = false; + // let mut best_destination = None; + // for (open, close) in enclosing_bracket_ranges { + // let close = close.to_inclusive(); + // let length = close.end() - open.start; + // let inside = selection.start >= open.end && selection.end <= *close.start(); + // let in_bracket_range = open.to_inclusive().contains(&selection.head()) + // || close.contains(&selection.head()); + + // // If best is next to a bracket and current isn't, skip + // if !in_bracket_range && best_in_bracket_range { + // continue; + // } + + // // Prefer smaller lengths unless best is inside and current isn't + // if length > best_length && (best_inside || !inside) { + // continue; + // } + + // best_length = length; + // best_inside = inside; + // best_in_bracket_range = in_bracket_range; + // best_destination = Some( + // if close.contains(&selection.start) && close.contains(&selection.end) { + // if inside { + // open.end + // } else { + // open.start + // } + // } else { + // if inside { + // *close.start() + // } else { + // *close.end() + // } + // }, + // ); + // } + + // if let Some(destination) = best_destination { + // selection.collapse_to(destination, SelectionGoal::None); + // } + // }) + // }); + // } + + // pub fn undo_selection(&mut self, _: &UndoSelection, cx: &mut ViewContext) { + // self.end_selection(cx); + // self.selection_history.mode = SelectionHistoryMode::Undoing; + // if let Some(entry) = self.selection_history.undo_stack.pop_back() { + // self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec())); + // self.select_next_state = entry.select_next_state; + // self.select_prev_state = entry.select_prev_state; + // self.add_selections_state = entry.add_selections_state; + // self.request_autoscroll(Autoscroll::newest(), cx); + // } + // self.selection_history.mode = SelectionHistoryMode::Normal; + // } + + // pub fn redo_selection(&mut self, _: &RedoSelection, cx: &mut ViewContext) { + // self.end_selection(cx); + // self.selection_history.mode = SelectionHistoryMode::Redoing; + // if let Some(entry) = self.selection_history.redo_stack.pop_back() { + // self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec())); + // self.select_next_state = entry.select_next_state; + // self.select_prev_state = entry.select_prev_state; + // self.add_selections_state = entry.add_selections_state; + // self.request_autoscroll(Autoscroll::newest(), cx); + // } + // self.selection_history.mode = SelectionHistoryMode::Normal; + // } + + // fn go_to_diagnostic(&mut self, _: &GoToDiagnostic, cx: &mut ViewContext) { + // self.go_to_diagnostic_impl(Direction::Next, cx) + // } + + // fn go_to_prev_diagnostic(&mut self, _: &GoToPrevDiagnostic, cx: &mut ViewContext) { + // self.go_to_diagnostic_impl(Direction::Prev, cx) + // } + + // pub fn go_to_diagnostic_impl(&mut self, direction: Direction, cx: &mut ViewContext) { + // let buffer = self.buffer.read(cx).snapshot(cx); + // let selection = self.selections.newest::(cx); + + // // If there is an active Diagnostic Popover. Jump to it's diagnostic instead. + // if direction == Direction::Next { + // if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() { + // let (group_id, jump_to) = popover.activation_info(); + // if self.activate_diagnostics(group_id, cx) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // let mut new_selection = s.newest_anchor().clone(); + // new_selection.collapse_to(jump_to, SelectionGoal::None); + // s.select_anchors(vec![new_selection.clone()]); + // }); + // } + // return; + // } + // } + + // let mut active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| { + // active_diagnostics + // .primary_range + // .to_offset(&buffer) + // .to_inclusive() + // }); + // let mut search_start = if let Some(active_primary_range) = active_primary_range.as_ref() { + // if active_primary_range.contains(&selection.head()) { + // *active_primary_range.end() + // } else { + // selection.head() + // } + // } else { + // selection.head() + // }; + + // loop { + // let mut diagnostics = if direction == Direction::Prev { + // buffer.diagnostics_in_range::<_, usize>(0..search_start, true) + // } else { + // buffer.diagnostics_in_range::<_, usize>(search_start..buffer.len(), false) + // }; + // let group = diagnostics.find_map(|entry| { + // if entry.diagnostic.is_primary + // && entry.diagnostic.severity <= DiagnosticSeverity::WARNING + // && !entry.range.is_empty() + // && Some(entry.range.end) != active_primary_range.as_ref().map(|r| *r.end()) + // && !entry.range.contains(&search_start) + // { + // Some((entry.range, entry.diagnostic.group_id)) + // } else { + // None + // } + // }); + + // if let Some((primary_range, group_id)) = group { + // if self.activate_diagnostics(group_id, cx) { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select(vec![Selection { + // id: selection.id, + // start: primary_range.start, + // end: primary_range.start, + // reversed: false, + // goal: SelectionGoal::None, + // }]); + // }); + // } + // break; + // } else { + // // Cycle around to the start of the buffer, potentially moving back to the start of + // // the currently active diagnostic. + // active_primary_range.take(); + // if direction == Direction::Prev { + // if search_start == buffer.len() { + // break; + // } else { + // search_start = buffer.len(); + // } + // } else if search_start == 0 { + // break; + // } else { + // search_start = 0; + // } + // } + // } + // } + + // fn go_to_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext) { + // let snapshot = self + // .display_map + // .update(cx, |display_map, cx| display_map.snapshot(cx)); + // let selection = self.selections.newest::(cx); + + // if !self.seek_in_direction( + // &snapshot, + // selection.head(), + // false, + // snapshot + // .buffer_snapshot + // .git_diff_hunks_in_range((selection.head().row + 1)..u32::MAX), + // cx, + // ) { + // let wrapped_point = Point::zero(); + // self.seek_in_direction( + // &snapshot, + // wrapped_point, + // true, + // snapshot + // .buffer_snapshot + // .git_diff_hunks_in_range((wrapped_point.row + 1)..u32::MAX), + // cx, + // ); + // } + // } + + // fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext) { + // let snapshot = self + // .display_map + // .update(cx, |display_map, cx| display_map.snapshot(cx)); + // let selection = self.selections.newest::(cx); + + // if !self.seek_in_direction( + // &snapshot, + // selection.head(), + // false, + // snapshot + // .buffer_snapshot + // .git_diff_hunks_in_range_rev(0..selection.head().row), + // cx, + // ) { + // let wrapped_point = snapshot.buffer_snapshot.max_point(); + // self.seek_in_direction( + // &snapshot, + // wrapped_point, + // true, + // snapshot + // .buffer_snapshot + // .git_diff_hunks_in_range_rev(0..wrapped_point.row), + // cx, + // ); + // } + // } + + // fn seek_in_direction( + // &mut self, + // snapshot: &DisplaySnapshot, + // initial_point: Point, + // is_wrapped: bool, + // hunks: impl Iterator>, + // cx: &mut ViewContext, + // ) -> bool { + // let display_point = initial_point.to_display_point(snapshot); + // let mut hunks = hunks + // .map(|hunk| diff_hunk_to_display(hunk, &snapshot)) + // .filter(|hunk| { + // if is_wrapped { + // true + // } else { + // !hunk.contains_display_row(display_point.row()) + // } + // }) + // .dedup(); + + // if let Some(hunk) = hunks.next() { + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // let row = hunk.start_display_row(); + // let point = DisplayPoint::new(row, 0); + // s.select_display_ranges([point..point]); + // }); + + // true + // } else { + // false + // } + // } + + pub fn go_to_definition(&mut self, _: &GoToDefinition, cx: &mut ViewContext) { + self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx); + } + + pub fn go_to_type_definition(&mut self, _: &GoToTypeDefinition, cx: &mut ViewContext) { + self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx); + } + + pub fn go_to_definition_split(&mut self, _: &GoToDefinitionSplit, cx: &mut ViewContext) { + self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, cx); + } + + pub fn go_to_type_definition_split( + &mut self, + _: &GoToTypeDefinitionSplit, + cx: &mut ViewContext, + ) { + self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, cx); + } + + fn go_to_definition_of_kind( + &mut self, + kind: GotoDefinitionKind, + split: bool, + cx: &mut ViewContext, + ) { + let Some(workspace) = self.workspace() else { + return; + }; + let buffer = self.buffer.read(cx); + let head = self.selections.newest::(cx).head(); + let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) { + text_anchor + } else { + return; + }; + + let project = workspace.read(cx).project().clone(); + let definitions = project.update(cx, |project, cx| match kind { + GotoDefinitionKind::Symbol => project.definition(&buffer, head, cx), + GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx), + }); + + cx.spawn(|editor, mut cx| async move { + let definitions = definitions.await?; + editor.update(&mut cx, |editor, cx| { + editor.navigate_to_definitions( + definitions + .into_iter() + .map(GoToDefinitionLink::Text) + .collect(), + split, + cx, + ); + })?; + Ok::<(), anyhow::Error>(()) + }) + .detach_and_log_err(cx); + } + + pub fn navigate_to_definitions( + &mut self, + mut definitions: Vec, + split: bool, + cx: &mut ViewContext, + ) { + let Some(workspace) = self.workspace() else { + return; + }; + let pane = workspace.read(cx).active_pane().clone(); + // If there is one definition, just open it directly + if definitions.len() == 1 { + let definition = definitions.pop().unwrap(); + let target_task = match definition { + GoToDefinitionLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))), + GoToDefinitionLink::InlayHint(lsp_location, server_id) => { + self.compute_target_location(lsp_location, server_id, cx) + } + }; + cx.spawn(|editor, mut cx| async move { + let target = target_task.await.context("target resolution task")?; + if let Some(target) = target { + editor.update(&mut cx, |editor, cx| { + let range = target.range.to_offset(target.buffer.read(cx)); + let range = editor.range_for_match(&range); + if Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref() { + editor.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_ranges([range]); + }); + } else { + cx.window_context().defer(move |cx| { + let target_editor: View = + workspace.update(cx, |workspace, cx| { + if split { + workspace.split_project_item(target.buffer.clone(), cx) + } else { + workspace.open_project_item(target.buffer.clone(), cx) + } + }); + target_editor.update(cx, |target_editor, cx| { + // When selecting a definition in a different buffer, disable the nav history + // to avoid creating a history entry at the previous cursor location. + pane.update(cx, |pane, _| pane.disable_history()); + target_editor.change_selections( + Some(Autoscroll::fit()), + cx, + |s| { + s.select_ranges([range]); + }, + ); + pane.update(cx, |pane, _| pane.enable_history()); + }); + }); + } + }) + } else { + Ok(()) + } + }) + .detach_and_log_err(cx); + } else if !definitions.is_empty() { + let replica_id = self.replica_id(cx); + cx.spawn(|editor, mut cx| async move { + let (title, location_tasks) = editor + .update(&mut cx, |editor, cx| { + let title = definitions + .iter() + .find_map(|definition| match definition { + GoToDefinitionLink::Text(link) => { + link.origin.as_ref().map(|origin| { + let buffer = origin.buffer.read(cx); + format!( + "Definitions for {}", + buffer + .text_for_range(origin.range.clone()) + .collect::() + ) + }) + } + GoToDefinitionLink::InlayHint(_, _) => None, + }) + .unwrap_or("Definitions".to_string()); + let location_tasks = definitions + .into_iter() + .map(|definition| match definition { + GoToDefinitionLink::Text(link) => { + Task::Ready(Some(Ok(Some(link.target)))) + } + GoToDefinitionLink::InlayHint(lsp_location, server_id) => { + editor.compute_target_location(lsp_location, server_id, cx) + } + }) + .collect::>(); + (title, location_tasks) + }) + .context("location tasks preparation")?; + + let locations = futures::future::join_all(location_tasks) + .await + .into_iter() + .filter_map(|location| location.transpose()) + .collect::>() + .context("location tasks")?; + workspace.update(&mut cx, |workspace, cx| { + Self::open_locations_in_multibuffer( + workspace, locations, replica_id, title, split, cx, + ) + }); + + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + } + } + + fn compute_target_location( + &self, + lsp_location: lsp::Location, + server_id: LanguageServerId, + cx: &mut ViewContext, + ) -> Task>> { + let Some(project) = self.project.clone() else { + return Task::Ready(Some(Ok(None))); + }; + + cx.spawn(move |editor, mut cx| async move { + let location_task = editor.update(&mut cx, |editor, cx| { + project.update(cx, |project, cx| { + let language_server_name = + editor.buffer.read(cx).as_singleton().and_then(|buffer| { + project + .language_server_for_buffer(buffer.read(cx), server_id, cx) + .map(|(_, lsp_adapter)| { + LanguageServerName(Arc::from(lsp_adapter.name())) + }) + }); + language_server_name.map(|language_server_name| { + project.open_local_buffer_via_lsp( + lsp_location.uri.clone(), + server_id, + language_server_name, + cx, + ) + }) + }) + })?; + let location = match location_task { + Some(task) => Some({ + let target_buffer_handle = task.await.context("open local buffer")?; + let range = target_buffer_handle.update(&mut cx, |target_buffer, _| { + let target_start = target_buffer + .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left); + let target_end = target_buffer + .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left); + target_buffer.anchor_after(target_start) + ..target_buffer.anchor_before(target_end) + })?; + Location { + buffer: target_buffer_handle, + range, + } + }), + None => None, + }; + Ok(location) + }) + } + + // pub fn find_all_references( + // workspace: &mut Workspace, + // _: &FindAllReferences, + // cx: &mut ViewContext, + // ) -> Option>> { + // let active_item = workspace.active_item(cx)?; + // let editor_handle = active_item.act_as::(cx)?; + + // let editor = editor_handle.read(cx); + // let buffer = editor.buffer.read(cx); + // let head = editor.selections.newest::(cx).head(); + // let (buffer, head) = buffer.text_anchor_for_position(head, cx)?; + // let replica_id = editor.replica_id(cx); + + // let project = workspace.project().clone(); + // let references = project.update(cx, |project, cx| project.references(&buffer, head, cx)); + // Some(cx.spawn_labeled( + // "Finding All References...", + // |workspace, mut cx| async move { + // let locations = references.await?; + // if locations.is_empty() { + // return Ok(()); + // } + + // workspace.update(&mut cx, |workspace, cx| { + // let title = locations + // .first() + // .as_ref() + // .map(|location| { + // let buffer = location.buffer.read(cx); + // format!( + // "References to `{}`", + // buffer + // .text_for_range(location.range.clone()) + // .collect::() + // ) + // }) + // .unwrap(); + // Self::open_locations_in_multibuffer( + // workspace, locations, replica_id, title, false, cx, + // ); + // })?; + + // Ok(()) + // }, + // )) + // } + + /// Opens a multibuffer with the given project locations in it + pub fn open_locations_in_multibuffer( + workspace: &mut Workspace, + mut locations: Vec, + replica_id: ReplicaId, + title: String, + split: bool, + cx: &mut ViewContext, + ) { + // If there are multiple definitions, open them in a multibuffer + locations.sort_by_key(|location| location.buffer.read(cx).remote_id()); + let mut locations = locations.into_iter().peekable(); + let mut ranges_to_highlight = Vec::new(); + + let excerpt_buffer = cx.build_model(|cx| { + let mut multibuffer = MultiBuffer::new(replica_id); + while let Some(location) = locations.next() { + let buffer = location.buffer.read(cx); + let mut ranges_for_buffer = Vec::new(); + let range = location.range.to_offset(buffer); + ranges_for_buffer.push(range.clone()); + + while let Some(next_location) = locations.peek() { + if next_location.buffer == location.buffer { + ranges_for_buffer.push(next_location.range.to_offset(buffer)); + locations.next(); + } else { + break; + } + } + + ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end))); + ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines( + location.buffer.clone(), + ranges_for_buffer, + 1, + cx, + )) + } + + multibuffer.with_title(title) + }); + + let editor = cx.build_view(|cx| { + Editor::for_multibuffer(excerpt_buffer, Some(workspace.project().clone()), cx) + }); + editor.update(cx, |editor, cx| { + editor.highlight_background::( + ranges_to_highlight, + |theme| todo!("theme.editor.highlighted_line_background"), + cx, + ); + }); + if split { + workspace.split_item(SplitDirection::Right, Box::new(editor), cx); + } else { + workspace.add_item(Box::new(editor), cx); + } + } + + // pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext) -> Option>> { + // use language::ToOffset as _; + + // let project = self.project.clone()?; + // let selection = self.selections.newest_anchor().clone(); + // let (cursor_buffer, cursor_buffer_position) = self + // .buffer + // .read(cx) + // .text_anchor_for_position(selection.head(), cx)?; + // let (tail_buffer, _) = self + // .buffer + // .read(cx) + // .text_anchor_for_position(selection.tail(), cx)?; + // if tail_buffer != cursor_buffer { + // return None; + // } + + // let snapshot = cursor_buffer.read(cx).snapshot(); + // let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot); + // let prepare_rename = project.update(cx, |project, cx| { + // project.prepare_rename(cursor_buffer, cursor_buffer_offset, cx) + // }); + + // Some(cx.spawn(|this, mut cx| async move { + // let rename_range = if let Some(range) = prepare_rename.await? { + // Some(range) + // } else { + // this.update(&mut cx, |this, cx| { + // let buffer = this.buffer.read(cx).snapshot(cx); + // let mut buffer_highlights = this + // .document_highlights_for_position(selection.head(), &buffer) + // .filter(|highlight| { + // highlight.start.excerpt_id == selection.head().excerpt_id + // && highlight.end.excerpt_id == selection.head().excerpt_id + // }); + // buffer_highlights + // .next() + // .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor) + // })? + // }; + // if let Some(rename_range) = rename_range { + // let rename_buffer_range = rename_range.to_offset(&snapshot); + // let cursor_offset_in_rename_range = + // cursor_buffer_offset.saturating_sub(rename_buffer_range.start); + + // this.update(&mut cx, |this, cx| { + // this.take_rename(false, cx); + // let style = this.style(cx); + // let buffer = this.buffer.read(cx).read(cx); + // let cursor_offset = selection.head().to_offset(&buffer); + // let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range); + // let rename_end = rename_start + rename_buffer_range.len(); + // let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end); + // let mut old_highlight_id = None; + // let old_name: Arc = buffer + // .chunks(rename_start..rename_end, true) + // .map(|chunk| { + // if old_highlight_id.is_none() { + // old_highlight_id = chunk.syntax_highlight_id; + // } + // chunk.text + // }) + // .collect::() + // .into(); + + // drop(buffer); + + // // Position the selection in the rename editor so that it matches the current selection. + // this.show_local_selections = false; + // let rename_editor = cx.add_view(|cx| { + // let mut editor = Editor::single_line(None, cx); + // if let Some(old_highlight_id) = old_highlight_id { + // editor.override_text_style = + // Some(Box::new(move |style| old_highlight_id.style(&style.syntax))); + // } + // editor.buffer.update(cx, |buffer, cx| { + // buffer.edit([(0..0, old_name.clone())], None, cx) + // }); + // editor.select_all(&SelectAll, cx); + // editor + // }); + + // let ranges = this + // .clear_background_highlights::(cx) + // .into_iter() + // .flat_map(|(_, ranges)| ranges.into_iter()) + // .chain( + // this.clear_background_highlights::(cx) + // .into_iter() + // .flat_map(|(_, ranges)| ranges.into_iter()), + // ) + // .collect(); + + // this.highlight_text::( + // ranges, + // HighlightStyle { + // fade_out: Some(style.rename_fade), + // ..Default::default() + // }, + // cx, + // ); + // cx.focus(&rename_editor); + // let block_id = this.insert_blocks( + // [BlockProperties { + // style: BlockStyle::Flex, + // position: range.start.clone(), + // height: 1, + // render: Arc::new({ + // let editor = rename_editor.clone(); + // move |cx: &mut BlockContext| { + // ChildView::new(&editor, cx) + // .contained() + // .with_padding_left(cx.anchor_x) + // .into_any() + // } + // }), + // disposition: BlockDisposition::Below, + // }], + // Some(Autoscroll::fit()), + // cx, + // )[0]; + // this.pending_rename = Some(RenameState { + // range, + // old_name, + // editor: rename_editor, + // block_id, + // }); + // })?; + // } + + // Ok(()) + // })) + // } + + // pub fn confirm_rename( + // workspace: &mut Workspace, + // _: &ConfirmRename, + // cx: &mut ViewContext, + // ) -> Option>> { + // let editor = workspace.active_item(cx)?.act_as::(cx)?; + + // let (buffer, range, old_name, new_name) = editor.update(cx, |editor, cx| { + // let rename = editor.take_rename(false, cx)?; + // let buffer = editor.buffer.read(cx); + // let (start_buffer, start) = + // buffer.text_anchor_for_position(rename.range.start.clone(), cx)?; + // let (end_buffer, end) = + // buffer.text_anchor_for_position(rename.range.end.clone(), cx)?; + // if start_buffer == end_buffer { + // let new_name = rename.editor.read(cx).text(cx); + // Some((start_buffer, start..end, rename.old_name, new_name)) + // } else { + // None + // } + // })?; + + // let rename = workspace.project().clone().update(cx, |project, cx| { + // project.perform_rename(buffer.clone(), range.start, new_name.clone(), true, cx) + // }); + + // let editor = editor.downgrade(); + // Some(cx.spawn(|workspace, mut cx| async move { + // let project_transaction = rename.await?; + // Self::open_project_transaction( + // &editor, + // workspace, + // project_transaction, + // format!("Rename: {} → {}", old_name, new_name), + // cx.clone(), + // ) + // .await?; + + // editor.update(&mut cx, |editor, cx| { + // editor.refresh_document_highlights(cx); + // })?; + // Ok(()) + // })) + // } + + fn take_rename( + &mut self, + moving_cursor: bool, + cx: &mut ViewContext, + ) -> Option { + let rename = self.pending_rename.take()?; + self.remove_blocks( + [rename.block_id].into_iter().collect(), + Some(Autoscroll::fit()), + cx, + ); + self.clear_highlights::(cx); + self.show_local_selections = true; + + if moving_cursor { + let rename_editor = rename.editor.read(cx); + let cursor_in_rename_editor = rename_editor.selections.newest::(cx).head(); + + // Update the selection to match the position of the selection inside + // the rename editor. + let snapshot = self.buffer.read(cx).read(cx); + let rename_range = rename.range.to_offset(&snapshot); + let cursor_in_editor = snapshot + .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left) + .min(rename_range.end); + drop(snapshot); + + self.change_selections(None, cx, |s| { + s.select_ranges(vec![cursor_in_editor..cursor_in_editor]) + }); + } else { + self.refresh_document_highlights(cx); + } + + Some(rename) + } + + #[cfg(any(test, feature = "test-support"))] + pub fn pending_rename(&self) -> Option<&RenameState> { + self.pending_rename.as_ref() + } + + // fn format(&mut self, _: &Format, cx: &mut ViewContext) -> Option>> { + // let project = match &self.project { + // Some(project) => project.clone(), + // None => return None, + // }; + + // Some(self.perform_format(project, FormatTrigger::Manual, cx)) + // } + + fn perform_format( + &mut self, + project: Model, + trigger: FormatTrigger, + cx: &mut ViewContext, + ) -> Task> { + let buffer = self.buffer().clone(); + let buffers = buffer.read(cx).all_buffers(); + + let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse(); + let format = project.update(cx, |project, cx| project.format(buffers, true, trigger, cx)); + + cx.spawn(|_, mut cx| async move { + let transaction = futures::select_biased! { + _ = timeout => { + log::warn!("timed out waiting for formatting"); + None + } + transaction = format.log_err().fuse() => transaction, + }; + + buffer.update(&mut cx, |buffer, cx| { + if let Some(transaction) = transaction { + if !buffer.is_singleton() { + buffer.push_transaction(&transaction.0, cx); + } + } + + cx.notify(); + }); + + Ok(()) + }) + } + + // fn restart_language_server(&mut self, _: &RestartLanguageServer, cx: &mut ViewContext) { + // if let Some(project) = self.project.clone() { + // self.buffer.update(cx, |multi_buffer, cx| { + // project.update(cx, |project, cx| { + // project.restart_language_servers_for_buffers(multi_buffer.all_buffers(), cx); + // }); + // }) + // } + // } + + // fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext) { + // cx.show_character_palette(); + // } + + fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext) { + if let Some(active_diagnostics) = self.active_diagnostics.as_mut() { + let buffer = self.buffer.read(cx).snapshot(cx); + let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer); + let is_valid = buffer + .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone(), false) + .any(|entry| { + entry.diagnostic.is_primary + && !entry.range.is_empty() + && entry.range.start == primary_range_start + && entry.diagnostic.message == active_diagnostics.primary_message + }); + + if is_valid != active_diagnostics.is_valid { + active_diagnostics.is_valid = is_valid; + let mut new_styles = HashMap::default(); + for (block_id, diagnostic) in &active_diagnostics.blocks { + new_styles.insert( + *block_id, + diagnostic_block_renderer(diagnostic.clone(), is_valid), + ); + } + self.display_map + .update(cx, |display_map, _| display_map.replace_blocks(new_styles)); + } + } + } + + // fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext) -> bool { + // self.dismiss_diagnostics(cx); + // self.active_diagnostics = self.display_map.update(cx, |display_map, cx| { + // let buffer = self.buffer.read(cx).snapshot(cx); + + // let mut primary_range = None; + // let mut primary_message = None; + // let mut group_end = Point::zero(); + // let diagnostic_group = buffer + // .diagnostic_group::(group_id) + // .map(|entry| { + // if entry.range.end > group_end { + // group_end = entry.range.end; + // } + // if entry.diagnostic.is_primary { + // primary_range = Some(entry.range.clone()); + // primary_message = Some(entry.diagnostic.message.clone()); + // } + // entry + // }) + // .collect::>(); + // let primary_range = primary_range?; + // let primary_message = primary_message?; + // let primary_range = + // buffer.anchor_after(primary_range.start)..buffer.anchor_before(primary_range.end); + + // let blocks = display_map + // .insert_blocks( + // diagnostic_group.iter().map(|entry| { + // let diagnostic = entry.diagnostic.clone(); + // let message_height = diagnostic.message.lines().count() as u8; + // BlockProperties { + // style: BlockStyle::Fixed, + // position: buffer.anchor_after(entry.range.start), + // height: message_height, + // render: diagnostic_block_renderer(diagnostic, true), + // disposition: BlockDisposition::Below, + // } + // }), + // cx, + // ) + // .into_iter() + // .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic)) + // .collect(); + + // Some(ActiveDiagnosticGroup { + // primary_range, + // primary_message, + // blocks, + // is_valid: true, + // }) + // }); + // self.active_diagnostics.is_some() + // } + + // fn dismiss_diagnostics(&mut self, cx: &mut ViewContext) { + // if let Some(active_diagnostic_group) = self.active_diagnostics.take() { + // self.display_map.update(cx, |display_map, cx| { + // display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx); + // }); + // cx.notify(); + // } + // } + + // pub fn set_selections_from_remote( + // &mut self, + // selections: Vec>, + // pending_selection: Option>, + // cx: &mut ViewContext, + // ) { + // let old_cursor_position = self.selections.newest_anchor().head(); + // self.selections.change_with(cx, |s| { + // s.select_anchors(selections); + // if let Some(pending_selection) = pending_selection { + // s.set_pending(pending_selection, SelectMode::Character); + // } else { + // s.clear_pending(); + // } + // }); + // self.selections_did_change(false, &old_cursor_position, cx); + // } + + fn push_to_selection_history(&mut self) { + self.selection_history.push(SelectionHistoryEntry { + selections: self.selections.disjoint_anchors(), + select_next_state: self.select_next_state.clone(), + select_prev_state: self.select_prev_state.clone(), + add_selections_state: self.add_selections_state.clone(), + }); + } + + pub fn transact( + &mut self, + cx: &mut ViewContext, + update: impl FnOnce(&mut Self, &mut ViewContext), + ) -> Option { + self.start_transaction_at(Instant::now(), cx); + update(self, cx); + self.end_transaction_at(Instant::now(), cx) + } + + fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext) { + todo!() + // self.end_selection(cx); + // if let Some(tx_id) = self + // .buffer + // .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx)) + // { + // self.selection_history + // .insert_transaction(tx_id, self.selections.disjoint_anchors()); + // } + } + + fn end_transaction_at( + &mut self, + now: Instant, + cx: &mut ViewContext, + ) -> Option { + todo!() + // if let Some(tx_id) = self + // .buffer + // .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx)) + // { + // if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) { + // *end_selections = Some(self.selections.disjoint_anchors()); + // } else { + // error!("unexpectedly ended a transaction that wasn't started by this editor"); + // } + + // cx.emit(Event::Edited); + // Some(tx_id) + // } else { + // None + // } + } + + // pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext) { + // let mut fold_ranges = Vec::new(); + + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + + // let selections = self.selections.all_adjusted(cx); + // for selection in selections { + // let range = selection.range().sorted(); + // let buffer_start_row = range.start.row; + + // for row in (0..=range.end.row).rev() { + // let fold_range = display_map.foldable_range(row); + + // if let Some(fold_range) = fold_range { + // if fold_range.end.row >= buffer_start_row { + // fold_ranges.push(fold_range); + // if row <= range.start.row { + // break; + // } + // } + // } + // } + // } + + // self.fold_ranges(fold_ranges, true, cx); + // } + + // pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext) { + // let buffer_row = fold_at.buffer_row; + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + + // if let Some(fold_range) = display_map.foldable_range(buffer_row) { + // let autoscroll = self + // .selections + // .all::(cx) + // .iter() + // .any(|selection| fold_range.overlaps(&selection.range())); + + // self.fold_ranges(std::iter::once(fold_range), autoscroll, cx); + // } + // } + + // pub fn unfold_lines(&mut self, _: &UnfoldLines, cx: &mut ViewContext) { + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + // let buffer = &display_map.buffer_snapshot; + // let selections = self.selections.all::(cx); + // let ranges = selections + // .iter() + // .map(|s| { + // let range = s.display_range(&display_map).sorted(); + // let mut start = range.start.to_point(&display_map); + // let mut end = range.end.to_point(&display_map); + // start.column = 0; + // end.column = buffer.line_len(end.row); + // start..end + // }) + // .collect::>(); + + // self.unfold_ranges(ranges, true, true, cx); + // } + + // pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext) { + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + + // let intersection_range = Point::new(unfold_at.buffer_row, 0) + // ..Point::new( + // unfold_at.buffer_row, + // display_map.buffer_snapshot.line_len(unfold_at.buffer_row), + // ); + + // let autoscroll = self + // .selections + // .all::(cx) + // .iter() + // .any(|selection| selection.range().overlaps(&intersection_range)); + + // self.unfold_ranges(std::iter::once(intersection_range), true, autoscroll, cx) + // } + + // pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext) { + // let selections = self.selections.all::(cx); + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + // let line_mode = self.selections.line_mode; + // let ranges = selections.into_iter().map(|s| { + // if line_mode { + // let start = Point::new(s.start.row, 0); + // let end = Point::new(s.end.row, display_map.buffer_snapshot.line_len(s.end.row)); + // start..end + // } else { + // s.start..s.end + // } + // }); + // self.fold_ranges(ranges, true, cx); + // } + + pub fn fold_ranges( + &mut self, + ranges: impl IntoIterator>, + auto_scroll: bool, + cx: &mut ViewContext, + ) { + let mut ranges = ranges.into_iter().peekable(); + if ranges.peek().is_some() { + self.display_map.update(cx, |map, cx| map.fold(ranges, cx)); + + if auto_scroll { + self.request_autoscroll(Autoscroll::fit(), cx); + } + + cx.notify(); + } + } + + pub fn unfold_ranges( + &mut self, + ranges: impl IntoIterator>, + inclusive: bool, + auto_scroll: bool, + cx: &mut ViewContext, + ) { + let mut ranges = ranges.into_iter().peekable(); + if ranges.peek().is_some() { + self.display_map + .update(cx, |map, cx| map.unfold(ranges, inclusive, cx)); + if auto_scroll { + self.request_autoscroll(Autoscroll::fit(), cx); + } + + cx.notify(); + } + } + + // pub fn gutter_hover( + // &mut self, + // GutterHover { hovered }: &GutterHover, + // cx: &mut ViewContext, + // ) { + // self.gutter_hovered = *hovered; + // cx.notify(); + // } + + // pub fn insert_blocks( + // &mut self, + // blocks: impl IntoIterator>, + // autoscroll: Option, + // cx: &mut ViewContext, + // ) -> Vec { + // let blocks = self + // .display_map + // .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx)); + // if let Some(autoscroll) = autoscroll { + // self.request_autoscroll(autoscroll, cx); + // } + // blocks + // } + + // pub fn replace_blocks( + // &mut self, + // blocks: HashMap, + // autoscroll: Option, + // cx: &mut ViewContext, + // ) { + // self.display_map + // .update(cx, |display_map, _| display_map.replace_blocks(blocks)); + // if let Some(autoscroll) = autoscroll { + // self.request_autoscroll(autoscroll, cx); + // } + // } + + pub fn remove_blocks( + &mut self, + block_ids: HashSet, + autoscroll: Option, + cx: &mut ViewContext, + ) { + self.display_map.update(cx, |display_map, cx| { + display_map.remove_blocks(block_ids, cx) + }); + if let Some(autoscroll) = autoscroll { + self.request_autoscroll(autoscroll, cx); + } + } + + // pub fn longest_row(&self, cx: &mut AppContext) -> u32 { + // self.display_map + // .update(cx, |map, cx| map.snapshot(cx)) + // .longest_row() + // } + + // pub fn max_point(&self, cx: &mut AppContext) -> DisplayPoint { + // self.display_map + // .update(cx, |map, cx| map.snapshot(cx)) + // .max_point() + // } + + // pub fn text(&self, cx: &AppContext) -> String { + // self.buffer.read(cx).read(cx).text() + // } + + // pub fn set_text(&mut self, text: impl Into>, cx: &mut ViewContext) { + // self.transact(cx, |this, cx| { + // this.buffer + // .read(cx) + // .as_singleton() + // .expect("you can only call set_text on editors for singleton buffers") + // .update(cx, |buffer, cx| buffer.set_text(text, cx)); + // }); + // } + + // pub fn display_text(&self, cx: &mut AppContext) -> String { + // self.display_map + // .update(cx, |map, cx| map.snapshot(cx)) + // .text() + // } + + // pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> { + // let mut wrap_guides = smallvec::smallvec![]; + + // if self.show_wrap_guides == Some(false) { + // return wrap_guides; + // } + + // let settings = self.buffer.read(cx).settings_at(0, cx); + // if settings.show_wrap_guides { + // if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) { + // wrap_guides.push((soft_wrap as usize, true)); + // } + // wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false))) + // } + + // wrap_guides + // } + + // pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap { + // let settings = self.buffer.read(cx).settings_at(0, cx); + // let mode = self + // .soft_wrap_mode_override + // .unwrap_or_else(|| settings.soft_wrap); + // match mode { + // language_settings::SoftWrap::None => SoftWrap::None, + // language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth, + // language_settings::SoftWrap::PreferredLineLength => { + // SoftWrap::Column(settings.preferred_line_length) + // } + // } + // } + + // pub fn set_soft_wrap_mode( + // &mut self, + // mode: language_settings::SoftWrap, + // cx: &mut ViewContext, + // ) { + // self.soft_wrap_mode_override = Some(mode); + // cx.notify(); + // } + + // pub fn set_wrap_width(&self, width: Option, cx: &mut AppContext) -> bool { + // self.display_map + // .update(cx, |map, cx| map.set_wrap_width(width, cx)) + // } + + // pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, cx: &mut ViewContext) { + // if self.soft_wrap_mode_override.is_some() { + // self.soft_wrap_mode_override.take(); + // } else { + // let soft_wrap = match self.soft_wrap_mode(cx) { + // SoftWrap::None => language_settings::SoftWrap::EditorWidth, + // SoftWrap::EditorWidth | SoftWrap::Column(_) => language_settings::SoftWrap::None, + // }; + // self.soft_wrap_mode_override = Some(soft_wrap); + // } + // cx.notify(); + // } + + // pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut ViewContext) { + // self.show_gutter = show_gutter; + // cx.notify(); + // } + + // pub fn set_show_wrap_guides(&mut self, show_gutter: bool, cx: &mut ViewContext) { + // self.show_wrap_guides = Some(show_gutter); + // cx.notify(); + // } + + // pub fn reveal_in_finder(&mut self, _: &RevealInFinder, cx: &mut ViewContext) { + // if let Some(buffer) = self.buffer().read(cx).as_singleton() { + // if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) { + // cx.reveal_path(&file.abs_path(cx)); + // } + // } + // } + + // pub fn copy_path(&mut self, _: &CopyPath, cx: &mut ViewContext) { + // if let Some(buffer) = self.buffer().read(cx).as_singleton() { + // if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) { + // if let Some(path) = file.abs_path(cx).to_str() { + // cx.write_to_clipboard(ClipboardItem::new(path.to_string())); + // } + // } + // } + // } + + // pub fn copy_relative_path(&mut self, _: &CopyRelativePath, cx: &mut ViewContext) { + // if let Some(buffer) = self.buffer().read(cx).as_singleton() { + // if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) { + // if let Some(path) = file.path().to_str() { + // cx.write_to_clipboard(ClipboardItem::new(path.to_string())); + // } + // } + // } + // } + + // pub fn highlight_rows(&mut self, rows: Option>) { + // self.highlighted_rows = rows; + // } + + // pub fn highlighted_rows(&self) -> Option> { + // self.highlighted_rows.clone() + // } + + pub fn highlight_background( + &mut self, + ranges: Vec>, + color_fetcher: fn(&ThemeColors) -> Hsla, + cx: &mut ViewContext, + ) { + self.background_highlights + .insert(TypeId::of::(), (color_fetcher, ranges)); + cx.notify(); + } + + // pub fn highlight_inlay_background( + // &mut self, + // ranges: Vec, + // color_fetcher: fn(&Theme) -> Color, + // cx: &mut ViewContext, + // ) { + // // TODO: no actual highlights happen for inlays currently, find a way to do that + // self.inlay_background_highlights + // .insert(Some(TypeId::of::()), (color_fetcher, ranges)); + // cx.notify(); + // } + + // pub fn clear_background_highlights( + // &mut self, + // cx: &mut ViewContext, + // ) -> Option { + // let text_highlights = self.background_highlights.remove(&TypeId::of::()); + // let inlay_highlights = self + // .inlay_background_highlights + // .remove(&Some(TypeId::of::())); + // if text_highlights.is_some() || inlay_highlights.is_some() { + // cx.notify(); + // } + // text_highlights + // } + + // #[cfg(feature = "test-support")] + // pub fn all_text_background_highlights( + // &mut self, + // cx: &mut ViewContext, + // ) -> Vec<(Range, Color)> { + // let snapshot = self.snapshot(cx); + // let buffer = &snapshot.buffer_snapshot; + // let start = buffer.anchor_before(0); + // let end = buffer.anchor_after(buffer.len()); + // let theme = theme::current(cx); + // self.background_highlights_in_range(start..end, &snapshot, theme.as_ref()) + // } + + // fn document_highlights_for_position<'a>( + // &'a self, + // position: Anchor, + // buffer: &'a MultiBufferSnapshot, + // ) -> impl 'a + Iterator> { + // let read_highlights = self + // .background_highlights + // .get(&TypeId::of::()) + // .map(|h| &h.1); + // let write_highlights = self + // .background_highlights + // .get(&TypeId::of::()) + // .map(|h| &h.1); + // let left_position = position.bias_left(buffer); + // let right_position = position.bias_right(buffer); + // read_highlights + // .into_iter() + // .chain(write_highlights) + // .flat_map(move |ranges| { + // let start_ix = match ranges.binary_search_by(|probe| { + // let cmp = probe.end.cmp(&left_position, buffer); + // if cmp.is_ge() { + // Ordering::Greater + // } else { + // Ordering::Less + // } + // }) { + // Ok(i) | Err(i) => i, + // }; + + // let right_position = right_position.clone(); + // ranges[start_ix..] + // .iter() + // .take_while(move |range| range.start.cmp(&right_position, buffer).is_le()) + // }) + // } + + // pub fn background_highlights_in_range( + // &self, + // search_range: Range, + // display_snapshot: &DisplaySnapshot, + // theme: &Theme, + // ) -> Vec<(Range, Color)> { + // let mut results = Vec::new(); + // for (color_fetcher, ranges) in self.background_highlights.values() { + // let color = color_fetcher(theme); + // let start_ix = match ranges.binary_search_by(|probe| { + // let cmp = probe + // .end + // .cmp(&search_range.start, &display_snapshot.buffer_snapshot); + // if cmp.is_gt() { + // Ordering::Greater + // } else { + // Ordering::Less + // } + // }) { + // Ok(i) | Err(i) => i, + // }; + // for range in &ranges[start_ix..] { + // if range + // .start + // .cmp(&search_range.end, &display_snapshot.buffer_snapshot) + // .is_ge() + // { + // break; + // } + + // let start = range.start.to_display_point(&display_snapshot); + // let end = range.end.to_display_point(&display_snapshot); + // results.push((start..end, color)) + // } + // } + // results + // } + + // pub fn background_highlight_row_ranges( + // &self, + // search_range: Range, + // display_snapshot: &DisplaySnapshot, + // count: usize, + // ) -> Vec> { + // let mut results = Vec::new(); + // let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::()) else { + // return vec![]; + // }; + + // let start_ix = match ranges.binary_search_by(|probe| { + // let cmp = probe + // .end + // .cmp(&search_range.start, &display_snapshot.buffer_snapshot); + // if cmp.is_gt() { + // Ordering::Greater + // } else { + // Ordering::Less + // } + // }) { + // Ok(i) | Err(i) => i, + // }; + // let mut push_region = |start: Option, end: Option| { + // if let (Some(start_display), Some(end_display)) = (start, end) { + // results.push( + // start_display.to_display_point(display_snapshot) + // ..=end_display.to_display_point(display_snapshot), + // ); + // } + // }; + // let mut start_row: Option = None; + // let mut end_row: Option = None; + // if ranges.len() > count { + // return Vec::new(); + // } + // for range in &ranges[start_ix..] { + // if range + // .start + // .cmp(&search_range.end, &display_snapshot.buffer_snapshot) + // .is_ge() + // { + // break; + // } + // let end = range.end.to_point(&display_snapshot.buffer_snapshot); + // if let Some(current_row) = &end_row { + // if end.row == current_row.row { + // continue; + // } + // } + // let start = range.start.to_point(&display_snapshot.buffer_snapshot); + // if start_row.is_none() { + // assert_eq!(end_row, None); + // start_row = Some(start); + // end_row = Some(end); + // continue; + // } + // if let Some(current_end) = end_row.as_mut() { + // if start.row > current_end.row + 1 { + // push_region(start_row, end_row); + // start_row = Some(start); + // end_row = Some(end); + // } else { + // // Merge two hunks. + // *current_end = end; + // } + // } else { + // unreachable!(); + // } + // } + // // We might still have a hunk that was not rendered (if there was a search hit on the last line) + // push_region(start_row, end_row); + // results + // } + + // pub fn highlight_text( + // &mut self, + // ranges: Vec>, + // style: HighlightStyle, + // cx: &mut ViewContext, + // ) { + // self.display_map.update(cx, |map, _| { + // map.highlight_text(TypeId::of::(), ranges, style) + // }); + // cx.notify(); + // } + + // pub fn highlight_inlays( + // &mut self, + // highlights: Vec, + // style: HighlightStyle, + // cx: &mut ViewContext, + // ) { + // self.display_map.update(cx, |map, _| { + // map.highlight_inlays(TypeId::of::(), highlights, style) + // }); + // cx.notify(); + // } + + // pub fn text_highlights<'a, T: 'static>( + // &'a self, + // cx: &'a AppContext, + // ) -> Option<(HighlightStyle, &'a [Range])> { + // self.display_map.read(cx).text_highlights(TypeId::of::()) + // } + + pub fn clear_highlights(&mut self, cx: &mut ViewContext) { + let cleared = self + .display_map + .update(cx, |map, _| map.clear_highlights(TypeId::of::())); + if cleared { + cx.notify(); + } + } + + // pub fn show_local_cursors(&self, cx: &AppContext) -> bool { + // self.blink_manager.read(cx).visible() && self.focused + // } + + fn on_buffer_changed(&mut self, _: Model, cx: &mut ViewContext) { + cx.notify(); + } + + fn on_buffer_event( + &mut self, + multibuffer: Model, + event: &multi_buffer::Event, + cx: &mut ViewContext, + ) { + match event { + multi_buffer::Event::Edited { + sigleton_buffer_edited, + } => { + self.refresh_active_diagnostics(cx); + self.refresh_code_actions(cx); + if self.has_active_copilot_suggestion(cx) { + self.update_visible_copilot_suggestion(cx); + } + cx.emit(Event::BufferEdited); + + if *sigleton_buffer_edited { + if let Some(project) = &self.project { + let project = project.read(cx); + let languages_affected = multibuffer + .read(cx) + .all_buffers() + .into_iter() + .filter_map(|buffer| { + let buffer = buffer.read(cx); + let language = buffer.language()?; + if project.is_local() + && project.language_servers_for_buffer(buffer, cx).count() == 0 + { + None + } else { + Some(language) + } + }) + .cloned() + .collect::>(); + if !languages_affected.is_empty() { + self.refresh_inlay_hints( + InlayHintRefreshReason::BufferEdited(languages_affected), + cx, + ); + } + } + } + } + multi_buffer::Event::ExcerptsAdded { + buffer, + predecessor, + excerpts, + } => { + cx.emit(Event::ExcerptsAdded { + buffer: buffer.clone(), + predecessor: *predecessor, + excerpts: excerpts.clone(), + }); + self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); + } + multi_buffer::Event::ExcerptsRemoved { ids } => { + self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx); + cx.emit(Event::ExcerptsRemoved { ids: ids.clone() }) + } + multi_buffer::Event::Reparsed => cx.emit(Event::Reparsed), + multi_buffer::Event::DirtyChanged => cx.emit(Event::DirtyChanged), + multi_buffer::Event::Saved => cx.emit(Event::Saved), + multi_buffer::Event::FileHandleChanged => cx.emit(Event::TitleChanged), + multi_buffer::Event::Reloaded => cx.emit(Event::TitleChanged), + multi_buffer::Event::DiffBaseChanged => cx.emit(Event::DiffBaseChanged), + multi_buffer::Event::Closed => cx.emit(Event::Closed), + multi_buffer::Event::DiagnosticsUpdated => { + self.refresh_active_diagnostics(cx); + } + _ => {} + }; + } + + fn on_display_map_changed(&mut self, _: Model, cx: &mut ViewContext) { + cx.notify(); + } + + fn settings_changed(&mut self, cx: &mut ViewContext) { + self.refresh_copilot_suggestions(true, cx); + self.refresh_inlay_hints( + InlayHintRefreshReason::SettingsChange(inlay_hint_settings( + self.selections.newest_anchor().head(), + &self.buffer.read(cx).snapshot(cx), + cx, + )), + cx, + ); + } + + // pub fn set_searchable(&mut self, searchable: bool) { + // self.searchable = searchable; + // } + + // pub fn searchable(&self) -> bool { + // self.searchable + // } + + // fn open_excerpts(workspace: &mut Workspace, _: &OpenExcerpts, cx: &mut ViewContext) { + // let active_item = workspace.active_item(cx); + // let editor_handle = if let Some(editor) = active_item + // .as_ref() + // .and_then(|item| item.act_as::(cx)) + // { + // editor + // } else { + // cx.propagate_action(); + // return; + // }; + + // let editor = editor_handle.read(cx); + // let buffer = editor.buffer.read(cx); + // if buffer.is_singleton() { + // cx.propagate_action(); + // return; + // } + + // let mut new_selections_by_buffer = HashMap::default(); + // for selection in editor.selections.all::(cx) { + // for (buffer, mut range, _) in + // buffer.range_to_buffer_ranges(selection.start..selection.end, cx) + // { + // if selection.reversed { + // mem::swap(&mut range.start, &mut range.end); + // } + // new_selections_by_buffer + // .entry(buffer) + // .or_insert(Vec::new()) + // .push(range) + // } + // } + + // editor_handle.update(cx, |editor, cx| { + // editor.push_to_nav_history(editor.selections.newest_anchor().head(), None, cx); + // }); + // let pane = workspace.active_pane().clone(); + // pane.update(cx, |pane, _| pane.disable_history()); + + // // We defer the pane interaction because we ourselves are a workspace item + // // and activating a new item causes the pane to call a method on us reentrantly, + // // which panics if we're on the stack. + // cx.defer(move |workspace, cx| { + // for (buffer, ranges) in new_selections_by_buffer.into_iter() { + // let editor = workspace.open_project_item::(buffer, cx); + // editor.update(cx, |editor, cx| { + // editor.change_selections(Some(Autoscroll::newest()), cx, |s| { + // s.select_ranges(ranges); + // }); + // }); + // } + + // pane.update(cx, |pane, _| pane.enable_history()); + // }); + // } + + // fn jump( + // workspace: &mut Workspace, + // path: ProjectPath, + // position: Point, + // anchor: language::Anchor, + // cx: &mut ViewContext, + // ) { + // let editor = workspace.open_path(path, None, true, cx); + // cx.spawn(|_, mut cx| async move { + // let editor = editor + // .await? + // .downcast::() + // .ok_or_else(|| anyhow!("opened item was not an editor"))? + // .downgrade(); + // editor.update(&mut cx, |editor, cx| { + // let buffer = editor + // .buffer() + // .read(cx) + // .as_singleton() + // .ok_or_else(|| anyhow!("cannot jump in a multi-buffer"))?; + // let buffer = buffer.read(cx); + // let cursor = if buffer.can_resolve(&anchor) { + // language::ToPoint::to_point(&anchor, buffer) + // } else { + // buffer.clip_point(position, Bias::Left) + // }; + + // let nav_history = editor.nav_history.take(); + // editor.change_selections(Some(Autoscroll::newest()), cx, |s| { + // s.select_ranges([cursor..cursor]); + // }); + // editor.nav_history = nav_history; + + // anyhow::Ok(()) + // })??; + + // anyhow::Ok(()) + // }) + // .detach_and_log_err(cx); + // } + + // fn marked_text_ranges(&self, cx: &AppContext) -> Option>> { + // let snapshot = self.buffer.read(cx).read(cx); + // let (_, ranges) = self.text_highlights::(cx)?; + // Some( + // ranges + // .iter() + // .map(move |range| { + // range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot) + // }) + // .collect(), + // ) + // } + + // fn selection_replacement_ranges( + // &self, + // range: Range, + // cx: &AppContext, + // ) -> Vec> { + // let selections = self.selections.all::(cx); + // let newest_selection = selections + // .iter() + // .max_by_key(|selection| selection.id) + // .unwrap(); + // let start_delta = range.start.0 as isize - newest_selection.start.0 as isize; + // let end_delta = range.end.0 as isize - newest_selection.end.0 as isize; + // let snapshot = self.buffer.read(cx).read(cx); + // selections + // .into_iter() + // .map(|mut selection| { + // selection.start.0 = + // (selection.start.0 as isize).saturating_add(start_delta) as usize; + // selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize; + // snapshot.clip_offset_utf16(selection.start, Bias::Left) + // ..snapshot.clip_offset_utf16(selection.end, Bias::Right) + // }) + // .collect() + // } + + fn report_copilot_event( + &self, + suggestion_id: Option, + suggestion_accepted: bool, + cx: &AppContext, + ) { + let Some(project) = &self.project else { return }; + + // If None, we are either getting suggestions in a new, unsaved file, or in a file without an extension + let file_extension = self + .buffer + .read(cx) + .as_singleton() + .and_then(|b| b.read(cx).file()) + .and_then(|file| Path::new(file.file_name(cx)).extension()) + .and_then(|e| e.to_str()) + .map(|a| a.to_string()); + + let telemetry = project.read(cx).client().telemetry().clone(); + let telemetry_settings = *TelemetrySettings::get_global(cx); + + let event = ClickhouseEvent::Copilot { + suggestion_id, + suggestion_accepted, + file_extension, + }; + telemetry.report_clickhouse_event(event, telemetry_settings); + } + + #[cfg(any(test, feature = "test-support"))] + fn report_editor_event( + &self, + _operation: &'static str, + _file_extension: Option, + _cx: &AppContext, + ) { + } + + #[cfg(not(any(test, feature = "test-support")))] + fn report_editor_event( + &self, + operation: &'static str, + file_extension: Option, + cx: &AppContext, + ) { + let Some(project) = &self.project else { return }; + + // If None, we are in a file without an extension + let file = self + .buffer + .read(cx) + .as_singleton() + .and_then(|b| b.read(cx).file()); + let file_extension = file_extension.or(file + .as_ref() + .and_then(|file| Path::new(file.file_name(cx)).extension()) + .and_then(|e| e.to_str()) + .map(|a| a.to_string())); + + let vim_mode = cx + .global::() + .raw_user_settings() + .get("vim_mode") + == Some(&serde_json::Value::Bool(true)); + let telemetry_settings = *TelemetrySettings::get_global(cx); + let copilot_enabled = all_language_settings(file, cx).copilot_enabled(None, None); + let copilot_enabled_for_language = self + .buffer + .read(cx) + .settings_at(0, cx) + .show_copilot_suggestions; + + let telemetry = project.read(cx).client().telemetry().clone(); + let event = ClickhouseEvent::Editor { + file_extension, + vim_mode, + operation, + copilot_enabled, + copilot_enabled_for_language, + }; + telemetry.report_clickhouse_event(event, telemetry_settings) + } + + // /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines, + // /// with each line being an array of {text, highlight} objects. + // fn copy_highlight_json(&mut self, _: &CopyHighlightJson, cx: &mut ViewContext) { + // let Some(buffer) = self.buffer.read(cx).as_singleton() else { + // return; + // }; + + // #[derive(Serialize)] + // struct Chunk<'a> { + // text: String, + // highlight: Option<&'a str>, + // } + + // let snapshot = buffer.read(cx).snapshot(); + // let range = self + // .selected_text_range(cx) + // .and_then(|selected_range| { + // if selected_range.is_empty() { + // None + // } else { + // Some(selected_range) + // } + // }) + // .unwrap_or_else(|| 0..snapshot.len()); + + // let chunks = snapshot.chunks(range, true); + // let mut lines = Vec::new(); + // let mut line: VecDeque = VecDeque::new(); + + // let theme = &theme::current(cx).editor.syntax; + + // for chunk in chunks { + // let highlight = chunk.syntax_highlight_id.and_then(|id| id.name(theme)); + // let mut chunk_lines = chunk.text.split("\n").peekable(); + // while let Some(text) = chunk_lines.next() { + // let mut merged_with_last_token = false; + // if let Some(last_token) = line.back_mut() { + // if last_token.highlight == highlight { + // last_token.text.push_str(text); + // merged_with_last_token = true; + // } + // } + + // if !merged_with_last_token { + // line.push_back(Chunk { + // text: text.into(), + // highlight, + // }); + // } + + // if chunk_lines.peek().is_some() { + // if line.len() > 1 && line.front().unwrap().text.is_empty() { + // line.pop_front(); + // } + // if line.len() > 1 && line.back().unwrap().text.is_empty() { + // line.pop_back(); + // } + + // lines.push(mem::take(&mut line)); + // } + // } + // } + + // let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else { + // return; + // }; + // cx.write_to_clipboard(ClipboardItem::new(lines)); + // } + + // pub fn inlay_hint_cache(&self) -> &InlayHintCache { + // &self.inlay_hint_cache + // } + + // pub fn replay_insert_event( + // &mut self, + // text: &str, + // relative_utf16_range: Option>, + // cx: &mut ViewContext, + // ) { + // if !self.input_enabled { + // cx.emit(Event::InputIgnored { text: text.into() }); + // return; + // } + // if let Some(relative_utf16_range) = relative_utf16_range { + // let selections = self.selections.all::(cx); + // self.change_selections(None, cx, |s| { + // let new_ranges = selections.into_iter().map(|range| { + // let start = OffsetUtf16( + // range + // .head() + // .0 + // .saturating_add_signed(relative_utf16_range.start), + // ); + // let end = OffsetUtf16( + // range + // .head() + // .0 + // .saturating_add_signed(relative_utf16_range.end), + // ); + // start..end + // }); + // s.select_ranges(new_ranges); + // }); + // } + + // self.handle_input(text, cx); + // } + + // pub fn supports_inlay_hints(&self, cx: &AppContext) -> bool { + // let Some(project) = self.project.as_ref() else { + // return false; + // }; + // let project = project.read(cx); + + // let mut supports = false; + // self.buffer().read(cx).for_each_buffer(|buffer| { + // if !supports { + // supports = project + // .language_servers_for_buffer(buffer.read(cx), cx) + // .any( + // |(_, server)| match server.capabilities().inlay_hint_provider { + // Some(lsp::OneOf::Left(enabled)) => enabled, + // Some(lsp::OneOf::Right(_)) => true, + // None => false, + // }, + // ) + // } + // }); + // supports + // } +} + +pub trait CollaborationHub { + fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap; + fn user_participant_indices<'a>( + &self, + cx: &'a AppContext, + ) -> &'a HashMap; +} + +impl CollaborationHub for Model { + fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap { + self.read(cx).collaborators() + } + + fn user_participant_indices<'a>( + &self, + cx: &'a AppContext, + ) -> &'a HashMap { + self.read(cx).user_store().read(cx).participant_indices() + } +} + +fn inlay_hint_settings( + location: Anchor, + snapshot: &MultiBufferSnapshot, + cx: &mut ViewContext<'_, Editor>, +) -> InlayHintSettings { + let file = snapshot.file_at(location); + let language = snapshot.language_at(location); + let settings = all_language_settings(file, cx); + settings + .language(language.map(|l| l.name()).as_deref()) + .inlay_hints +} + +fn consume_contiguous_rows( + contiguous_row_selections: &mut Vec>, + selection: &Selection, + display_map: &DisplaySnapshot, + selections: &mut std::iter::Peekable>>, +) -> (u32, u32) { + contiguous_row_selections.push(selection.clone()); + let start_row = selection.start.row; + let mut end_row = ending_row(selection, display_map); + + while let Some(next_selection) = selections.peek() { + if next_selection.start.row <= end_row { + end_row = ending_row(next_selection, display_map); + contiguous_row_selections.push(selections.next().unwrap().clone()); + } else { + break; + } + } + (start_row, end_row) +} + +fn ending_row(next_selection: &Selection, display_map: &DisplaySnapshot) -> u32 { + if next_selection.end.column > 0 || next_selection.is_empty() { + display_map.next_line_boundary(next_selection.end).0.row + 1 + } else { + next_selection.end.row + } +} + +impl EditorSnapshot { + pub fn remote_selections_in_range<'a>( + &'a self, + range: &'a Range, + collaboration_hub: &dyn CollaborationHub, + cx: &'a AppContext, + ) -> impl 'a + Iterator { + let participant_indices = collaboration_hub.user_participant_indices(cx); + let collaborators_by_peer_id = collaboration_hub.collaborators(cx); + let collaborators_by_replica_id = collaborators_by_peer_id + .iter() + .map(|(_, collaborator)| (collaborator.replica_id, collaborator)) + .collect::>(); + self.buffer_snapshot + .remote_selections_in_range(range) + .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(); + Some(RemoteSelection { + replica_id, + selection, + cursor_shape, + line_mode, + participant_index, + peer_id: collaborator.peer_id, + }) + }) + } + + pub fn language_at(&self, position: T) -> Option<&Arc> { + self.display_snapshot.buffer_snapshot.language_at(position) + } + + pub fn is_focused(&self) -> bool { + self.is_focused + } + + pub fn placeholder_text(&self) -> Option<&Arc> { + self.placeholder_text.as_ref() + } + + pub fn scroll_position(&self) -> gpui::Point { + self.scroll_anchor.scroll_position(&self.display_snapshot) + } +} + +impl Deref for EditorSnapshot { + type Target = DisplaySnapshot; + + fn deref(&self) -> &Self::Target { + &self.display_snapshot + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Event { + InputIgnored { + text: Arc, + }, + InputHandled { + utf16_range_to_replace: Option>, + text: Arc, + }, + ExcerptsAdded { + buffer: Model, + predecessor: ExcerptId, + excerpts: Vec<(ExcerptId, ExcerptRange)>, + }, + ExcerptsRemoved { + ids: Vec, + }, + BufferEdited, + Edited, + Reparsed, + Focused, + Blurred, + DirtyChanged, + Saved, + TitleChanged, + DiffBaseChanged, + SelectionsChanged { + local: bool, + }, + ScrollPositionChanged { + local: bool, + autoscroll: bool, + }, + Closed, +} + +pub struct EditorFocused(pub View); +pub struct EditorBlurred(pub View); +pub struct EditorReleased(pub WeakView); + +// impl Entity for Editor { +// type Event = Event; + +// fn release(&mut self, cx: &mut AppContext) { +// cx.emit_global(EditorReleased(self.handle.clone())); +// } +// } +// +impl EventEmitter for Editor { + type Event = Event; +} + +impl Render for Editor { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + // todo!() + div() + } +} + +// impl View for Editor { +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// let style = self.style(cx); +// let font_changed = self.display_map.update(cx, |map, cx| { +// map.set_fold_ellipses_color(style.folds.ellipses.text_color); +// map.set_font_with_size(style.text.font_id, style.text.font_size, cx) +// }); + +// if font_changed { +// cx.defer(move |editor, cx: &mut ViewContext| { +// hide_hover(editor, cx); +// hide_link_definition(editor, cx); +// }); +// } + +// Stack::new() +// .with_child(EditorElement::new(style.clone())) +// .with_child(ChildView::new(&self.mouse_context_menu, cx)) +// .into_any() +// } + +// fn ui_name() -> &'static str { +// "Editor" +// } + +// fn focus_in(&mut self, focused: AnyView, cx: &mut ViewContext) { +// if cx.is_self_focused() { +// let focused_event = EditorFocused(cx.handle()); +// cx.emit(Event::Focused); +// cx.emit_global(focused_event); +// } +// if let Some(rename) = self.pending_rename.as_ref() { +// cx.focus(&rename.editor); +// } else if cx.is_self_focused() || !focused.is::() { +// if !self.focused { +// self.blink_manager.update(cx, BlinkManager::enable); +// } +// self.focused = true; +// self.buffer.update(cx, |buffer, cx| { +// buffer.finalize_last_transaction(cx); +// if self.leader_peer_id.is_none() { +// buffer.set_active_selections( +// &self.selections.disjoint_anchors(), +// self.selections.line_mode, +// self.cursor_shape, +// cx, +// ); +// } +// }); +// } +// } + +// fn focus_out(&mut self, _: AnyView, cx: &mut ViewContext) { +// let blurred_event = EditorBlurred(cx.handle()); +// cx.emit_global(blurred_event); +// self.focused = false; +// self.blink_manager.update(cx, BlinkManager::disable); +// self.buffer +// .update(cx, |buffer, cx| buffer.remove_active_selections(cx)); +// self.hide_context_menu(cx); +// hide_hover(self, cx); +// cx.emit(Event::Blurred); +// cx.notify(); +// } + +// fn modifiers_changed( +// &mut self, +// event: &gpui::platform::ModifiersChangedEvent, +// cx: &mut ViewContext, +// ) -> bool { +// let pending_selection = self.has_pending_selection(); + +// if let Some(point) = &self.link_go_to_definition_state.last_trigger_point { +// if event.cmd && !pending_selection { +// let point = point.clone(); +// let snapshot = self.snapshot(cx); +// let kind = point.definition_kind(event.shift); + +// show_link_definition(kind, self, point, snapshot, cx); +// return false; +// } +// } + +// { +// if self.link_go_to_definition_state.symbol_range.is_some() +// || !self.link_go_to_definition_state.definitions.is_empty() +// { +// self.link_go_to_definition_state.symbol_range.take(); +// self.link_go_to_definition_state.definitions.clear(); +// cx.notify(); +// } + +// self.link_go_to_definition_state.task = None; + +// self.clear_highlights::(cx); +// } + +// false +// } + +// fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &AppContext) { +// Self::reset_to_default_keymap_context(keymap); +// let mode = match self.mode { +// EditorMode::SingleLine => "single_line", +// EditorMode::AutoHeight { .. } => "auto_height", +// EditorMode::Full => "full", +// }; +// keymap.add_key("mode", mode); +// if self.pending_rename.is_some() { +// keymap.add_identifier("renaming"); +// } +// if self.context_menu_visible() { +// match self.context_menu.read().as_ref() { +// Some(ContextMenu::Completions(_)) => { +// keymap.add_identifier("menu"); +// keymap.add_identifier("showing_completions") +// } +// Some(ContextMenu::CodeActions(_)) => { +// keymap.add_identifier("menu"); +// keymap.add_identifier("showing_code_actions") +// } +// None => {} +// } +// } + +// for layer in self.keymap_context_layers.values() { +// keymap.extend(layer); +// } + +// if let Some(extension) = self +// .buffer +// .read(cx) +// .as_singleton() +// .and_then(|buffer| buffer.read(cx).file()?.path().extension()?.to_str()) +// { +// keymap.add_key("extension", extension.to_string()); +// } +// } + +// fn text_for_range(&self, range_utf16: Range, cx: &AppContext) -> Option { +// Some( +// self.buffer +// .read(cx) +// .read(cx) +// .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end)) +// .collect(), +// ) +// } + +// fn selected_text_range(&self, cx: &AppContext) -> Option> { +// // Prevent the IME menu from appearing when holding down an alphabetic key +// // while input is disabled. +// if !self.input_enabled { +// return None; +// } + +// let range = self.selections.newest::(cx).range(); +// Some(range.start.0..range.end.0) +// } + +// fn marked_text_range(&self, cx: &AppContext) -> Option> { +// let snapshot = self.buffer.read(cx).read(cx); +// let range = self.text_highlights::(cx)?.1.get(0)?; +// Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0) +// } + +// fn unmark_text(&mut self, cx: &mut ViewContext) { +// self.clear_highlights::(cx); +// self.ime_transaction.take(); +// } + +// fn replace_text_in_range( +// &mut self, +// range_utf16: Option>, +// text: &str, +// cx: &mut ViewContext, +// ) { +// if !self.input_enabled { +// cx.emit(Event::InputIgnored { text: text.into() }); +// return; +// } + +// self.transact(cx, |this, cx| { +// let new_selected_ranges = if let Some(range_utf16) = range_utf16 { +// let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); +// Some(this.selection_replacement_ranges(range_utf16, cx)) +// } else { +// this.marked_text_ranges(cx) +// }; + +// let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| { +// let newest_selection_id = this.selections.newest_anchor().id; +// this.selections +// .all::(cx) +// .iter() +// .zip(ranges_to_replace.iter()) +// .find_map(|(selection, range)| { +// if selection.id == newest_selection_id { +// Some( +// (range.start.0 as isize - selection.head().0 as isize) +// ..(range.end.0 as isize - selection.head().0 as isize), +// ) +// } else { +// None +// } +// }) +// }); + +// cx.emit(Event::InputHandled { +// utf16_range_to_replace: range_to_replace, +// text: text.into(), +// }); + +// if let Some(new_selected_ranges) = new_selected_ranges { +// this.change_selections(None, cx, |selections| { +// selections.select_ranges(new_selected_ranges) +// }); +// } + +// this.handle_input(text, cx); +// }); + +// if let Some(transaction) = self.ime_transaction { +// self.buffer.update(cx, |buffer, cx| { +// buffer.group_until_transaction(transaction, cx); +// }); +// } + +// self.unmark_text(cx); +// } + +// fn replace_and_mark_text_in_range( +// &mut self, +// range_utf16: Option>, +// text: &str, +// new_selected_range_utf16: Option>, +// cx: &mut ViewContext, +// ) { +// if !self.input_enabled { +// cx.emit(Event::InputIgnored { text: text.into() }); +// return; +// } + +// let transaction = self.transact(cx, |this, cx| { +// let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) { +// let snapshot = this.buffer.read(cx).read(cx); +// if let Some(relative_range_utf16) = range_utf16.as_ref() { +// for marked_range in &mut marked_ranges { +// marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end; +// marked_range.start.0 += relative_range_utf16.start; +// marked_range.start = +// snapshot.clip_offset_utf16(marked_range.start, Bias::Left); +// marked_range.end = +// snapshot.clip_offset_utf16(marked_range.end, Bias::Right); +// } +// } +// Some(marked_ranges) +// } else if let Some(range_utf16) = range_utf16 { +// let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); +// Some(this.selection_replacement_ranges(range_utf16, cx)) +// } else { +// None +// }; + +// let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| { +// let newest_selection_id = this.selections.newest_anchor().id; +// this.selections +// .all::(cx) +// .iter() +// .zip(ranges_to_replace.iter()) +// .find_map(|(selection, range)| { +// if selection.id == newest_selection_id { +// Some( +// (range.start.0 as isize - selection.head().0 as isize) +// ..(range.end.0 as isize - selection.head().0 as isize), +// ) +// } else { +// None +// } +// }) +// }); + +// cx.emit(Event::InputHandled { +// utf16_range_to_replace: range_to_replace, +// text: text.into(), +// }); + +// if let Some(ranges) = ranges_to_replace { +// this.change_selections(None, cx, |s| s.select_ranges(ranges)); +// } + +// let marked_ranges = { +// let snapshot = this.buffer.read(cx).read(cx); +// this.selections +// .disjoint_anchors() +// .iter() +// .map(|selection| { +// selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot) +// }) +// .collect::>() +// }; + +// if text.is_empty() { +// this.unmark_text(cx); +// } else { +// this.highlight_text::( +// marked_ranges.clone(), +// this.style(cx).composition_mark, +// cx, +// ); +// } + +// this.handle_input(text, cx); + +// if let Some(new_selected_range) = new_selected_range_utf16 { +// let snapshot = this.buffer.read(cx).read(cx); +// let new_selected_ranges = marked_ranges +// .into_iter() +// .map(|marked_range| { +// let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0; +// let new_start = OffsetUtf16(new_selected_range.start + insertion_start); +// let new_end = OffsetUtf16(new_selected_range.end + insertion_start); +// snapshot.clip_offset_utf16(new_start, Bias::Left) +// ..snapshot.clip_offset_utf16(new_end, Bias::Right) +// }) +// .collect::>(); + +// drop(snapshot); +// this.change_selections(None, cx, |selections| { +// selections.select_ranges(new_selected_ranges) +// }); +// } +// }); + +// self.ime_transaction = self.ime_transaction.or(transaction); +// if let Some(transaction) = self.ime_transaction { +// self.buffer.update(cx, |buffer, cx| { +// buffer.group_until_transaction(transaction, cx); +// }); +// } + +// if self.text_highlights::(cx).is_none() { +// self.ime_transaction.take(); +// } +// } +// } + +// fn build_style( +// settings: &ThemeSettings, +// get_field_editor_theme: Option<&GetFieldEditorTheme>, +// override_text_style: Option<&OverrideTextStyle>, +// cx: &mut AppContext, +// ) -> EditorStyle { +// let font_cache = cx.font_cache(); +// let line_height_scalar = settings.line_height(); +// let theme_id = settings.theme.meta.id; +// let mut theme = settings.theme.editor.clone(); +// let mut style = if let Some(get_field_editor_theme) = get_field_editor_theme { +// let field_editor_theme = get_field_editor_theme(&settings.theme); +// theme.text_color = field_editor_theme.text.color; +// theme.selection = field_editor_theme.selection; +// theme.background = field_editor_theme +// .container +// .background_color +// .unwrap_or_default(); +// EditorStyle { +// text: field_editor_theme.text, +// placeholder_text: field_editor_theme.placeholder_text, +// line_height_scalar, +// theme, +// theme_id, +// } +// } else { +// todo!(); +// // let font_family_id = settings.buffer_font_family; +// // let font_family_name = cx.font_cache().family_name(font_family_id).unwrap(); +// // let font_properties = Default::default(); +// // let font_id = font_cache +// // .select_font(font_family_id, &font_properties) +// // .unwrap(); +// // let font_size = settings.buffer_font_size(cx); +// // EditorStyle { +// // text: TextStyle { +// // color: settings.theme.editor.text_color, +// // font_family_name, +// // font_family_id, +// // font_id, +// // font_size, +// // font_properties, +// // underline: Default::default(), +// // soft_wrap: false, +// // }, +// // placeholder_text: None, +// // line_height_scalar, +// // theme, +// // theme_id, +// // } +// }; + +// if let Some(highlight_style) = override_text_style.and_then(|build_style| build_style(&style)) { +// if let Some(highlighted) = style +// .text +// .clone() +// .highlight(highlight_style, font_cache) +// .log_err() +// { +// style.text = highlighted; +// } +// } + +// style +// } + +trait SelectionExt { + fn offset_range(&self, buffer: &MultiBufferSnapshot) -> Range; + fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range; + fn display_range(&self, map: &DisplaySnapshot) -> Range; + fn spanned_rows(&self, include_end_if_at_line_start: bool, map: &DisplaySnapshot) + -> Range; +} + +impl SelectionExt for Selection { + fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range { + let start = self.start.to_point(buffer); + let end = self.end.to_point(buffer); + if self.reversed { + end..start + } else { + start..end + } + } + + fn offset_range(&self, buffer: &MultiBufferSnapshot) -> Range { + let start = self.start.to_offset(buffer); + let end = self.end.to_offset(buffer); + if self.reversed { + end..start + } else { + start..end + } + } + + fn display_range(&self, map: &DisplaySnapshot) -> Range { + let start = self + .start + .to_point(&map.buffer_snapshot) + .to_display_point(map); + let end = self + .end + .to_point(&map.buffer_snapshot) + .to_display_point(map); + if self.reversed { + end..start + } else { + start..end + } + } + + fn spanned_rows( + &self, + include_end_if_at_line_start: bool, + map: &DisplaySnapshot, + ) -> Range { + let start = self.start.to_point(&map.buffer_snapshot); + let mut end = self.end.to_point(&map.buffer_snapshot); + if !include_end_if_at_line_start && start.row != end.row && end.column == 0 { + end.row -= 1; + } + + let buffer_start = map.prev_line_boundary(start).0; + let buffer_end = map.next_line_boundary(end).0; + buffer_start.row..buffer_end.row + 1 + } +} + +impl InvalidationStack { + fn invalidate(&mut self, selections: &[Selection], buffer: &MultiBufferSnapshot) + where + S: Clone + ToOffset, + { + while let Some(region) = self.last() { + let all_selections_inside_invalidation_ranges = + if selections.len() == region.ranges().len() { + selections + .iter() + .zip(region.ranges().iter().map(|r| r.to_offset(buffer))) + .all(|(selection, invalidation_range)| { + let head = selection.head().to_offset(buffer); + invalidation_range.start <= head && invalidation_range.end >= head + }) + } else { + false + }; + + if all_selections_inside_invalidation_ranges { + break; + } else { + self.pop(); + } + } + } +} + +impl Default for InvalidationStack { + fn default() -> Self { + Self(Default::default()) + } +} + +impl Deref for InvalidationStack { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for InvalidationStack { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl InvalidationRegion for SnippetState { + fn ranges(&self) -> &[Range] { + &self.ranges[self.active_index] + } +} + +// impl Deref for EditorStyle { +// type Target = theme::Editor; + +// fn deref(&self) -> &Self::Target { +// &self.theme +// } +// } + +pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> RenderBlock { + let mut highlighted_lines = Vec::new(); + + for (index, line) in diagnostic.message.lines().enumerate() { + let line = match &diagnostic.source { + Some(source) if index == 0 => { + let source_highlight = Vec::from_iter(0..source.len()); + highlight_diagnostic_message(source_highlight, &format!("{source}: {line}")) + } + + _ => highlight_diagnostic_message(Vec::new(), line), + }; + highlighted_lines.push(line); + } + let message = diagnostic.message; + Arc::new(move |cx: &mut BlockContext| { + todo!() + // let message = message.clone(); + // let settings = ThemeSettings::get_global(cx); + // let tooltip_style = settings.theme.tooltip.clone(); + // let theme = &settings.theme.editor; + // let style = diagnostic_style(diagnostic.severity, is_valid, theme); + // let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round(); + // let anchor_x = cx.anchor_x; + // enum BlockContextToolip {} + // MouseEventHandler::new::(cx.block_id, cx, |_, _| { + // Flex::column() + // .with_children(highlighted_lines.iter().map(|(line, highlights)| { + // Label::new( + // line.clone(), + // style.message.clone().with_font_size(font_size), + // ) + // .with_highlights(highlights.clone()) + // .contained() + // .with_margin_left(anchor_x) + // })) + // .aligned() + // .left() + // .into_any() + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_click(MouseButton::Left, move |_, _, cx| { + // cx.write_to_clipboard(ClipboardItem::new(message.clone())); + // }) + // // We really need to rethink this ID system... + // .with_tooltip::( + // cx.block_id, + // "Copy diagnostic message", + // None, + // tooltip_style, + // cx, + // ) + // .into_any() + }) +} + +pub fn highlight_diagnostic_message( + initial_highlights: Vec, + message: &str, +) -> (String, Vec) { + let mut message_without_backticks = String::new(); + let mut prev_offset = 0; + let mut inside_block = false; + let mut highlights = initial_highlights; + for (match_ix, (offset, _)) in message + .match_indices('`') + .chain([(message.len(), "")]) + .enumerate() + { + message_without_backticks.push_str(&message[prev_offset..offset]); + if inside_block { + highlights.extend(prev_offset - match_ix..offset - match_ix); + } + + inside_block = !inside_block; + prev_offset = offset + 1; + } + + (message_without_backticks, highlights) +} + +// pub fn diagnostic_style( +// severity: DiagnosticSeverity, +// valid: bool, +// theme: &theme::Editor, +// ) -> DiagnosticStyle { +// match (severity, valid) { +// (DiagnosticSeverity::ERROR, true) => theme.error_diagnostic.clone(), +// (DiagnosticSeverity::ERROR, false) => theme.invalid_error_diagnostic.clone(), +// (DiagnosticSeverity::WARNING, true) => theme.warning_diagnostic.clone(), +// (DiagnosticSeverity::WARNING, false) => theme.invalid_warning_diagnostic.clone(), +// (DiagnosticSeverity::INFORMATION, true) => theme.information_diagnostic.clone(), +// (DiagnosticSeverity::INFORMATION, false) => theme.invalid_information_diagnostic.clone(), +// (DiagnosticSeverity::HINT, true) => theme.hint_diagnostic.clone(), +// (DiagnosticSeverity::HINT, false) => theme.invalid_hint_diagnostic.clone(), +// _ => theme.invalid_hint_diagnostic.clone(), +// } +// } + +// pub fn combine_syntax_and_fuzzy_match_highlights( +// text: &str, +// default_style: HighlightStyle, +// syntax_ranges: impl Iterator, HighlightStyle)>, +// match_indices: &[usize], +// ) -> Vec<(Range, HighlightStyle)> { +// let mut result = Vec::new(); +// let mut match_indices = match_indices.iter().copied().peekable(); + +// for (range, mut syntax_highlight) in syntax_ranges.chain([(usize::MAX..0, Default::default())]) +// { +// syntax_highlight.weight = None; + +// // Add highlights for any fuzzy match characters before the next +// // syntax highlight range. +// while let Some(&match_index) = match_indices.peek() { +// if match_index >= range.start { +// break; +// } +// match_indices.next(); +// let end_index = char_ix_after(match_index, text); +// let mut match_style = default_style; +// match_style.weight = Some(FontWeight::BOLD); +// result.push((match_index..end_index, match_style)); +// } + +// if range.start == usize::MAX { +// break; +// } + +// // Add highlights for any fuzzy match characters within the +// // syntax highlight range. +// let mut offset = range.start; +// while let Some(&match_index) = match_indices.peek() { +// if match_index >= range.end { +// break; +// } + +// match_indices.next(); +// if match_index > offset { +// result.push((offset..match_index, syntax_highlight)); +// } + +// let mut end_index = char_ix_after(match_index, text); +// while let Some(&next_match_index) = match_indices.peek() { +// if next_match_index == end_index && next_match_index < range.end { +// end_index = char_ix_after(next_match_index, text); +// match_indices.next(); +// } else { +// break; +// } +// } + +// let mut match_style = syntax_highlight; +// match_style.weight = Some(FontWeight::BOLD); +// result.push((match_index..end_index, match_style)); +// offset = end_index; +// } + +// if offset < range.end { +// result.push((offset..range.end, syntax_highlight)); +// } +// } + +// fn char_ix_after(ix: usize, text: &str) -> usize { +// ix + text[ix..].chars().next().unwrap().len_utf8() +// } + +// result +// } + +// pub fn styled_runs_for_code_label<'a>( +// label: &'a CodeLabel, +// syntax_theme: &'a theme::SyntaxTheme, +// ) -> impl 'a + Iterator, HighlightStyle)> { +// let fade_out = HighlightStyle { +// fade_out: Some(0.35), +// ..Default::default() +// }; + +// let mut prev_end = label.filter_range.end; +// label +// .runs +// .iter() +// .enumerate() +// .flat_map(move |(ix, (range, highlight_id))| { +// let style = if let Some(style) = highlight_id.style(syntax_theme) { +// style +// } else { +// return Default::default(); +// }; +// let mut muted_style = style; +// muted_style.highlight(fade_out); + +// let mut runs = SmallVec::<[(Range, HighlightStyle); 3]>::new(); +// if range.start >= label.filter_range.end { +// if range.start > prev_end { +// runs.push((prev_end..range.start, fade_out)); +// } +// runs.push((range.clone(), muted_style)); +// } else if range.end <= label.filter_range.end { +// runs.push((range.clone(), style)); +// } else { +// runs.push((range.start..label.filter_range.end, style)); +// runs.push((label.filter_range.end..range.end, muted_style)); +// } +// prev_end = cmp::max(prev_end, range.end); + +// if ix + 1 == label.runs.len() && label.text.len() > prev_end { +// runs.push((prev_end..label.text.len(), fade_out)); +// } + +// runs +// }) + +pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator + 'a { + let mut index = 0; + let mut codepoints = text.char_indices().peekable(); + + std::iter::from_fn(move || { + let start_index = index; + while let Some((new_index, codepoint)) = codepoints.next() { + index = new_index + codepoint.len_utf8(); + let current_upper = codepoint.is_uppercase(); + let next_upper = codepoints + .peek() + .map(|(_, c)| c.is_uppercase()) + .unwrap_or(false); + + if !current_upper && next_upper { + return Some(&text[start_index..index]); + } + } + + index = text.len(); + if start_index < text.len() { + return Some(&text[start_index..]); + } + None + }) + .flat_map(|word| word.split_inclusive('_')) + .flat_map(|word| word.split_inclusive('-')) +} + +trait RangeToAnchorExt { + fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range; +} + +impl RangeToAnchorExt for Range { + fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range { + snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end) + } +} diff --git a/crates/editor2/src/editor_settings.rs b/crates/editor2/src/editor_settings.rs new file mode 100644 index 0000000000..45c797598f --- /dev/null +++ b/crates/editor2/src/editor_settings.rs @@ -0,0 +1,62 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::Settings; + +#[derive(Deserialize)] +pub struct EditorSettings { + pub cursor_blink: bool, + pub hover_popover_enabled: bool, + pub show_completions_on_input: bool, + pub show_completion_documentation: bool, + pub use_on_type_format: bool, + pub scrollbar: Scrollbar, + pub relative_line_numbers: bool, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct Scrollbar { + pub show: ShowScrollbar, + pub git_diff: bool, + pub selections: bool, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ShowScrollbar { + Auto, + System, + Always, + Never, +} + +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +pub struct EditorSettingsContent { + pub cursor_blink: Option, + pub hover_popover_enabled: Option, + pub show_completions_on_input: Option, + pub show_completion_documentation: Option, + pub use_on_type_format: Option, + pub scrollbar: Option, + pub relative_line_numbers: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct ScrollbarContent { + pub show: Option, + pub git_diff: Option, + pub selections: Option, +} + +impl Settings for EditorSettings { + const KEY: Option<&'static str> = None; + + type FileContent = EditorSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &mut gpui::AppContext, + ) -> anyhow::Result { + Self::load_via_json_merge(default_value, user_values) + } +} diff --git a/crates/editor2/src/editor_tests.rs b/crates/editor2/src/editor_tests.rs new file mode 100644 index 0000000000..5b5a40ba8e --- /dev/null +++ b/crates/editor2/src/editor_tests.rs @@ -0,0 +1,8191 @@ +// use super::*; +// use crate::{ +// scroll::scroll_amount::ScrollAmount, +// test::{ +// assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext, +// editor_test_context::EditorTestContext, select_ranges, +// }, +// JoinLines, +// }; +// use drag_and_drop::DragAndDrop; +// use futures::StreamExt; +// use gpui::{ +// executor::Deterministic, +// geometry::{rect::RectF, vector::vec2f}, +// platform::{WindowBounds, WindowOptions}, +// serde_json::{self, json}, +// TestAppContext, +// }; +// use indoc::indoc; +// use language::{ +// language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent}, +// BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry, +// Override, Point, +// }; +// use parking_lot::Mutex; +// use project::project_settings::{LspSettings, ProjectSettings}; +// use project::FakeFs; +// use std::sync::atomic; +// use std::sync::atomic::AtomicUsize; +// use std::{cell::RefCell, future::Future, rc::Rc, time::Instant}; +// use unindent::Unindent; +// use util::{ +// assert_set_eq, +// test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker}, +// }; +// use workspace::{ +// item::{FollowableItem, Item, ItemHandle}, +// NavigationEntry, ViewId, +// }; + +// #[gpui::test] +// fn test_edit_events(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let buffer = cx.add_model(|cx| { +// let mut buffer = language::Buffer::new(0, cx.model_id() as u64, "123456"); +// buffer.set_group_interval(Duration::from_secs(1)); +// buffer +// }); + +// let events = Rc::new(RefCell::new(Vec::new())); +// let editor1 = cx +// .add_window({ +// let events = events.clone(); +// |cx| { +// cx.subscribe(&cx.handle(), move |_, _, event, _| { +// if matches!( +// event, +// Event::Edited | Event::BufferEdited | Event::DirtyChanged +// ) { +// events.borrow_mut().push(("editor1", event.clone())); +// } +// }) +// .detach(); +// Editor::for_buffer(buffer.clone(), None, cx) +// } +// }) +// .root(cx); +// let editor2 = cx +// .add_window({ +// let events = events.clone(); +// |cx| { +// cx.subscribe(&cx.handle(), move |_, _, event, _| { +// if matches!( +// event, +// Event::Edited | Event::BufferEdited | Event::DirtyChanged +// ) { +// events.borrow_mut().push(("editor2", event.clone())); +// } +// }) +// .detach(); +// Editor::for_buffer(buffer.clone(), None, cx) +// } +// }) +// .root(cx); +// assert_eq!(mem::take(&mut *events.borrow_mut()), []); + +// // Mutating editor 1 will emit an `Edited` event only for that editor. +// editor1.update(cx, |editor, cx| editor.insert("X", cx)); +// assert_eq!( +// mem::take(&mut *events.borrow_mut()), +// [ +// ("editor1", Event::Edited), +// ("editor1", Event::BufferEdited), +// ("editor2", Event::BufferEdited), +// ("editor1", Event::DirtyChanged), +// ("editor2", Event::DirtyChanged) +// ] +// ); + +// // Mutating editor 2 will emit an `Edited` event only for that editor. +// editor2.update(cx, |editor, cx| editor.delete(&Delete, cx)); +// assert_eq!( +// mem::take(&mut *events.borrow_mut()), +// [ +// ("editor2", Event::Edited), +// ("editor1", Event::BufferEdited), +// ("editor2", Event::BufferEdited), +// ] +// ); + +// // Undoing on editor 1 will emit an `Edited` event only for that editor. +// editor1.update(cx, |editor, cx| editor.undo(&Undo, cx)); +// assert_eq!( +// mem::take(&mut *events.borrow_mut()), +// [ +// ("editor1", Event::Edited), +// ("editor1", Event::BufferEdited), +// ("editor2", Event::BufferEdited), +// ("editor1", Event::DirtyChanged), +// ("editor2", Event::DirtyChanged), +// ] +// ); + +// // Redoing on editor 1 will emit an `Edited` event only for that editor. +// editor1.update(cx, |editor, cx| editor.redo(&Redo, cx)); +// assert_eq!( +// mem::take(&mut *events.borrow_mut()), +// [ +// ("editor1", Event::Edited), +// ("editor1", Event::BufferEdited), +// ("editor2", Event::BufferEdited), +// ("editor1", Event::DirtyChanged), +// ("editor2", Event::DirtyChanged), +// ] +// ); + +// // Undoing on editor 2 will emit an `Edited` event only for that editor. +// editor2.update(cx, |editor, cx| editor.undo(&Undo, cx)); +// assert_eq!( +// mem::take(&mut *events.borrow_mut()), +// [ +// ("editor2", Event::Edited), +// ("editor1", Event::BufferEdited), +// ("editor2", Event::BufferEdited), +// ("editor1", Event::DirtyChanged), +// ("editor2", Event::DirtyChanged), +// ] +// ); + +// // Redoing on editor 2 will emit an `Edited` event only for that editor. +// editor2.update(cx, |editor, cx| editor.redo(&Redo, cx)); +// assert_eq!( +// mem::take(&mut *events.borrow_mut()), +// [ +// ("editor2", Event::Edited), +// ("editor1", Event::BufferEdited), +// ("editor2", Event::BufferEdited), +// ("editor1", Event::DirtyChanged), +// ("editor2", Event::DirtyChanged), +// ] +// ); + +// // No event is emitted when the mutation is a no-op. +// editor2.update(cx, |editor, cx| { +// editor.change_selections(None, cx, |s| s.select_ranges([0..0])); + +// editor.backspace(&Backspace, cx); +// }); +// assert_eq!(mem::take(&mut *events.borrow_mut()), []); +// } + +// #[gpui::test] +// fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let mut now = Instant::now(); +// let buffer = cx.add_model(|cx| language::Buffer::new(0, cx.model_id() as u64, "123456")); +// let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval()); +// let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); +// let editor = cx +// .add_window(|cx| build_editor(buffer.clone(), cx)) +// .root(cx); + +// editor.update(cx, |editor, cx| { +// editor.start_transaction_at(now, cx); +// editor.change_selections(None, cx, |s| s.select_ranges([2..4])); + +// editor.insert("cd", cx); +// editor.end_transaction_at(now, cx); +// assert_eq!(editor.text(cx), "12cd56"); +// assert_eq!(editor.selections.ranges(cx), vec![4..4]); + +// editor.start_transaction_at(now, cx); +// editor.change_selections(None, cx, |s| s.select_ranges([4..5])); +// editor.insert("e", cx); +// editor.end_transaction_at(now, cx); +// assert_eq!(editor.text(cx), "12cde6"); +// assert_eq!(editor.selections.ranges(cx), vec![5..5]); + +// now += group_interval + Duration::from_millis(1); +// editor.change_selections(None, cx, |s| s.select_ranges([2..2])); + +// // Simulate an edit in another editor +// buffer.update(cx, |buffer, cx| { +// buffer.start_transaction_at(now, cx); +// buffer.edit([(0..1, "a")], None, cx); +// buffer.edit([(1..1, "b")], None, cx); +// buffer.end_transaction_at(now, cx); +// }); + +// assert_eq!(editor.text(cx), "ab2cde6"); +// assert_eq!(editor.selections.ranges(cx), vec![3..3]); + +// // Last transaction happened past the group interval in a different editor. +// // Undo it individually and don't restore selections. +// editor.undo(&Undo, cx); +// assert_eq!(editor.text(cx), "12cde6"); +// assert_eq!(editor.selections.ranges(cx), vec![2..2]); + +// // First two transactions happened within the group interval in this editor. +// // Undo them together and restore selections. +// editor.undo(&Undo, cx); +// editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op. +// assert_eq!(editor.text(cx), "123456"); +// assert_eq!(editor.selections.ranges(cx), vec![0..0]); + +// // Redo the first two transactions together. +// editor.redo(&Redo, cx); +// assert_eq!(editor.text(cx), "12cde6"); +// assert_eq!(editor.selections.ranges(cx), vec![5..5]); + +// // Redo the last transaction on its own. +// editor.redo(&Redo, cx); +// assert_eq!(editor.text(cx), "ab2cde6"); +// assert_eq!(editor.selections.ranges(cx), vec![6..6]); + +// // Test empty transactions. +// editor.start_transaction_at(now, cx); +// editor.end_transaction_at(now, cx); +// editor.undo(&Undo, cx); +// assert_eq!(editor.text(cx), "12cde6"); +// }); +// } + +// #[gpui::test] +// fn test_ime_composition(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let buffer = cx.add_model(|cx| { +// let mut buffer = language::Buffer::new(0, cx.model_id() as u64, "abcde"); +// // Ensure automatic grouping doesn't occur. +// buffer.set_group_interval(Duration::ZERO); +// buffer +// }); + +// let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); +// cx.add_window(|cx| { +// let mut editor = build_editor(buffer.clone(), cx); + +// // Start a new IME composition. +// editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx); +// editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx); +// editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx); +// assert_eq!(editor.text(cx), "äbcde"); +// assert_eq!( +// editor.marked_text_ranges(cx), +// Some(vec![OffsetUtf16(0)..OffsetUtf16(1)]) +// ); + +// // Finalize IME composition. +// editor.replace_text_in_range(None, "ā", cx); +// assert_eq!(editor.text(cx), "ābcde"); +// assert_eq!(editor.marked_text_ranges(cx), None); + +// // IME composition edits are grouped and are undone/redone at once. +// editor.undo(&Default::default(), cx); +// assert_eq!(editor.text(cx), "abcde"); +// assert_eq!(editor.marked_text_ranges(cx), None); +// editor.redo(&Default::default(), cx); +// assert_eq!(editor.text(cx), "ābcde"); +// assert_eq!(editor.marked_text_ranges(cx), None); + +// // Start a new IME composition. +// editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx); +// assert_eq!( +// editor.marked_text_ranges(cx), +// Some(vec![OffsetUtf16(0)..OffsetUtf16(1)]) +// ); + +// // Undoing during an IME composition cancels it. +// editor.undo(&Default::default(), cx); +// assert_eq!(editor.text(cx), "ābcde"); +// assert_eq!(editor.marked_text_ranges(cx), None); + +// // Start a new IME composition with an invalid marked range, ensuring it gets clipped. +// editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx); +// assert_eq!(editor.text(cx), "ābcdè"); +// assert_eq!( +// editor.marked_text_ranges(cx), +// Some(vec![OffsetUtf16(4)..OffsetUtf16(5)]) +// ); + +// // Finalize IME composition with an invalid replacement range, ensuring it gets clipped. +// editor.replace_text_in_range(Some(4..999), "ę", cx); +// assert_eq!(editor.text(cx), "ābcdę"); +// assert_eq!(editor.marked_text_ranges(cx), None); + +// // Start a new IME composition with multiple cursors. +// editor.change_selections(None, cx, |s| { +// s.select_ranges([ +// OffsetUtf16(1)..OffsetUtf16(1), +// OffsetUtf16(3)..OffsetUtf16(3), +// OffsetUtf16(5)..OffsetUtf16(5), +// ]) +// }); +// editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx); +// assert_eq!(editor.text(cx), "XYZbXYZdXYZ"); +// assert_eq!( +// editor.marked_text_ranges(cx), +// Some(vec![ +// OffsetUtf16(0)..OffsetUtf16(3), +// OffsetUtf16(4)..OffsetUtf16(7), +// OffsetUtf16(8)..OffsetUtf16(11) +// ]) +// ); + +// // Ensure the newly-marked range gets treated as relative to the previously-marked ranges. +// editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx); +// assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z"); +// assert_eq!( +// editor.marked_text_ranges(cx), +// Some(vec![ +// OffsetUtf16(1)..OffsetUtf16(2), +// OffsetUtf16(5)..OffsetUtf16(6), +// OffsetUtf16(9)..OffsetUtf16(10) +// ]) +// ); + +// // Finalize IME composition with multiple cursors. +// editor.replace_text_in_range(Some(9..10), "2", cx); +// assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z"); +// assert_eq!(editor.marked_text_ranges(cx), None); + +// editor +// }); +// } + +// #[gpui::test] +// fn test_selection_with_mouse(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let editor = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx); +// build_editor(buffer, cx) +// }) +// .root(cx); +// editor.update(cx, |view, cx| { +// view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx); +// }); +// assert_eq!( +// editor.update(cx, |view, cx| view.selections.display_ranges(cx)), +// [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)] +// ); + +// editor.update(cx, |view, cx| { +// view.update_selection(DisplayPoint::new(3, 3), 0, Point::zero(), cx); +// }); + +// assert_eq!( +// editor.update(cx, |view, cx| view.selections.display_ranges(cx)), +// [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)] +// ); + +// editor.update(cx, |view, cx| { +// view.update_selection(DisplayPoint::new(1, 1), 0, Point::zero(), cx); +// }); + +// assert_eq!( +// editor.update(cx, |view, cx| view.selections.display_ranges(cx)), +// [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)] +// ); + +// editor.update(cx, |view, cx| { +// view.end_selection(cx); +// view.update_selection(DisplayPoint::new(3, 3), 0, Point::zero(), cx); +// }); + +// assert_eq!( +// editor.update(cx, |view, cx| view.selections.display_ranges(cx)), +// [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)] +// ); + +// editor.update(cx, |view, cx| { +// view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx); +// view.update_selection(DisplayPoint::new(0, 0), 0, Point::zero(), cx); +// }); + +// assert_eq!( +// editor.update(cx, |view, cx| view.selections.display_ranges(cx)), +// [ +// DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1), +// DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0) +// ] +// ); + +// editor.update(cx, |view, cx| { +// view.end_selection(cx); +// }); + +// assert_eq!( +// editor.update(cx, |view, cx| view.selections.display_ranges(cx)), +// [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)] +// ); +// } + +// #[gpui::test] +// fn test_canceling_pending_selection(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx); +// build_editor(buffer, cx) +// }) +// .root(cx); + +// view.update(cx, |view, cx| { +// view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.update_selection(DisplayPoint::new(3, 3), 0, Point::zero(), cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.cancel(&Cancel, cx); +// view.update_selection(DisplayPoint::new(1, 1), 0, Point::zero(), cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)] +// ); +// }); +// } + +// #[gpui::test] +// fn test_clone(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let (text, selection_ranges) = marked_text_ranges( +// indoc! {" +// one +// two +// threeˇ +// four +// fiveˇ +// "}, +// true, +// ); + +// let editor = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple(&text, cx); +// build_editor(buffer, cx) +// }) +// .root(cx); + +// editor.update(cx, |editor, cx| { +// editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone())); +// editor.fold_ranges( +// [ +// Point::new(1, 0)..Point::new(2, 0), +// Point::new(3, 0)..Point::new(4, 0), +// ], +// true, +// cx, +// ); +// }); + +// let cloned_editor = editor +// .update(cx, |editor, cx| { +// cx.add_window(Default::default(), |cx| editor.clone(cx)) +// }) +// .root(cx); + +// let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)); +// let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)); + +// assert_eq!( +// cloned_editor.update(cx, |e, cx| e.display_text(cx)), +// editor.update(cx, |e, cx| e.display_text(cx)) +// ); +// assert_eq!( +// cloned_snapshot +// .folds_in_range(0..text.len()) +// .collect::>(), +// snapshot.folds_in_range(0..text.len()).collect::>(), +// ); +// assert_set_eq!( +// cloned_editor.read_with(cx, |editor, cx| editor.selections.ranges::(cx)), +// editor.read_with(cx, |editor, cx| editor.selections.ranges(cx)) +// ); +// assert_set_eq!( +// cloned_editor.update(cx, |e, cx| e.selections.display_ranges(cx)), +// editor.update(cx, |e, cx| e.selections.display_ranges(cx)) +// ); +// } + +// #[gpui::test] +// async fn test_navigation_history(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// cx.set_global(DragAndDrop::::default()); +// use workspace::item::Item; + +// let fs = FakeFs::new(cx.background()); +// let project = Project::test(fs, [], cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project, cx)); +// let workspace = window.root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); +// window.add_view(cx, |cx| { +// let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx); +// let mut editor = build_editor(buffer.clone(), cx); +// let handle = cx.handle(); +// editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle))); + +// fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option { +// editor.nav_history.as_mut().unwrap().pop_backward(cx) +// } + +// // Move the cursor a small distance. +// // Nothing is added to the navigation history. +// editor.change_selections(None, cx, |s| { +// s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]) +// }); +// editor.change_selections(None, cx, |s| { +// s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]) +// }); +// assert!(pop_history(&mut editor, cx).is_none()); + +// // Move the cursor a large distance. +// // The history can jump back to the previous position. +// editor.change_selections(None, cx, |s| { +// s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)]) +// }); +// let nav_entry = pop_history(&mut editor, cx).unwrap(); +// editor.navigate(nav_entry.data.unwrap(), cx); +// assert_eq!(nav_entry.item.id(), cx.view_id()); +// assert_eq!( +// editor.selections.display_ranges(cx), +// &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)] +// ); +// assert!(pop_history(&mut editor, cx).is_none()); + +// // Move the cursor a small distance via the mouse. +// // Nothing is added to the navigation history. +// editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx); +// editor.end_selection(cx); +// assert_eq!( +// editor.selections.display_ranges(cx), +// &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)] +// ); +// assert!(pop_history(&mut editor, cx).is_none()); + +// // Move the cursor a large distance via the mouse. +// // The history can jump back to the previous position. +// editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx); +// editor.end_selection(cx); +// assert_eq!( +// editor.selections.display_ranges(cx), +// &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)] +// ); +// let nav_entry = pop_history(&mut editor, cx).unwrap(); +// editor.navigate(nav_entry.data.unwrap(), cx); +// assert_eq!(nav_entry.item.id(), cx.view_id()); +// assert_eq!( +// editor.selections.display_ranges(cx), +// &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)] +// ); +// assert!(pop_history(&mut editor, cx).is_none()); + +// // Set scroll position to check later +// editor.set_scroll_position(Point::new(5.5, 5.5), cx); +// let original_scroll_position = editor.scroll_manager.anchor(); + +// // Jump to the end of the document and adjust scroll +// editor.move_to_end(&MoveToEnd, cx); +// editor.set_scroll_position(Point::new(-2.5, -0.5), cx); +// assert_ne!(editor.scroll_manager.anchor(), original_scroll_position); + +// let nav_entry = pop_history(&mut editor, cx).unwrap(); +// editor.navigate(nav_entry.data.unwrap(), cx); +// assert_eq!(editor.scroll_manager.anchor(), original_scroll_position); + +// // Ensure we don't panic when navigation data contains invalid anchors *and* points. +// let mut invalid_anchor = editor.scroll_manager.anchor().anchor; +// invalid_anchor.text_anchor.buffer_id = Some(999); +// let invalid_point = Point::new(9999, 0); +// editor.navigate( +// Box::new(NavigationData { +// cursor_anchor: invalid_anchor, +// cursor_position: invalid_point, +// scroll_anchor: ScrollAnchor { +// anchor: invalid_anchor, +// offset: Default::default(), +// }, +// scroll_top_row: invalid_point.row, +// }), +// cx, +// ); +// assert_eq!( +// editor.selections.display_ranges(cx), +// &[editor.max_point(cx)..editor.max_point(cx)] +// ); +// assert_eq!( +// editor.scroll_position(cx), +// vec2f(0., editor.max_point(cx).row() as f32) +// ); + +// editor +// }); +// } + +// #[gpui::test] +// fn test_cancel(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx); +// build_editor(buffer, cx) +// }) +// .root(cx); + +// view.update(cx, |view, cx| { +// view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx); +// view.update_selection(DisplayPoint::new(1, 1), 0, Point::zero(), cx); +// view.end_selection(cx); + +// view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx); +// view.update_selection(DisplayPoint::new(0, 3), 0, Point::zero(), cx); +// view.end_selection(cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// [ +// DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3), +// DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1), +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.cancel(&Cancel, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.cancel(&Cancel, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)] +// ); +// }); +// } + +// #[gpui::test] +// fn test_fold_action(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple( +// &" +// impl Foo { +// // Hello! + +// fn a() { +// 1 +// } + +// fn b() { +// 2 +// } + +// fn c() { +// 3 +// } +// } +// " +// .unindent(), +// cx, +// ); +// build_editor(buffer.clone(), cx) +// }) +// .root(cx); + +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)]); +// }); +// view.fold(&Fold, cx); +// assert_eq!( +// view.display_text(cx), +// " +// impl Foo { +// // Hello! + +// fn a() { +// 1 +// } + +// fn b() {⋯ +// } + +// fn c() {⋯ +// } +// } +// " +// .unindent(), +// ); + +// view.fold(&Fold, cx); +// assert_eq!( +// view.display_text(cx), +// " +// impl Foo {⋯ +// } +// " +// .unindent(), +// ); + +// view.unfold_lines(&UnfoldLines, cx); +// assert_eq!( +// view.display_text(cx), +// " +// impl Foo { +// // Hello! + +// fn a() { +// 1 +// } + +// fn b() {⋯ +// } + +// fn c() {⋯ +// } +// } +// " +// .unindent(), +// ); + +// view.unfold_lines(&UnfoldLines, cx); +// assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text()); +// }); +// } + +// #[gpui::test] +// fn test_move_cursor(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx)); +// let view = cx +// .add_window(|cx| build_editor(buffer.clone(), cx)) +// .root(cx); + +// buffer.update(cx, |buffer, cx| { +// buffer.edit( +// vec![ +// (Point::new(1, 0)..Point::new(1, 0), "\t"), +// (Point::new(1, 1)..Point::new(1, 1), "\t"), +// ], +// None, +// cx, +// ); +// }); +// view.update(cx, |view, cx| { +// assert_eq!( +// view.selections.display_ranges(cx), +// &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)] +// ); + +// view.move_down(&MoveDown, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)] +// ); + +// view.move_right(&MoveRight, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)] +// ); + +// view.move_left(&MoveLeft, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)] +// ); + +// view.move_up(&MoveUp, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)] +// ); + +// view.move_to_end(&MoveToEnd, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)] +// ); + +// view.move_to_beginning(&MoveToBeginning, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)] +// ); + +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]); +// }); +// view.select_to_beginning(&SelectToBeginning, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)] +// ); + +// view.select_to_end(&SelectToEnd, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)] +// ); +// }); +// } + +// #[gpui::test] +// fn test_move_cursor_multibyte(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx); +// build_editor(buffer.clone(), cx) +// }) +// .root(cx); + +// assert_eq!('ⓐ'.len_utf8(), 3); +// assert_eq!('α'.len_utf8(), 2); + +// view.update(cx, |view, cx| { +// view.fold_ranges( +// vec![ +// Point::new(0, 6)..Point::new(0, 12), +// Point::new(1, 2)..Point::new(1, 4), +// Point::new(2, 4)..Point::new(2, 8), +// ], +// true, +// cx, +// ); +// assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε"); + +// view.move_right(&MoveRight, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(0, "ⓐ".len())] +// ); +// view.move_right(&MoveRight, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(0, "ⓐⓑ".len())] +// ); +// view.move_right(&MoveRight, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(0, "ⓐⓑ⋯".len())] +// ); + +// view.move_down(&MoveDown, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(1, "ab⋯e".len())] +// ); +// view.move_left(&MoveLeft, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(1, "ab⋯".len())] +// ); +// view.move_left(&MoveLeft, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(1, "ab".len())] +// ); +// view.move_left(&MoveLeft, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(1, "a".len())] +// ); + +// view.move_down(&MoveDown, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(2, "α".len())] +// ); +// view.move_right(&MoveRight, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(2, "αβ".len())] +// ); +// view.move_right(&MoveRight, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(2, "αβ⋯".len())] +// ); +// view.move_right(&MoveRight, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(2, "αβ⋯ε".len())] +// ); + +// view.move_up(&MoveUp, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(1, "ab⋯e".len())] +// ); +// view.move_down(&MoveDown, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(2, "αβ⋯ε".len())] +// ); +// view.move_up(&MoveUp, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(1, "ab⋯e".len())] +// ); + +// view.move_up(&MoveUp, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(0, "ⓐⓑ".len())] +// ); +// view.move_left(&MoveLeft, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(0, "ⓐ".len())] +// ); +// view.move_left(&MoveLeft, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(0, "".len())] +// ); +// }); +// } + +// #[gpui::test] +// fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx); +// build_editor(buffer.clone(), cx) +// }) +// .root(cx); +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]); +// }); +// view.move_down(&MoveDown, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(1, "abcd".len())] +// ); + +// view.move_down(&MoveDown, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(2, "αβγ".len())] +// ); + +// view.move_down(&MoveDown, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(3, "abcd".len())] +// ); + +// view.move_down(&MoveDown, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())] +// ); + +// view.move_up(&MoveUp, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(3, "abcd".len())] +// ); + +// view.move_up(&MoveUp, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[empty_range(2, "αβγ".len())] +// ); +// }); +// } + +// #[gpui::test] +// fn test_beginning_end_of_line(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("abc\n def", cx); +// build_editor(buffer, cx) +// }) +// .root(cx); +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([ +// DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), +// DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4), +// ]); +// }); +// }); + +// view.update(cx, |view, cx| { +// view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[ +// DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), +// DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2), +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[ +// DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), +// DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[ +// DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), +// DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2), +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.move_to_end_of_line(&MoveToEndOfLine, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[ +// DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), +// DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5), +// ] +// ); +// }); + +// // Moving to the end of line again is a no-op. +// view.update(cx, |view, cx| { +// view.move_to_end_of_line(&MoveToEndOfLine, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[ +// DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), +// DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5), +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.move_left(&MoveLeft, cx); +// view.select_to_beginning_of_line( +// &SelectToBeginningOfLine { +// stop_at_soft_wraps: true, +// }, +// cx, +// ); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[ +// DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0), +// DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2), +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.select_to_beginning_of_line( +// &SelectToBeginningOfLine { +// stop_at_soft_wraps: true, +// }, +// cx, +// ); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[ +// DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0), +// DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0), +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.select_to_beginning_of_line( +// &SelectToBeginningOfLine { +// stop_at_soft_wraps: true, +// }, +// cx, +// ); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[ +// DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0), +// DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2), +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.select_to_end_of_line( +// &SelectToEndOfLine { +// stop_at_soft_wraps: true, +// }, +// cx, +// ); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[ +// DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3), +// DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5), +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.delete_to_end_of_line(&DeleteToEndOfLine, cx); +// assert_eq!(view.display_text(cx), "ab\n de"); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[ +// DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), +// DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4), +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx); +// assert_eq!(view.display_text(cx), "\n"); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[ +// DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), +// DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), +// ] +// ); +// }); +// } + +// #[gpui::test] +// fn test_prev_next_word_boundary(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx); +// build_editor(buffer, cx) +// }) +// .root(cx); +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([ +// DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11), +// DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4), +// ]) +// }); + +// view.move_to_previous_word_start(&MoveToPreviousWordStart, cx); +// assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx); + +// view.move_to_previous_word_start(&MoveToPreviousWordStart, cx); +// assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx); + +// view.move_to_previous_word_start(&MoveToPreviousWordStart, cx); +// assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx); + +// view.move_to_previous_word_start(&MoveToPreviousWordStart, cx); +// assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx); + +// view.move_to_previous_word_start(&MoveToPreviousWordStart, cx); +// assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx); + +// view.move_to_next_word_end(&MoveToNextWordEnd, cx); +// assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx); + +// view.move_to_next_word_end(&MoveToNextWordEnd, cx); +// assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx); + +// view.move_to_next_word_end(&MoveToNextWordEnd, cx); +// assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx); + +// view.move_right(&MoveRight, cx); +// view.select_to_previous_word_start(&SelectToPreviousWordStart, cx); +// assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx); + +// view.select_to_previous_word_start(&SelectToPreviousWordStart, cx); +// assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx); + +// view.select_to_next_word_end(&SelectToNextWordEnd, cx); +// assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx); +// }); +// } + +// #[gpui::test] +// fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = +// MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx); +// build_editor(buffer, cx) +// }) +// .root(cx); + +// view.update(cx, |view, cx| { +// view.set_wrap_width(Some(140.), cx); +// assert_eq!( +// view.display_text(cx), +// "use one::{\n two::three::\n four::five\n};" +// ); + +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]); +// }); + +// view.move_to_next_word_end(&MoveToNextWordEnd, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)] +// ); + +// view.move_to_next_word_end(&MoveToNextWordEnd, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)] +// ); + +// view.move_to_next_word_end(&MoveToNextWordEnd, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)] +// ); + +// view.move_to_next_word_end(&MoveToNextWordEnd, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)] +// ); + +// view.move_to_previous_word_start(&MoveToPreviousWordStart, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)] +// ); + +// view.move_to_previous_word_start(&MoveToPreviousWordStart, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)] +// ); +// }); +// } + +// #[gpui::test] +// async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); +// let mut cx = EditorTestContext::new(cx).await; + +// let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache())); +// let window = cx.window; +// window.simulate_resize(vec2f(100., 4. * line_height), &mut cx); + +// cx.set_state( +// &r#"ˇone +// two + +// three +// fourˇ +// five + +// six"# +// .unindent(), +// ); + +// cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx)); +// cx.assert_editor_state( +// &r#"one +// two +// ˇ +// three +// four +// five +// ˇ +// six"# +// .unindent(), +// ); + +// cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx)); +// cx.assert_editor_state( +// &r#"one +// two + +// three +// four +// five +// ˇ +// sixˇ"# +// .unindent(), +// ); + +// cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx)); +// cx.assert_editor_state( +// &r#"one +// two + +// three +// four +// five + +// sixˇ"# +// .unindent(), +// ); + +// cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx)); +// cx.assert_editor_state( +// &r#"one +// two + +// three +// four +// five +// ˇ +// six"# +// .unindent(), +// ); + +// cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx)); +// cx.assert_editor_state( +// &r#"one +// two +// ˇ +// three +// four +// five + +// six"# +// .unindent(), +// ); + +// cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx)); +// cx.assert_editor_state( +// &r#"ˇone +// two + +// three +// four +// five + +// six"# +// .unindent(), +// ); +// } + +// #[gpui::test] +// async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); +// let mut cx = EditorTestContext::new(cx).await; +// let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache())); +// let window = cx.window; +// window.simulate_resize(vec2f(1000., 4. * line_height + 0.5), &mut cx); + +// cx.set_state( +// &r#"ˇone +// two +// three +// four +// five +// six +// seven +// eight +// nine +// ten +// "#, +// ); + +// cx.update_editor(|editor, cx| { +// assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.)); +// editor.scroll_screen(&ScrollAmount::Page(1.), cx); +// assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.)); +// editor.scroll_screen(&ScrollAmount::Page(1.), cx); +// assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 6.)); +// editor.scroll_screen(&ScrollAmount::Page(-1.), cx); +// assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.)); + +// editor.scroll_screen(&ScrollAmount::Page(-0.5), cx); +// assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 1.)); +// editor.scroll_screen(&ScrollAmount::Page(0.5), cx); +// assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.)); +// }); +// } + +// #[gpui::test] +// async fn test_autoscroll(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); +// let mut cx = EditorTestContext::new(cx).await; + +// let line_height = cx.update_editor(|editor, cx| { +// editor.set_vertical_scroll_margin(2, cx); +// editor.style(cx).text.line_height(cx.font_cache()) +// }); + +// let window = cx.window; +// window.simulate_resize(vec2f(1000., 6.0 * line_height), &mut cx); + +// cx.set_state( +// &r#"ˇone +// two +// three +// four +// five +// six +// seven +// eight +// nine +// ten +// "#, +// ); +// cx.update_editor(|editor, cx| { +// assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.0)); +// }); + +// // Add a cursor below the visible area. Since both cursors cannot fit +// // on screen, the editor autoscrolls to reveal the newest cursor, and +// // allows the vertical scroll margin below that cursor. +// cx.update_editor(|editor, cx| { +// editor.change_selections(Some(Autoscroll::fit()), cx, |selections| { +// selections.select_ranges([ +// Point::new(0, 0)..Point::new(0, 0), +// Point::new(6, 0)..Point::new(6, 0), +// ]); +// }) +// }); +// cx.update_editor(|editor, cx| { +// assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.0)); +// }); + +// // Move down. The editor cursor scrolls down to track the newest cursor. +// cx.update_editor(|editor, cx| { +// editor.move_down(&Default::default(), cx); +// }); +// cx.update_editor(|editor, cx| { +// assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 4.0)); +// }); + +// // Add a cursor above the visible area. Since both cursors fit on screen, +// // the editor scrolls to show both. +// cx.update_editor(|editor, cx| { +// editor.change_selections(Some(Autoscroll::fit()), cx, |selections| { +// selections.select_ranges([ +// Point::new(1, 0)..Point::new(1, 0), +// Point::new(6, 0)..Point::new(6, 0), +// ]); +// }) +// }); +// cx.update_editor(|editor, cx| { +// assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 1.0)); +// }); +// } + +// #[gpui::test] +// async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); +// let mut cx = EditorTestContext::new(cx).await; + +// let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache())); +// let window = cx.window; +// window.simulate_resize(vec2f(100., 4. * line_height), &mut cx); + +// cx.set_state( +// &r#" +// ˇone +// two +// threeˇ +// four +// five +// six +// seven +// eight +// nine +// ten +// "# +// .unindent(), +// ); + +// cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx)); +// cx.assert_editor_state( +// &r#" +// one +// two +// three +// ˇfour +// five +// sixˇ +// seven +// eight +// nine +// ten +// "# +// .unindent(), +// ); + +// cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx)); +// cx.assert_editor_state( +// &r#" +// one +// two +// three +// four +// five +// six +// ˇseven +// eight +// nineˇ +// ten +// "# +// .unindent(), +// ); + +// cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx)); +// cx.assert_editor_state( +// &r#" +// one +// two +// three +// ˇfour +// five +// sixˇ +// seven +// eight +// nine +// ten +// "# +// .unindent(), +// ); + +// cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx)); +// cx.assert_editor_state( +// &r#" +// ˇone +// two +// threeˇ +// four +// five +// six +// seven +// eight +// nine +// ten +// "# +// .unindent(), +// ); + +// // Test select collapsing +// cx.update_editor(|editor, cx| { +// editor.move_page_down(&MovePageDown::default(), cx); +// editor.move_page_down(&MovePageDown::default(), cx); +// editor.move_page_down(&MovePageDown::default(), cx); +// }); +// cx.assert_editor_state( +// &r#" +// one +// two +// three +// four +// five +// six +// seven +// eight +// nine +// ˇten +// ˇ"# +// .unindent(), +// ); +// } + +// #[gpui::test] +// async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); +// let mut cx = EditorTestContext::new(cx).await; +// cx.set_state("one «two threeˇ» four"); +// cx.update_editor(|editor, cx| { +// editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx); +// assert_eq!(editor.text(cx), " four"); +// }); +// } + +// #[gpui::test] +// fn test_delete_to_word_boundary(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("one two three four", cx); +// build_editor(buffer.clone(), cx) +// }) +// .root(cx); + +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([ +// // an empty selection - the preceding word fragment is deleted +// DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), +// // characters selected - they are deleted +// DisplayPoint::new(0, 9)..DisplayPoint::new(0, 12), +// ]) +// }); +// view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx); +// assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four"); +// }); + +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([ +// // an empty selection - the following word fragment is deleted +// DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), +// // characters selected - they are deleted +// DisplayPoint::new(0, 9)..DisplayPoint::new(0, 10), +// ]) +// }); +// view.delete_to_next_word_end(&DeleteToNextWordEnd, cx); +// assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our"); +// }); +// } + +// #[gpui::test] +// fn test_newline(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx); +// build_editor(buffer.clone(), cx) +// }) +// .root(cx); + +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([ +// DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), +// DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2), +// DisplayPoint::new(1, 6)..DisplayPoint::new(1, 6), +// ]) +// }); + +// view.newline(&Newline, cx); +// assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n"); +// }); +// } + +// #[gpui::test] +// fn test_newline_with_old_selections(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let editor = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple( +// " +// a +// b( +// X +// ) +// c( +// X +// ) +// " +// .unindent() +// .as_str(), +// cx, +// ); +// let mut editor = build_editor(buffer.clone(), cx); +// editor.change_selections(None, cx, |s| { +// s.select_ranges([ +// Point::new(2, 4)..Point::new(2, 5), +// Point::new(5, 4)..Point::new(5, 5), +// ]) +// }); +// editor +// }) +// .root(cx); + +// editor.update(cx, |editor, cx| { +// // Edit the buffer directly, deleting ranges surrounding the editor's selections +// editor.buffer.update(cx, |buffer, cx| { +// buffer.edit( +// [ +// (Point::new(1, 2)..Point::new(3, 0), ""), +// (Point::new(4, 2)..Point::new(6, 0), ""), +// ], +// None, +// cx, +// ); +// assert_eq!( +// buffer.read(cx).text(), +// " +// a +// b() +// c() +// " +// .unindent() +// ); +// }); +// assert_eq!( +// editor.selections.ranges(cx), +// &[ +// Point::new(1, 2)..Point::new(1, 2), +// Point::new(2, 2)..Point::new(2, 2), +// ], +// ); + +// editor.newline(&Newline, cx); +// assert_eq!( +// editor.text(cx), +// " +// a +// b( +// ) +// c( +// ) +// " +// .unindent() +// ); + +// // The selections are moved after the inserted newlines +// assert_eq!( +// editor.selections.ranges(cx), +// &[ +// Point::new(2, 0)..Point::new(2, 0), +// Point::new(4, 0)..Point::new(4, 0), +// ], +// ); +// }); +// } + +// #[gpui::test] +// async fn test_newline_above(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.tab_size = NonZeroU32::new(4) +// }); + +// let language = Arc::new( +// Language::new( +// LanguageConfig::default(), +// Some(tree_sitter_rust::language()), +// ) +// .with_indents_query(r#"(_ "(" ")" @end) @indent"#) +// .unwrap(), +// ); + +// let mut cx = EditorTestContext::new(cx).await; +// cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); +// cx.set_state(indoc! {" +// const a: ˇA = ( +// (ˇ +// «const_functionˇ»(ˇ), +// so«mˇ»et«hˇ»ing_ˇelse,ˇ +// )ˇ +// ˇ);ˇ +// "}); + +// cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx)); +// cx.assert_editor_state(indoc! {" +// ˇ +// const a: A = ( +// ˇ +// ( +// ˇ +// ˇ +// const_function(), +// ˇ +// ˇ +// ˇ +// ˇ +// something_else, +// ˇ +// ) +// ˇ +// ˇ +// ); +// "}); +// } + +// #[gpui::test] +// async fn test_newline_below(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.tab_size = NonZeroU32::new(4) +// }); + +// let language = Arc::new( +// Language::new( +// LanguageConfig::default(), +// Some(tree_sitter_rust::language()), +// ) +// .with_indents_query(r#"(_ "(" ")" @end) @indent"#) +// .unwrap(), +// ); + +// let mut cx = EditorTestContext::new(cx).await; +// cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); +// cx.set_state(indoc! {" +// const a: ˇA = ( +// (ˇ +// «const_functionˇ»(ˇ), +// so«mˇ»et«hˇ»ing_ˇelse,ˇ +// )ˇ +// ˇ);ˇ +// "}); + +// cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx)); +// cx.assert_editor_state(indoc! {" +// const a: A = ( +// ˇ +// ( +// ˇ +// const_function(), +// ˇ +// ˇ +// something_else, +// ˇ +// ˇ +// ˇ +// ˇ +// ) +// ˇ +// ); +// ˇ +// ˇ +// "}); +// } + +// #[gpui::test] +// async fn test_newline_comments(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.tab_size = NonZeroU32::new(4) +// }); + +// let language = Arc::new(Language::new( +// LanguageConfig { +// line_comment: Some("//".into()), +// ..LanguageConfig::default() +// }, +// None, +// )); +// { +// let mut cx = EditorTestContext::new(cx).await; +// cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); +// cx.set_state(indoc! {" +// // Fooˇ +// "}); + +// cx.update_editor(|e, cx| e.newline(&Newline, cx)); +// cx.assert_editor_state(indoc! {" +// // Foo +// //ˇ +// "}); +// // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix. +// cx.set_state(indoc! {" +// ˇ// Foo +// "}); +// cx.update_editor(|e, cx| e.newline(&Newline, cx)); +// cx.assert_editor_state(indoc! {" + +// ˇ// Foo +// "}); +// } +// // Ensure that comment continuations can be disabled. +// update_test_language_settings(cx, |settings| { +// settings.defaults.extend_comment_on_newline = Some(false); +// }); +// let mut cx = EditorTestContext::new(cx).await; +// cx.set_state(indoc! {" +// // Fooˇ +// "}); +// cx.update_editor(|e, cx| e.newline(&Newline, cx)); +// cx.assert_editor_state(indoc! {" +// // Foo +// ˇ +// "}); +// } + +// #[gpui::test] +// fn test_insert_with_old_selections(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let editor = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx); +// let mut editor = build_editor(buffer.clone(), cx); +// editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20])); +// editor +// }) +// .root(cx); + +// editor.update(cx, |editor, cx| { +// // Edit the buffer directly, deleting ranges surrounding the editor's selections +// editor.buffer.update(cx, |buffer, cx| { +// buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx); +// assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent()); +// }); +// assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],); + +// editor.insert("Z", cx); +// assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)"); + +// // The selections are moved after the inserted characters +// assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],); +// }); +// } + +// #[gpui::test] +// async fn test_tab(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.tab_size = NonZeroU32::new(3) +// }); + +// let mut cx = EditorTestContext::new(cx).await; +// cx.set_state(indoc! {" +// ˇabˇc +// ˇ🏀ˇ🏀ˇefg +// dˇ +// "}); +// cx.update_editor(|e, cx| e.tab(&Tab, cx)); +// cx.assert_editor_state(indoc! {" +// ˇab ˇc +// ˇ🏀 ˇ🏀 ˇefg +// d ˇ +// "}); + +// cx.set_state(indoc! {" +// a +// «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ» +// "}); +// cx.update_editor(|e, cx| e.tab(&Tab, cx)); +// cx.assert_editor_state(indoc! {" +// a +// «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ» +// "}); +// } + +// #[gpui::test] +// async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorTestContext::new(cx).await; +// let language = Arc::new( +// Language::new( +// LanguageConfig::default(), +// Some(tree_sitter_rust::language()), +// ) +// .with_indents_query(r#"(_ "(" ")" @end) @indent"#) +// .unwrap(), +// ); +// cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); + +// // cursors that are already at the suggested indent level insert +// // a soft tab. cursors that are to the left of the suggested indent +// // auto-indent their line. +// cx.set_state(indoc! {" +// ˇ +// const a: B = ( +// c( +// d( +// ˇ +// ) +// ˇ +// ˇ ) +// ); +// "}); +// cx.update_editor(|e, cx| e.tab(&Tab, cx)); +// cx.assert_editor_state(indoc! {" +// ˇ +// const a: B = ( +// c( +// d( +// ˇ +// ) +// ˇ +// ˇ) +// ); +// "}); + +// // handle auto-indent when there are multiple cursors on the same line +// cx.set_state(indoc! {" +// const a: B = ( +// c( +// ˇ ˇ +// ˇ ) +// ); +// "}); +// cx.update_editor(|e, cx| e.tab(&Tab, cx)); +// cx.assert_editor_state(indoc! {" +// const a: B = ( +// c( +// ˇ +// ˇ) +// ); +// "}); +// } + +// #[gpui::test] +// async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.tab_size = NonZeroU32::new(4) +// }); + +// let language = Arc::new( +// Language::new( +// LanguageConfig::default(), +// Some(tree_sitter_rust::language()), +// ) +// .with_indents_query(r#"(_ "{" "}" @end) @indent"#) +// .unwrap(), +// ); + +// let mut cx = EditorTestContext::new(cx).await; +// cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); +// cx.set_state(indoc! {" +// fn a() { +// if b { +// \t ˇc +// } +// } +// "}); + +// cx.update_editor(|e, cx| e.tab(&Tab, cx)); +// cx.assert_editor_state(indoc! {" +// fn a() { +// if b { +// ˇc +// } +// } +// "}); +// } + +// #[gpui::test] +// async fn test_indent_outdent(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.tab_size = NonZeroU32::new(4); +// }); + +// let mut cx = EditorTestContext::new(cx).await; + +// cx.set_state(indoc! {" +// «oneˇ» «twoˇ» +// three +// four +// "}); +// cx.update_editor(|e, cx| e.tab(&Tab, cx)); +// cx.assert_editor_state(indoc! {" +// «oneˇ» «twoˇ» +// three +// four +// "}); + +// cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); +// cx.assert_editor_state(indoc! {" +// «oneˇ» «twoˇ» +// three +// four +// "}); + +// // select across line ending +// cx.set_state(indoc! {" +// one two +// t«hree +// ˇ» four +// "}); +// cx.update_editor(|e, cx| e.tab(&Tab, cx)); +// cx.assert_editor_state(indoc! {" +// one two +// t«hree +// ˇ» four +// "}); + +// cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); +// cx.assert_editor_state(indoc! {" +// one two +// t«hree +// ˇ» four +// "}); + +// // Ensure that indenting/outdenting works when the cursor is at column 0. +// cx.set_state(indoc! {" +// one two +// ˇthree +// four +// "}); +// cx.update_editor(|e, cx| e.tab(&Tab, cx)); +// cx.assert_editor_state(indoc! {" +// one two +// ˇthree +// four +// "}); + +// cx.set_state(indoc! {" +// one two +// ˇ three +// four +// "}); +// cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); +// cx.assert_editor_state(indoc! {" +// one two +// ˇthree +// four +// "}); +// } + +// #[gpui::test] +// async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.hard_tabs = Some(true); +// }); + +// let mut cx = EditorTestContext::new(cx).await; + +// // select two ranges on one line +// cx.set_state(indoc! {" +// «oneˇ» «twoˇ» +// three +// four +// "}); +// cx.update_editor(|e, cx| e.tab(&Tab, cx)); +// cx.assert_editor_state(indoc! {" +// \t«oneˇ» «twoˇ» +// three +// four +// "}); +// cx.update_editor(|e, cx| e.tab(&Tab, cx)); +// cx.assert_editor_state(indoc! {" +// \t\t«oneˇ» «twoˇ» +// three +// four +// "}); +// cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); +// cx.assert_editor_state(indoc! {" +// \t«oneˇ» «twoˇ» +// three +// four +// "}); +// cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); +// cx.assert_editor_state(indoc! {" +// «oneˇ» «twoˇ» +// three +// four +// "}); + +// // select across a line ending +// cx.set_state(indoc! {" +// one two +// t«hree +// ˇ»four +// "}); +// cx.update_editor(|e, cx| e.tab(&Tab, cx)); +// cx.assert_editor_state(indoc! {" +// one two +// \tt«hree +// ˇ»four +// "}); +// cx.update_editor(|e, cx| e.tab(&Tab, cx)); +// cx.assert_editor_state(indoc! {" +// one two +// \t\tt«hree +// ˇ»four +// "}); +// cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); +// cx.assert_editor_state(indoc! {" +// one two +// \tt«hree +// ˇ»four +// "}); +// cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); +// cx.assert_editor_state(indoc! {" +// one two +// t«hree +// ˇ»four +// "}); + +// // Ensure that indenting/outdenting works when the cursor is at column 0. +// cx.set_state(indoc! {" +// one two +// ˇthree +// four +// "}); +// cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); +// cx.assert_editor_state(indoc! {" +// one two +// ˇthree +// four +// "}); +// cx.update_editor(|e, cx| e.tab(&Tab, cx)); +// cx.assert_editor_state(indoc! {" +// one two +// \tˇthree +// four +// "}); +// cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); +// cx.assert_editor_state(indoc! {" +// one two +// ˇthree +// four +// "}); +// } + +// #[gpui::test] +// fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) { +// init_test(cx, |settings| { +// settings.languages.extend([ +// ( +// "TOML".into(), +// LanguageSettingsContent { +// tab_size: NonZeroU32::new(2), +// ..Default::default() +// }, +// ), +// ( +// "Rust".into(), +// LanguageSettingsContent { +// tab_size: NonZeroU32::new(4), +// ..Default::default() +// }, +// ), +// ]); +// }); + +// let toml_language = Arc::new(Language::new( +// LanguageConfig { +// name: "TOML".into(), +// ..Default::default() +// }, +// None, +// )); +// let rust_language = Arc::new(Language::new( +// LanguageConfig { +// name: "Rust".into(), +// ..Default::default() +// }, +// None, +// )); + +// let toml_buffer = cx.add_model(|cx| { +// Buffer::new(0, cx.model_id() as u64, "a = 1\nb = 2\n").with_language(toml_language, cx) +// }); +// let rust_buffer = cx.add_model(|cx| { +// Buffer::new(0, cx.model_id() as u64, "const c: usize = 3;\n") +// .with_language(rust_language, cx) +// }); +// let multibuffer = cx.add_model(|cx| { +// let mut multibuffer = MultiBuffer::new(0); +// multibuffer.push_excerpts( +// toml_buffer.clone(), +// [ExcerptRange { +// context: Point::new(0, 0)..Point::new(2, 0), +// primary: None, +// }], +// cx, +// ); +// multibuffer.push_excerpts( +// rust_buffer.clone(), +// [ExcerptRange { +// context: Point::new(0, 0)..Point::new(1, 0), +// primary: None, +// }], +// cx, +// ); +// multibuffer +// }); + +// cx.add_window(|cx| { +// let mut editor = build_editor(multibuffer, cx); + +// assert_eq!( +// editor.text(cx), +// indoc! {" +// a = 1 +// b = 2 + +// const c: usize = 3; +// "} +// ); + +// select_ranges( +// &mut editor, +// indoc! {" +// «aˇ» = 1 +// b = 2 + +// «const c:ˇ» usize = 3; +// "}, +// cx, +// ); + +// editor.tab(&Tab, cx); +// assert_text_with_selections( +// &mut editor, +// indoc! {" +// «aˇ» = 1 +// b = 2 + +// «const c:ˇ» usize = 3; +// "}, +// cx, +// ); +// editor.tab_prev(&TabPrev, cx); +// assert_text_with_selections( +// &mut editor, +// indoc! {" +// «aˇ» = 1 +// b = 2 + +// «const c:ˇ» usize = 3; +// "}, +// cx, +// ); + +// editor +// }); +// } + +// #[gpui::test] +// async fn test_backspace(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorTestContext::new(cx).await; + +// // Basic backspace +// cx.set_state(indoc! {" +// onˇe two three +// fou«rˇ» five six +// seven «ˇeight nine +// »ten +// "}); +// cx.update_editor(|e, cx| e.backspace(&Backspace, cx)); +// cx.assert_editor_state(indoc! {" +// oˇe two three +// fouˇ five six +// seven ˇten +// "}); + +// // Test backspace inside and around indents +// cx.set_state(indoc! {" +// zero +// ˇone +// ˇtwo +// ˇ ˇ ˇ three +// ˇ ˇ four +// "}); +// cx.update_editor(|e, cx| e.backspace(&Backspace, cx)); +// cx.assert_editor_state(indoc! {" +// zero +// ˇone +// ˇtwo +// ˇ threeˇ four +// "}); + +// // Test backspace with line_mode set to true +// cx.update_editor(|e, _| e.selections.line_mode = true); +// cx.set_state(indoc! {" +// The ˇquick ˇbrown +// fox jumps over +// the lazy dog +// ˇThe qu«ick bˇ»rown"}); +// cx.update_editor(|e, cx| e.backspace(&Backspace, cx)); +// cx.assert_editor_state(indoc! {" +// ˇfox jumps over +// the lazy dogˇ"}); +// } + +// #[gpui::test] +// async fn test_delete(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorTestContext::new(cx).await; +// cx.set_state(indoc! {" +// onˇe two three +// fou«rˇ» five six +// seven «ˇeight nine +// »ten +// "}); +// cx.update_editor(|e, cx| e.delete(&Delete, cx)); +// cx.assert_editor_state(indoc! {" +// onˇ two three +// fouˇ five six +// seven ˇten +// "}); + +// // Test backspace with line_mode set to true +// cx.update_editor(|e, _| e.selections.line_mode = true); +// cx.set_state(indoc! {" +// The ˇquick ˇbrown +// fox «ˇjum»ps over +// the lazy dog +// ˇThe qu«ick bˇ»rown"}); +// cx.update_editor(|e, cx| e.backspace(&Backspace, cx)); +// cx.assert_editor_state("ˇthe lazy dogˇ"); +// } + +// #[gpui::test] +// fn test_delete_line(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); +// build_editor(buffer, cx) +// }) +// .root(cx); +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([ +// DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), +// DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1), +// DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0), +// ]) +// }); +// view.delete_line(&DeleteLine, cx); +// assert_eq!(view.display_text(cx), "ghi"); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), +// DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1) +// ] +// ); +// }); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); +// build_editor(buffer, cx) +// }) +// .root(cx); +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)]) +// }); +// view.delete_line(&DeleteLine, cx); +// assert_eq!(view.display_text(cx), "ghi\n"); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)] +// ); +// }); +// } + +// #[gpui::test] +// fn test_join_lines_with_single_selection(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// cx.add_window(|cx| { +// let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx); +// let mut editor = build_editor(buffer.clone(), cx); +// let buffer = buffer.read(cx).as_singleton().unwrap(); + +// assert_eq!( +// editor.selections.ranges::(cx), +// &[Point::new(0, 0)..Point::new(0, 0)] +// ); + +// // When on single line, replace newline at end by space +// editor.join_lines(&JoinLines, cx); +// assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n"); +// assert_eq!( +// editor.selections.ranges::(cx), +// &[Point::new(0, 3)..Point::new(0, 3)] +// ); + +// // When multiple lines are selected, remove newlines that are spanned by the selection +// editor.change_selections(None, cx, |s| { +// s.select_ranges([Point::new(0, 5)..Point::new(2, 2)]) +// }); +// editor.join_lines(&JoinLines, cx); +// assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n"); +// assert_eq!( +// editor.selections.ranges::(cx), +// &[Point::new(0, 11)..Point::new(0, 11)] +// ); + +// // Undo should be transactional +// editor.undo(&Undo, cx); +// assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n"); +// assert_eq!( +// editor.selections.ranges::(cx), +// &[Point::new(0, 5)..Point::new(2, 2)] +// ); + +// // When joining an empty line don't insert a space +// editor.change_selections(None, cx, |s| { +// s.select_ranges([Point::new(2, 1)..Point::new(2, 2)]) +// }); +// editor.join_lines(&JoinLines, cx); +// assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n"); +// assert_eq!( +// editor.selections.ranges::(cx), +// [Point::new(2, 3)..Point::new(2, 3)] +// ); + +// // We can remove trailing newlines +// editor.join_lines(&JoinLines, cx); +// assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd"); +// assert_eq!( +// editor.selections.ranges::(cx), +// [Point::new(2, 3)..Point::new(2, 3)] +// ); + +// // We don't blow up on the last line +// editor.join_lines(&JoinLines, cx); +// assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd"); +// assert_eq!( +// editor.selections.ranges::(cx), +// [Point::new(2, 3)..Point::new(2, 3)] +// ); + +// // reset to test indentation +// editor.buffer.update(cx, |buffer, cx| { +// buffer.edit( +// [ +// (Point::new(1, 0)..Point::new(1, 2), " "), +// (Point::new(2, 0)..Point::new(2, 3), " \n\td"), +// ], +// None, +// cx, +// ) +// }); + +// // We remove any leading spaces +// assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td"); +// editor.change_selections(None, cx, |s| { +// s.select_ranges([Point::new(0, 1)..Point::new(0, 1)]) +// }); +// editor.join_lines(&JoinLines, cx); +// assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td"); + +// // We don't insert a space for a line containing only spaces +// editor.join_lines(&JoinLines, cx); +// assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td"); + +// // We ignore any leading tabs +// editor.join_lines(&JoinLines, cx); +// assert_eq!(buffer.read(cx).text(), "aaa bbb c d"); + +// editor +// }); +// } + +// #[gpui::test] +// fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// cx.add_window(|cx| { +// let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx); +// let mut editor = build_editor(buffer.clone(), cx); +// let buffer = buffer.read(cx).as_singleton().unwrap(); + +// editor.change_selections(None, cx, |s| { +// s.select_ranges([ +// Point::new(0, 2)..Point::new(1, 1), +// Point::new(1, 2)..Point::new(1, 2), +// Point::new(3, 1)..Point::new(3, 2), +// ]) +// }); + +// editor.join_lines(&JoinLines, cx); +// assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n"); + +// assert_eq!( +// editor.selections.ranges::(cx), +// [ +// Point::new(0, 7)..Point::new(0, 7), +// Point::new(1, 3)..Point::new(1, 3) +// ] +// ); +// editor +// }); +// } + +// #[gpui::test] +// async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorTestContext::new(cx).await; + +// // Test sort_lines_case_insensitive() +// cx.set_state(indoc! {" +// «z +// y +// x +// Z +// Y +// Xˇ» +// "}); +// cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx)); +// cx.assert_editor_state(indoc! {" +// «x +// X +// y +// Y +// z +// Zˇ» +// "}); + +// // Test reverse_lines() +// cx.set_state(indoc! {" +// «5 +// 4 +// 3 +// 2 +// 1ˇ» +// "}); +// cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx)); +// cx.assert_editor_state(indoc! {" +// «1 +// 2 +// 3 +// 4 +// 5ˇ» +// "}); + +// // Skip testing shuffle_line() + +// // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive() +// // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines) + +// // Don't manipulate when cursor is on single line, but expand the selection +// cx.set_state(indoc! {" +// ddˇdd +// ccc +// bb +// a +// "}); +// cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx)); +// cx.assert_editor_state(indoc! {" +// «ddddˇ» +// ccc +// bb +// a +// "}); + +// // Basic manipulate case +// // Start selection moves to column 0 +// // End of selection shrinks to fit shorter line +// cx.set_state(indoc! {" +// dd«d +// ccc +// bb +// aaaaaˇ» +// "}); +// cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx)); +// cx.assert_editor_state(indoc! {" +// «aaaaa +// bb +// ccc +// dddˇ» +// "}); + +// // Manipulate case with newlines +// cx.set_state(indoc! {" +// dd«d +// ccc + +// bb +// aaaaa + +// ˇ» +// "}); +// cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx)); +// cx.assert_editor_state(indoc! {" +// « + +// aaaaa +// bb +// ccc +// dddˇ» + +// "}); +// } + +// #[gpui::test] +// async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorTestContext::new(cx).await; + +// // Manipulate with multiple selections on a single line +// cx.set_state(indoc! {" +// dd«dd +// cˇ»c«c +// bb +// aaaˇ»aa +// "}); +// cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx)); +// cx.assert_editor_state(indoc! {" +// «aaaaa +// bb +// ccc +// ddddˇ» +// "}); + +// // Manipulate with multiple disjoin selections +// cx.set_state(indoc! {" +// 5« +// 4 +// 3 +// 2 +// 1ˇ» + +// dd«dd +// ccc +// bb +// aaaˇ»aa +// "}); +// cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx)); +// cx.assert_editor_state(indoc! {" +// «1 +// 2 +// 3 +// 4 +// 5ˇ» + +// «aaaaa +// bb +// ccc +// ddddˇ» +// "}); +// } + +// #[gpui::test] +// async fn test_manipulate_text(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorTestContext::new(cx).await; + +// // Test convert_to_upper_case() +// cx.set_state(indoc! {" +// «hello worldˇ» +// "}); +// cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx)); +// cx.assert_editor_state(indoc! {" +// «HELLO WORLDˇ» +// "}); + +// // Test convert_to_lower_case() +// cx.set_state(indoc! {" +// «HELLO WORLDˇ» +// "}); +// cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx)); +// cx.assert_editor_state(indoc! {" +// «hello worldˇ» +// "}); + +// // Test multiple line, single selection case +// // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary +// cx.set_state(indoc! {" +// «The quick brown +// fox jumps over +// the lazy dogˇ» +// "}); +// cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx)); +// cx.assert_editor_state(indoc! {" +// «The Quick Brown +// Fox Jumps Over +// The Lazy Dogˇ» +// "}); + +// // Test multiple line, single selection case +// // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary +// cx.set_state(indoc! {" +// «The quick brown +// fox jumps over +// the lazy dogˇ» +// "}); +// cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx)); +// cx.assert_editor_state(indoc! {" +// «TheQuickBrown +// FoxJumpsOver +// TheLazyDogˇ» +// "}); + +// // From here on out, test more complex cases of manipulate_text() + +// // Test no selection case - should affect words cursors are in +// // Cursor at beginning, middle, and end of word +// cx.set_state(indoc! {" +// ˇhello big beauˇtiful worldˇ +// "}); +// cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx)); +// cx.assert_editor_state(indoc! {" +// «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ» +// "}); + +// // Test multiple selections on a single line and across multiple lines +// cx.set_state(indoc! {" +// «Theˇ» quick «brown +// foxˇ» jumps «overˇ» +// the «lazyˇ» dog +// "}); +// cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx)); +// cx.assert_editor_state(indoc! {" +// «THEˇ» quick «BROWN +// FOXˇ» jumps «OVERˇ» +// the «LAZYˇ» dog +// "}); + +// // Test case where text length grows +// cx.set_state(indoc! {" +// «tschüߡ» +// "}); +// cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx)); +// cx.assert_editor_state(indoc! {" +// «TSCHÜSSˇ» +// "}); + +// // Test to make sure we don't crash when text shrinks +// cx.set_state(indoc! {" +// aaa_bbbˇ +// "}); +// cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx)); +// cx.assert_editor_state(indoc! {" +// «aaaBbbˇ» +// "}); + +// // Test to make sure we all aware of the fact that each word can grow and shrink +// // Final selections should be aware of this fact +// cx.set_state(indoc! {" +// aaa_bˇbb bbˇb_ccc ˇccc_ddd +// "}); +// cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx)); +// cx.assert_editor_state(indoc! {" +// «aaaBbbˇ» «bbbCccˇ» «cccDddˇ» +// "}); +// } + +// #[gpui::test] +// fn test_duplicate_line(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); +// build_editor(buffer, cx) +// }) +// .root(cx); +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([ +// DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1), +// DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), +// DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), +// DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0), +// ]) +// }); +// view.duplicate_line(&DuplicateLine, cx); +// assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n"); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1), +// DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2), +// DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0), +// DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0), +// ] +// ); +// }); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); +// build_editor(buffer, cx) +// }) +// .root(cx); +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([ +// DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1), +// DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1), +// ]) +// }); +// view.duplicate_line(&DuplicateLine, cx); +// assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n"); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1), +// DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1), +// ] +// ); +// }); +// } + +// #[gpui::test] +// fn test_move_line_up_down(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx); +// build_editor(buffer, cx) +// }) +// .root(cx); +// view.update(cx, |view, cx| { +// view.fold_ranges( +// vec![ +// Point::new(0, 2)..Point::new(1, 2), +// Point::new(2, 3)..Point::new(4, 1), +// Point::new(7, 0)..Point::new(8, 4), +// ], +// true, +// cx, +// ); +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([ +// DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), +// DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1), +// DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3), +// DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2), +// ]) +// }); +// assert_eq!( +// view.display_text(cx), +// "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj" +// ); + +// view.move_line_up(&MoveLineUp, cx); +// assert_eq!( +// view.display_text(cx), +// "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff" +// ); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), +// DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1), +// DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3), +// DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2) +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.move_line_down(&MoveLineDown, cx); +// assert_eq!( +// view.display_text(cx), +// "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj" +// ); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1), +// DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1), +// DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3), +// DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2) +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.move_line_down(&MoveLineDown, cx); +// assert_eq!( +// view.display_text(cx), +// "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj" +// ); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1), +// DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1), +// DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3), +// DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2) +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.move_line_up(&MoveLineUp, cx); +// assert_eq!( +// view.display_text(cx), +// "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff" +// ); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1), +// DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1), +// DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3), +// DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2) +// ] +// ); +// }); +// } + +// #[gpui::test] +// fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let editor = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx); +// build_editor(buffer, cx) +// }) +// .root(cx); +// editor.update(cx, |editor, cx| { +// let snapshot = editor.buffer.read(cx).snapshot(cx); +// editor.insert_blocks( +// [BlockProperties { +// style: BlockStyle::Fixed, +// position: snapshot.anchor_after(Point::new(2, 0)), +// disposition: BlockDisposition::Below, +// height: 1, +// render: Arc::new(|_| Empty::new().into_any()), +// }], +// Some(Autoscroll::fit()), +// cx, +// ); +// editor.change_selections(None, cx, |s| { +// s.select_ranges([Point::new(2, 0)..Point::new(2, 0)]) +// }); +// editor.move_line_down(&MoveLineDown, cx); +// }); +// } + +// #[gpui::test] +// fn test_transpose(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// _ = cx.add_window(|cx| { +// let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx); + +// editor.change_selections(None, cx, |s| s.select_ranges([1..1])); +// editor.transpose(&Default::default(), cx); +// assert_eq!(editor.text(cx), "bac"); +// assert_eq!(editor.selections.ranges(cx), [2..2]); + +// editor.transpose(&Default::default(), cx); +// assert_eq!(editor.text(cx), "bca"); +// assert_eq!(editor.selections.ranges(cx), [3..3]); + +// editor.transpose(&Default::default(), cx); +// assert_eq!(editor.text(cx), "bac"); +// assert_eq!(editor.selections.ranges(cx), [3..3]); + +// editor +// }); + +// _ = cx.add_window(|cx| { +// let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx); + +// editor.change_selections(None, cx, |s| s.select_ranges([3..3])); +// editor.transpose(&Default::default(), cx); +// assert_eq!(editor.text(cx), "acb\nde"); +// assert_eq!(editor.selections.ranges(cx), [3..3]); + +// editor.change_selections(None, cx, |s| s.select_ranges([4..4])); +// editor.transpose(&Default::default(), cx); +// assert_eq!(editor.text(cx), "acbd\ne"); +// assert_eq!(editor.selections.ranges(cx), [5..5]); + +// editor.transpose(&Default::default(), cx); +// assert_eq!(editor.text(cx), "acbde\n"); +// assert_eq!(editor.selections.ranges(cx), [6..6]); + +// editor.transpose(&Default::default(), cx); +// assert_eq!(editor.text(cx), "acbd\ne"); +// assert_eq!(editor.selections.ranges(cx), [6..6]); + +// editor +// }); + +// _ = cx.add_window(|cx| { +// let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx); + +// editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4])); +// editor.transpose(&Default::default(), cx); +// assert_eq!(editor.text(cx), "bacd\ne"); +// assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]); + +// editor.transpose(&Default::default(), cx); +// assert_eq!(editor.text(cx), "bcade\n"); +// assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]); + +// editor.transpose(&Default::default(), cx); +// assert_eq!(editor.text(cx), "bcda\ne"); +// assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]); + +// editor.transpose(&Default::default(), cx); +// assert_eq!(editor.text(cx), "bcade\n"); +// assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]); + +// editor.transpose(&Default::default(), cx); +// assert_eq!(editor.text(cx), "bcaed\n"); +// assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]); + +// editor +// }); + +// _ = cx.add_window(|cx| { +// let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx); + +// editor.change_selections(None, cx, |s| s.select_ranges([4..4])); +// editor.transpose(&Default::default(), cx); +// assert_eq!(editor.text(cx), "🏀🍐✋"); +// assert_eq!(editor.selections.ranges(cx), [8..8]); + +// editor.transpose(&Default::default(), cx); +// assert_eq!(editor.text(cx), "🏀✋🍐"); +// assert_eq!(editor.selections.ranges(cx), [11..11]); + +// editor.transpose(&Default::default(), cx); +// assert_eq!(editor.text(cx), "🏀🍐✋"); +// assert_eq!(editor.selections.ranges(cx), [11..11]); + +// editor +// }); +// } + +// #[gpui::test] +// async fn test_clipboard(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorTestContext::new(cx).await; + +// cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six "); +// cx.update_editor(|e, cx| e.cut(&Cut, cx)); +// cx.assert_editor_state("ˇtwo ˇfour ˇsix "); + +// // Paste with three cursors. Each cursor pastes one slice of the clipboard text. +// cx.set_state("two ˇfour ˇsix ˇ"); +// cx.update_editor(|e, cx| e.paste(&Paste, cx)); +// cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ"); + +// // Paste again but with only two cursors. Since the number of cursors doesn't +// // match the number of slices in the clipboard, the entire clipboard text +// // is pasted at each cursor. +// cx.set_state("ˇtwo one✅ four three six five ˇ"); +// cx.update_editor(|e, cx| { +// e.handle_input("( ", cx); +// e.paste(&Paste, cx); +// e.handle_input(") ", cx); +// }); +// cx.assert_editor_state( +// &([ +// "( one✅ ", +// "three ", +// "five ) ˇtwo one✅ four three six five ( one✅ ", +// "three ", +// "five ) ˇ", +// ] +// .join("\n")), +// ); + +// // Cut with three selections, one of which is full-line. +// cx.set_state(indoc! {" +// 1«2ˇ»3 +// 4ˇ567 +// «8ˇ»9"}); +// cx.update_editor(|e, cx| e.cut(&Cut, cx)); +// cx.assert_editor_state(indoc! {" +// 1ˇ3 +// ˇ9"}); + +// // Paste with three selections, noticing how the copied selection that was full-line +// // gets inserted before the second cursor. +// cx.set_state(indoc! {" +// 1ˇ3 +// 9ˇ +// «oˇ»ne"}); +// cx.update_editor(|e, cx| e.paste(&Paste, cx)); +// cx.assert_editor_state(indoc! {" +// 12ˇ3 +// 4567 +// 9ˇ +// 8ˇne"}); + +// // Copy with a single cursor only, which writes the whole line into the clipboard. +// cx.set_state(indoc! {" +// The quick brown +// fox juˇmps over +// the lazy dog"}); +// cx.update_editor(|e, cx| e.copy(&Copy, cx)); +// cx.cx.assert_clipboard_content(Some("fox jumps over\n")); + +// // Paste with three selections, noticing how the copied full-line selection is inserted +// // before the empty selections but replaces the selection that is non-empty. +// cx.set_state(indoc! {" +// Tˇhe quick brown +// «foˇ»x jumps over +// tˇhe lazy dog"}); +// cx.update_editor(|e, cx| e.paste(&Paste, cx)); +// cx.assert_editor_state(indoc! {" +// fox jumps over +// Tˇhe quick brown +// fox jumps over +// ˇx jumps over +// fox jumps over +// tˇhe lazy dog"}); +// } + +// #[gpui::test] +// async fn test_paste_multiline(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorTestContext::new(cx).await; +// let language = Arc::new(Language::new( +// LanguageConfig::default(), +// Some(tree_sitter_rust::language()), +// )); +// cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); + +// // Cut an indented block, without the leading whitespace. +// cx.set_state(indoc! {" +// const a: B = ( +// c(), +// «d( +// e, +// f +// )ˇ» +// ); +// "}); +// cx.update_editor(|e, cx| e.cut(&Cut, cx)); +// cx.assert_editor_state(indoc! {" +// const a: B = ( +// c(), +// ˇ +// ); +// "}); + +// // Paste it at the same position. +// cx.update_editor(|e, cx| e.paste(&Paste, cx)); +// cx.assert_editor_state(indoc! {" +// const a: B = ( +// c(), +// d( +// e, +// f +// )ˇ +// ); +// "}); + +// // Paste it at a line with a lower indent level. +// cx.set_state(indoc! {" +// ˇ +// const a: B = ( +// c(), +// ); +// "}); +// cx.update_editor(|e, cx| e.paste(&Paste, cx)); +// cx.assert_editor_state(indoc! {" +// d( +// e, +// f +// )ˇ +// const a: B = ( +// c(), +// ); +// "}); + +// // Cut an indented block, with the leading whitespace. +// cx.set_state(indoc! {" +// const a: B = ( +// c(), +// « d( +// e, +// f +// ) +// ˇ»); +// "}); +// cx.update_editor(|e, cx| e.cut(&Cut, cx)); +// cx.assert_editor_state(indoc! {" +// const a: B = ( +// c(), +// ˇ); +// "}); + +// // Paste it at the same position. +// cx.update_editor(|e, cx| e.paste(&Paste, cx)); +// cx.assert_editor_state(indoc! {" +// const a: B = ( +// c(), +// d( +// e, +// f +// ) +// ˇ); +// "}); + +// // Paste it at a line with a higher indent level. +// cx.set_state(indoc! {" +// const a: B = ( +// c(), +// d( +// e, +// fˇ +// ) +// ); +// "}); +// cx.update_editor(|e, cx| e.paste(&Paste, cx)); +// cx.assert_editor_state(indoc! {" +// const a: B = ( +// c(), +// d( +// e, +// f d( +// e, +// f +// ) +// ˇ +// ) +// ); +// "}); +// } + +// #[gpui::test] +// fn test_select_all(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx); +// build_editor(buffer, cx) +// }) +// .root(cx); +// view.update(cx, |view, cx| { +// view.select_all(&SelectAll, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)] +// ); +// }); +// } + +// #[gpui::test] +// fn test_select_line(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx); +// build_editor(buffer, cx) +// }) +// .root(cx); +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([ +// DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1), +// DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), +// DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), +// DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2), +// ]) +// }); +// view.select_line(&SelectLine, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0), +// DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0), +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.select_line(&SelectLine, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0), +// DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5), +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.select_line(&SelectLine, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)] +// ); +// }); +// } + +// #[gpui::test] +// fn test_split_selection_into_lines(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx); +// build_editor(buffer, cx) +// }) +// .root(cx); +// view.update(cx, |view, cx| { +// view.fold_ranges( +// vec![ +// Point::new(0, 2)..Point::new(1, 2), +// Point::new(2, 3)..Point::new(4, 1), +// Point::new(7, 0)..Point::new(8, 4), +// ], +// true, +// cx, +// ); +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([ +// DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1), +// DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), +// DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), +// DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4), +// ]) +// }); +// assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"); +// }); + +// view.update(cx, |view, cx| { +// view.split_selection_into_lines(&SplitSelectionIntoLines, cx); +// assert_eq!( +// view.display_text(cx), +// "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i" +// ); +// assert_eq!( +// view.selections.display_ranges(cx), +// [ +// DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), +// DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), +// DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0), +// DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4) +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)]) +// }); +// view.split_selection_into_lines(&SplitSelectionIntoLines, cx); +// assert_eq!( +// view.display_text(cx), +// "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii" +// ); +// assert_eq!( +// view.selections.display_ranges(cx), +// [ +// DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5), +// DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5), +// DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5), +// DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5), +// DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5), +// DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5), +// DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5), +// DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0) +// ] +// ); +// }); +// } + +// #[gpui::test] +// fn test_add_selection_above_below(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let view = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx); +// build_editor(buffer, cx) +// }) +// .root(cx); + +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]) +// }); +// }); +// view.update(cx, |view, cx| { +// view.add_selection_above(&AddSelectionAbove, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), +// DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3) +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.add_selection_above(&AddSelectionAbove, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), +// DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3) +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.add_selection_below(&AddSelectionBelow, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)] +// ); + +// view.undo_selection(&UndoSelection, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), +// DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3) +// ] +// ); + +// view.redo_selection(&RedoSelection, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.add_selection_below(&AddSelectionBelow, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3), +// DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3) +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.add_selection_below(&AddSelectionBelow, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3), +// DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3) +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]) +// }); +// }); +// view.update(cx, |view, cx| { +// view.add_selection_below(&AddSelectionBelow, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3), +// DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3) +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.add_selection_below(&AddSelectionBelow, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3), +// DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3) +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.add_selection_above(&AddSelectionAbove, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.add_selection_above(&AddSelectionAbove, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)]) +// }); +// view.add_selection_below(&AddSelectionBelow, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3), +// DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4), +// DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2), +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.add_selection_below(&AddSelectionBelow, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3), +// DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4), +// DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2), +// DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4), +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.add_selection_above(&AddSelectionAbove, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3), +// DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4), +// DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2), +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)]) +// }); +// }); +// view.update(cx, |view, cx| { +// view.add_selection_above(&AddSelectionAbove, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1), +// DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1), +// DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1), +// DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1), +// ] +// ); +// }); + +// view.update(cx, |view, cx| { +// view.add_selection_below(&AddSelectionBelow, cx); +// assert_eq!( +// view.selections.display_ranges(cx), +// vec![ +// DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1), +// DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1), +// DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1), +// ] +// ); +// }); +// } + +// #[gpui::test] +// async fn test_select_next(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorTestContext::new(cx).await; +// cx.set_state("abc\nˇabc abc\ndefabc\nabc"); + +// cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx)) +// .unwrap(); +// cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc"); + +// cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx)) +// .unwrap(); +// cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc"); + +// cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx)); +// cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc"); + +// cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx)); +// cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc"); + +// cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx)) +// .unwrap(); +// cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»"); + +// cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx)) +// .unwrap(); +// cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»"); +// } + +// #[gpui::test] +// async fn test_select_previous(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); +// { +// // `Select previous` without a selection (selects wordwise) +// let mut cx = EditorTestContext::new(cx).await; +// cx.set_state("abc\nˇabc abc\ndefabc\nabc"); + +// cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) +// .unwrap(); +// cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc"); + +// cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) +// .unwrap(); +// cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc"); + +// cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx)); +// cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc"); + +// cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx)); +// cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc"); + +// cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) +// .unwrap(); +// cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»"); + +// cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) +// .unwrap(); +// cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»"); +// } +// { +// // `Select previous` with a selection +// let mut cx = EditorTestContext::new(cx).await; +// cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc"); + +// cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) +// .unwrap(); +// cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc"); + +// cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) +// .unwrap(); +// cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»"); + +// cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx)); +// cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc"); + +// cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx)); +// cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»"); + +// cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) +// .unwrap(); +// cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»"); + +// cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) +// .unwrap(); +// cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»"); +// } +// } + +// #[gpui::test] +// async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let language = Arc::new(Language::new( +// LanguageConfig::default(), +// Some(tree_sitter_rust::language()), +// )); + +// let text = r#" +// use mod1::mod2::{mod3, mod4}; + +// fn fn_1(param1: bool, param2: &str) { +// let var1 = "text"; +// } +// "# +// .unindent(); + +// let buffer = +// cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx)); +// let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); +// let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); +// view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx)) +// .await; + +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([ +// DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25), +// DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12), +// DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18), +// ]); +// }); +// view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx); +// }); +// assert_eq!( +// view.update(cx, |view, cx| { view.selections.display_ranges(cx) }), +// &[ +// DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27), +// DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7), +// DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21), +// ] +// ); + +// view.update(cx, |view, cx| { +// view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx); +// }); +// assert_eq!( +// view.update(cx, |view, cx| view.selections.display_ranges(cx)), +// &[ +// DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28), +// DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0), +// ] +// ); + +// view.update(cx, |view, cx| { +// view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx); +// }); +// assert_eq!( +// view.update(cx, |view, cx| view.selections.display_ranges(cx)), +// &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)] +// ); + +// // Trying to expand the selected syntax node one more time has no effect. +// view.update(cx, |view, cx| { +// view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx); +// }); +// assert_eq!( +// view.update(cx, |view, cx| view.selections.display_ranges(cx)), +// &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)] +// ); + +// view.update(cx, |view, cx| { +// view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx); +// }); +// assert_eq!( +// view.update(cx, |view, cx| view.selections.display_ranges(cx)), +// &[ +// DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28), +// DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0), +// ] +// ); + +// view.update(cx, |view, cx| { +// view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx); +// }); +// assert_eq!( +// view.update(cx, |view, cx| view.selections.display_ranges(cx)), +// &[ +// DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27), +// DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7), +// DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21), +// ] +// ); + +// view.update(cx, |view, cx| { +// view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx); +// }); +// assert_eq!( +// view.update(cx, |view, cx| view.selections.display_ranges(cx)), +// &[ +// DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25), +// DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12), +// DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18), +// ] +// ); + +// // Trying to shrink the selected syntax node one more time has no effect. +// view.update(cx, |view, cx| { +// view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx); +// }); +// assert_eq!( +// view.update(cx, |view, cx| view.selections.display_ranges(cx)), +// &[ +// DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25), +// DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12), +// DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18), +// ] +// ); + +// // Ensure that we keep expanding the selection if the larger selection starts or ends within +// // a fold. +// view.update(cx, |view, cx| { +// view.fold_ranges( +// vec![ +// Point::new(0, 21)..Point::new(0, 24), +// Point::new(3, 20)..Point::new(3, 22), +// ], +// true, +// cx, +// ); +// view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx); +// }); +// assert_eq!( +// view.update(cx, |view, cx| view.selections.display_ranges(cx)), +// &[ +// DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28), +// DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7), +// DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23), +// ] +// ); +// } + +// #[gpui::test] +// async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let language = Arc::new( +// Language::new( +// LanguageConfig { +// brackets: BracketPairConfig { +// pairs: vec![ +// BracketPair { +// start: "{".to_string(), +// end: "}".to_string(), +// close: false, +// newline: true, +// }, +// BracketPair { +// start: "(".to_string(), +// end: ")".to_string(), +// close: false, +// newline: true, +// }, +// ], +// ..Default::default() +// }, +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ) +// .with_indents_query( +// r#" +// (_ "(" ")" @end) @indent +// (_ "{" "}" @end) @indent +// "#, +// ) +// .unwrap(), +// ); + +// let text = "fn a() {}"; + +// let buffer = +// cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx)); +// let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); +// let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); +// editor +// .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx)) +// .await; + +// editor.update(cx, |editor, cx| { +// editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9])); +// editor.newline(&Newline, cx); +// assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n"); +// assert_eq!( +// editor.selections.ranges(cx), +// &[ +// Point::new(1, 4)..Point::new(1, 4), +// Point::new(3, 4)..Point::new(3, 4), +// Point::new(5, 0)..Point::new(5, 0) +// ] +// ); +// }); +// } + +// #[gpui::test] +// async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorTestContext::new(cx).await; + +// let language = Arc::new(Language::new( +// LanguageConfig { +// brackets: BracketPairConfig { +// pairs: vec![ +// BracketPair { +// start: "{".to_string(), +// end: "}".to_string(), +// close: true, +// newline: true, +// }, +// BracketPair { +// start: "(".to_string(), +// end: ")".to_string(), +// close: true, +// newline: true, +// }, +// BracketPair { +// start: "/*".to_string(), +// end: " */".to_string(), +// close: true, +// newline: true, +// }, +// BracketPair { +// start: "[".to_string(), +// end: "]".to_string(), +// close: false, +// newline: true, +// }, +// BracketPair { +// start: "\"".to_string(), +// end: "\"".to_string(), +// close: true, +// newline: false, +// }, +// ], +// ..Default::default() +// }, +// autoclose_before: "})]".to_string(), +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// )); + +// let registry = Arc::new(LanguageRegistry::test()); +// registry.add(language.clone()); +// cx.update_buffer(|buffer, cx| { +// buffer.set_language_registry(registry); +// buffer.set_language(Some(language), cx); +// }); + +// cx.set_state( +// &r#" +// 🏀ˇ +// εˇ +// ❤️ˇ +// "# +// .unindent(), +// ); + +// // autoclose multiple nested brackets at multiple cursors +// cx.update_editor(|view, cx| { +// view.handle_input("{", cx); +// view.handle_input("{", cx); +// view.handle_input("{", cx); +// }); +// cx.assert_editor_state( +// &" +// 🏀{{{ˇ}}} +// ε{{{ˇ}}} +// ❤️{{{ˇ}}} +// " +// .unindent(), +// ); + +// // insert a different closing bracket +// cx.update_editor(|view, cx| { +// view.handle_input(")", cx); +// }); +// cx.assert_editor_state( +// &" +// 🏀{{{)ˇ}}} +// ε{{{)ˇ}}} +// ❤️{{{)ˇ}}} +// " +// .unindent(), +// ); + +// // skip over the auto-closed brackets when typing a closing bracket +// cx.update_editor(|view, cx| { +// view.move_right(&MoveRight, cx); +// view.handle_input("}", cx); +// view.handle_input("}", cx); +// view.handle_input("}", cx); +// }); +// cx.assert_editor_state( +// &" +// 🏀{{{)}}}}ˇ +// ε{{{)}}}}ˇ +// ❤️{{{)}}}}ˇ +// " +// .unindent(), +// ); + +// // autoclose multi-character pairs +// cx.set_state( +// &" +// ˇ +// ˇ +// " +// .unindent(), +// ); +// cx.update_editor(|view, cx| { +// view.handle_input("/", cx); +// view.handle_input("*", cx); +// }); +// cx.assert_editor_state( +// &" +// /*ˇ */ +// /*ˇ */ +// " +// .unindent(), +// ); + +// // one cursor autocloses a multi-character pair, one cursor +// // does not autoclose. +// cx.set_state( +// &" +// /ˇ +// ˇ +// " +// .unindent(), +// ); +// cx.update_editor(|view, cx| view.handle_input("*", cx)); +// cx.assert_editor_state( +// &" +// /*ˇ */ +// *ˇ +// " +// .unindent(), +// ); + +// // Don't autoclose if the next character isn't whitespace and isn't +// // listed in the language's "autoclose_before" section. +// cx.set_state("ˇa b"); +// cx.update_editor(|view, cx| view.handle_input("{", cx)); +// cx.assert_editor_state("{ˇa b"); + +// // Don't autoclose if `close` is false for the bracket pair +// cx.set_state("ˇ"); +// cx.update_editor(|view, cx| view.handle_input("[", cx)); +// cx.assert_editor_state("[ˇ"); + +// // Surround with brackets if text is selected +// cx.set_state("«aˇ» b"); +// cx.update_editor(|view, cx| view.handle_input("{", cx)); +// cx.assert_editor_state("{«aˇ»} b"); + +// // Autclose pair where the start and end characters are the same +// cx.set_state("aˇ"); +// cx.update_editor(|view, cx| view.handle_input("\"", cx)); +// cx.assert_editor_state("a\"ˇ\""); +// cx.update_editor(|view, cx| view.handle_input("\"", cx)); +// cx.assert_editor_state("a\"\"ˇ"); +// } + +// #[gpui::test] +// async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorTestContext::new(cx).await; + +// let html_language = Arc::new( +// Language::new( +// LanguageConfig { +// name: "HTML".into(), +// brackets: BracketPairConfig { +// pairs: vec![ +// BracketPair { +// start: "<".into(), +// end: ">".into(), +// close: true, +// ..Default::default() +// }, +// BracketPair { +// start: "{".into(), +// end: "}".into(), +// close: true, +// ..Default::default() +// }, +// BracketPair { +// start: "(".into(), +// end: ")".into(), +// close: true, +// ..Default::default() +// }, +// ], +// ..Default::default() +// }, +// autoclose_before: "})]>".into(), +// ..Default::default() +// }, +// Some(tree_sitter_html::language()), +// ) +// .with_injection_query( +// r#" +// (script_element +// (raw_text) @content +// (#set! "language" "javascript")) +// "#, +// ) +// .unwrap(), +// ); + +// let javascript_language = Arc::new(Language::new( +// LanguageConfig { +// name: "JavaScript".into(), +// brackets: BracketPairConfig { +// pairs: vec![ +// BracketPair { +// start: "/*".into(), +// end: " */".into(), +// close: true, +// ..Default::default() +// }, +// BracketPair { +// start: "{".into(), +// end: "}".into(), +// close: true, +// ..Default::default() +// }, +// BracketPair { +// start: "(".into(), +// end: ")".into(), +// close: true, +// ..Default::default() +// }, +// ], +// ..Default::default() +// }, +// autoclose_before: "})]>".into(), +// ..Default::default() +// }, +// Some(tree_sitter_typescript::language_tsx()), +// )); + +// let registry = Arc::new(LanguageRegistry::test()); +// registry.add(html_language.clone()); +// registry.add(javascript_language.clone()); + +// cx.update_buffer(|buffer, cx| { +// buffer.set_language_registry(registry); +// buffer.set_language(Some(html_language), cx); +// }); + +// cx.set_state( +// &r#" +// ˇ +// +// ˇ +// "# +// .unindent(), +// ); + +// // Precondition: different languages are active at different locations. +// cx.update_editor(|editor, cx| { +// let snapshot = editor.snapshot(cx); +// let cursors = editor.selections.ranges::(cx); +// let languages = cursors +// .iter() +// .map(|c| snapshot.language_at(c.start).unwrap().name()) +// .collect::>(); +// assert_eq!( +// languages, +// &["HTML".into(), "JavaScript".into(), "HTML".into()] +// ); +// }); + +// // Angle brackets autoclose in HTML, but not JavaScript. +// cx.update_editor(|editor, cx| { +// editor.handle_input("<", cx); +// editor.handle_input("a", cx); +// }); +// cx.assert_editor_state( +// &r#" +// +// +// +// "# +// .unindent(), +// ); + +// // Curly braces and parens autoclose in both HTML and JavaScript. +// cx.update_editor(|editor, cx| { +// editor.handle_input(" b=", cx); +// editor.handle_input("{", cx); +// editor.handle_input("c", cx); +// editor.handle_input("(", cx); +// }); +// cx.assert_editor_state( +// &r#" +// +// +// +// "# +// .unindent(), +// ); + +// // Brackets that were already autoclosed are skipped. +// cx.update_editor(|editor, cx| { +// editor.handle_input(")", cx); +// editor.handle_input("d", cx); +// editor.handle_input("}", cx); +// }); +// cx.assert_editor_state( +// &r#" +// +// +// +// "# +// .unindent(), +// ); +// cx.update_editor(|editor, cx| { +// editor.handle_input(">", cx); +// }); +// cx.assert_editor_state( +// &r#" +// ˇ +// +// ˇ +// "# +// .unindent(), +// ); + +// // Reset +// cx.set_state( +// &r#" +// ˇ +// +// ˇ +// "# +// .unindent(), +// ); + +// cx.update_editor(|editor, cx| { +// editor.handle_input("<", cx); +// }); +// cx.assert_editor_state( +// &r#" +// <ˇ> +// +// <ˇ> +// "# +// .unindent(), +// ); + +// // When backspacing, the closing angle brackets are removed. +// cx.update_editor(|editor, cx| { +// editor.backspace(&Backspace, cx); +// }); +// cx.assert_editor_state( +// &r#" +// ˇ +// +// ˇ +// "# +// .unindent(), +// ); + +// // Block comments autoclose in JavaScript, but not HTML. +// cx.update_editor(|editor, cx| { +// editor.handle_input("/", cx); +// editor.handle_input("*", cx); +// }); +// cx.assert_editor_state( +// &r#" +// /*ˇ +// +// /*ˇ +// "# +// .unindent(), +// ); +// } + +// #[gpui::test] +// async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorTestContext::new(cx).await; + +// let rust_language = Arc::new( +// Language::new( +// LanguageConfig { +// name: "Rust".into(), +// brackets: serde_json::from_value(json!([ +// { "start": "{", "end": "}", "close": true, "newline": true }, +// { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] }, +// ])) +// .unwrap(), +// autoclose_before: "})]>".into(), +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ) +// .with_override_query("(string_literal) @string") +// .unwrap(), +// ); + +// let registry = Arc::new(LanguageRegistry::test()); +// registry.add(rust_language.clone()); + +// cx.update_buffer(|buffer, cx| { +// buffer.set_language_registry(registry); +// buffer.set_language(Some(rust_language), cx); +// }); + +// cx.set_state( +// &r#" +// let x = ˇ +// "# +// .unindent(), +// ); + +// // Inserting a quotation mark. A closing quotation mark is automatically inserted. +// cx.update_editor(|editor, cx| { +// editor.handle_input("\"", cx); +// }); +// cx.assert_editor_state( +// &r#" +// let x = "ˇ" +// "# +// .unindent(), +// ); + +// // Inserting another quotation mark. The cursor moves across the existing +// // automatically-inserted quotation mark. +// cx.update_editor(|editor, cx| { +// editor.handle_input("\"", cx); +// }); +// cx.assert_editor_state( +// &r#" +// let x = ""ˇ +// "# +// .unindent(), +// ); + +// // Reset +// cx.set_state( +// &r#" +// let x = ˇ +// "# +// .unindent(), +// ); + +// // Inserting a quotation mark inside of a string. A second quotation mark is not inserted. +// cx.update_editor(|editor, cx| { +// editor.handle_input("\"", cx); +// editor.handle_input(" ", cx); +// editor.move_left(&Default::default(), cx); +// editor.handle_input("\\", cx); +// editor.handle_input("\"", cx); +// }); +// cx.assert_editor_state( +// &r#" +// let x = "\"ˇ " +// "# +// .unindent(), +// ); + +// // Inserting a closing quotation mark at the position of an automatically-inserted quotation +// // mark. Nothing is inserted. +// cx.update_editor(|editor, cx| { +// editor.move_right(&Default::default(), cx); +// editor.handle_input("\"", cx); +// }); +// cx.assert_editor_state( +// &r#" +// let x = "\" "ˇ +// "# +// .unindent(), +// ); +// } + +// #[gpui::test] +// async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let language = Arc::new(Language::new( +// LanguageConfig { +// brackets: BracketPairConfig { +// pairs: vec![ +// BracketPair { +// start: "{".to_string(), +// end: "}".to_string(), +// close: true, +// newline: true, +// }, +// BracketPair { +// start: "/* ".to_string(), +// end: "*/".to_string(), +// close: true, +// ..Default::default() +// }, +// ], +// ..Default::default() +// }, +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// )); + +// let text = r#" +// a +// b +// c +// "# +// .unindent(); + +// let buffer = +// cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx)); +// let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); +// let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); +// view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx)) +// .await; + +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([ +// DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1), +// DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1), +// DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1), +// ]) +// }); + +// view.handle_input("{", cx); +// view.handle_input("{", cx); +// view.handle_input("{", cx); +// assert_eq!( +// view.text(cx), +// " +// {{{a}}} +// {{{b}}} +// {{{c}}} +// " +// .unindent() +// ); +// assert_eq!( +// view.selections.display_ranges(cx), +// [ +// DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4), +// DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4), +// DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4) +// ] +// ); + +// view.undo(&Undo, cx); +// view.undo(&Undo, cx); +// view.undo(&Undo, cx); +// assert_eq!( +// view.text(cx), +// " +// a +// b +// c +// " +// .unindent() +// ); +// assert_eq!( +// view.selections.display_ranges(cx), +// [ +// DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1), +// DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1), +// DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1) +// ] +// ); + +// // Ensure inserting the first character of a multi-byte bracket pair +// // doesn't surround the selections with the bracket. +// view.handle_input("/", cx); +// assert_eq!( +// view.text(cx), +// " +// / +// / +// / +// " +// .unindent() +// ); +// assert_eq!( +// view.selections.display_ranges(cx), +// [ +// DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), +// DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1), +// DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1) +// ] +// ); + +// view.undo(&Undo, cx); +// assert_eq!( +// view.text(cx), +// " +// a +// b +// c +// " +// .unindent() +// ); +// assert_eq!( +// view.selections.display_ranges(cx), +// [ +// DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1), +// DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1), +// DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1) +// ] +// ); + +// // Ensure inserting the last character of a multi-byte bracket pair +// // doesn't surround the selections with the bracket. +// view.handle_input("*", cx); +// assert_eq!( +// view.text(cx), +// " +// * +// * +// * +// " +// .unindent() +// ); +// assert_eq!( +// view.selections.display_ranges(cx), +// [ +// DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), +// DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1), +// DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1) +// ] +// ); +// }); +// } + +// #[gpui::test] +// async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let language = Arc::new(Language::new( +// LanguageConfig { +// brackets: BracketPairConfig { +// pairs: vec![BracketPair { +// start: "{".to_string(), +// end: "}".to_string(), +// close: true, +// newline: true, +// }], +// ..Default::default() +// }, +// autoclose_before: "}".to_string(), +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// )); + +// let text = r#" +// a +// b +// c +// "# +// .unindent(); + +// let buffer = +// cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx)); +// let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); +// let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); +// editor +// .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx)) +// .await; + +// editor.update(cx, |editor, cx| { +// editor.change_selections(None, cx, |s| { +// s.select_ranges([ +// Point::new(0, 1)..Point::new(0, 1), +// Point::new(1, 1)..Point::new(1, 1), +// Point::new(2, 1)..Point::new(2, 1), +// ]) +// }); + +// editor.handle_input("{", cx); +// editor.handle_input("{", cx); +// editor.handle_input("_", cx); +// assert_eq!( +// editor.text(cx), +// " +// a{{_}} +// b{{_}} +// c{{_}} +// " +// .unindent() +// ); +// assert_eq!( +// editor.selections.ranges::(cx), +// [ +// Point::new(0, 4)..Point::new(0, 4), +// Point::new(1, 4)..Point::new(1, 4), +// Point::new(2, 4)..Point::new(2, 4) +// ] +// ); + +// editor.backspace(&Default::default(), cx); +// editor.backspace(&Default::default(), cx); +// assert_eq!( +// editor.text(cx), +// " +// a{} +// b{} +// c{} +// " +// .unindent() +// ); +// assert_eq!( +// editor.selections.ranges::(cx), +// [ +// Point::new(0, 2)..Point::new(0, 2), +// Point::new(1, 2)..Point::new(1, 2), +// Point::new(2, 2)..Point::new(2, 2) +// ] +// ); + +// editor.delete_to_previous_word_start(&Default::default(), cx); +// assert_eq!( +// editor.text(cx), +// " +// a +// b +// c +// " +// .unindent() +// ); +// assert_eq!( +// editor.selections.ranges::(cx), +// [ +// Point::new(0, 1)..Point::new(0, 1), +// Point::new(1, 1)..Point::new(1, 1), +// Point::new(2, 1)..Point::new(2, 1) +// ] +// ); +// }); +// } + +// #[gpui::test] +// async fn test_snippets(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let (text, insertion_ranges) = marked_text_ranges( +// indoc! {" +// a.ˇ b +// a.ˇ b +// a.ˇ b +// "}, +// false, +// ); + +// let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx)); +// let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); + +// editor.update(cx, |editor, cx| { +// let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap(); + +// editor +// .insert_snippet(&insertion_ranges, snippet, cx) +// .unwrap(); + +// fn assert(editor: &mut Editor, cx: &mut ViewContext, marked_text: &str) { +// let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false); +// assert_eq!(editor.text(cx), expected_text); +// assert_eq!(editor.selections.ranges::(cx), selection_ranges); +// } + +// assert( +// editor, +// cx, +// indoc! {" +// a.f(«one», two, «three») b +// a.f(«one», two, «three») b +// a.f(«one», two, «three») b +// "}, +// ); + +// // Can't move earlier than the first tab stop +// assert!(!editor.move_to_prev_snippet_tabstop(cx)); +// assert( +// editor, +// cx, +// indoc! {" +// a.f(«one», two, «three») b +// a.f(«one», two, «three») b +// a.f(«one», two, «three») b +// "}, +// ); + +// assert!(editor.move_to_next_snippet_tabstop(cx)); +// assert( +// editor, +// cx, +// indoc! {" +// a.f(one, «two», three) b +// a.f(one, «two», three) b +// a.f(one, «two», three) b +// "}, +// ); + +// editor.move_to_prev_snippet_tabstop(cx); +// assert( +// editor, +// cx, +// indoc! {" +// a.f(«one», two, «three») b +// a.f(«one», two, «three») b +// a.f(«one», two, «three») b +// "}, +// ); + +// assert!(editor.move_to_next_snippet_tabstop(cx)); +// assert( +// editor, +// cx, +// indoc! {" +// a.f(one, «two», three) b +// a.f(one, «two», three) b +// a.f(one, «two», three) b +// "}, +// ); +// assert!(editor.move_to_next_snippet_tabstop(cx)); +// assert( +// editor, +// cx, +// indoc! {" +// a.f(one, two, three)ˇ b +// a.f(one, two, three)ˇ b +// a.f(one, two, three)ˇ b +// "}, +// ); + +// // As soon as the last tab stop is reached, snippet state is gone +// editor.move_to_prev_snippet_tabstop(cx); +// assert( +// editor, +// cx, +// indoc! {" +// a.f(one, two, three)ˇ b +// a.f(one, two, three)ˇ b +// a.f(one, two, three)ˇ b +// "}, +// ); +// }); +// } + +// #[gpui::test] +// async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut language = Language::new( +// LanguageConfig { +// name: "Rust".into(), +// path_suffixes: vec!["rs".to_string()], +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ); +// let mut fake_servers = language +// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { +// capabilities: lsp::ServerCapabilities { +// document_formatting_provider: Some(lsp::OneOf::Left(true)), +// ..Default::default() +// }, +// ..Default::default() +// })) +// .await; + +// let fs = FakeFs::new(cx.background()); +// fs.insert_file("/file.rs", Default::default()).await; + +// let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; +// project.update(cx, |project, _| project.languages().add(Arc::new(language))); +// let buffer = project +// .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) +// .await +// .unwrap(); + +// cx.foreground().start_waiting(); +// let fake_server = fake_servers.next().await.unwrap(); + +// let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); +// let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); +// editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); +// assert!(cx.read(|cx| editor.is_dirty(cx))); + +// let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx)); +// fake_server +// .handle_request::(move |params, _| async move { +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path("/file.rs").unwrap() +// ); +// assert_eq!(params.options.tab_size, 4); +// Ok(Some(vec![lsp::TextEdit::new( +// lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)), +// ", ".to_string(), +// )])) +// }) +// .next() +// .await; +// cx.foreground().start_waiting(); +// save.await.unwrap(); +// assert_eq!( +// editor.read_with(cx, |editor, cx| editor.text(cx)), +// "one, two\nthree\n" +// ); +// assert!(!cx.read(|cx| editor.is_dirty(cx))); + +// editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); +// assert!(cx.read(|cx| editor.is_dirty(cx))); + +// // Ensure we can still save even if formatting hangs. +// fake_server.handle_request::(move |params, _| async move { +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path("/file.rs").unwrap() +// ); +// futures::future::pending::<()>().await; +// unreachable!() +// }); +// let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx)); +// cx.foreground().advance_clock(super::FORMAT_TIMEOUT); +// cx.foreground().start_waiting(); +// save.await.unwrap(); +// assert_eq!( +// editor.read_with(cx, |editor, cx| editor.text(cx)), +// "one\ntwo\nthree\n" +// ); +// assert!(!cx.read(|cx| editor.is_dirty(cx))); + +// // Set rust language override and assert overridden tabsize is sent to language server +// update_test_language_settings(cx, |settings| { +// settings.languages.insert( +// "Rust".into(), +// LanguageSettingsContent { +// tab_size: NonZeroU32::new(8), +// ..Default::default() +// }, +// ); +// }); + +// let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx)); +// fake_server +// .handle_request::(move |params, _| async move { +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path("/file.rs").unwrap() +// ); +// assert_eq!(params.options.tab_size, 8); +// Ok(Some(vec![])) +// }) +// .next() +// .await; +// cx.foreground().start_waiting(); +// save.await.unwrap(); +// } + +// #[gpui::test] +// async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut language = Language::new( +// LanguageConfig { +// name: "Rust".into(), +// path_suffixes: vec!["rs".to_string()], +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ); +// let mut fake_servers = language +// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { +// capabilities: lsp::ServerCapabilities { +// document_range_formatting_provider: Some(lsp::OneOf::Left(true)), +// ..Default::default() +// }, +// ..Default::default() +// })) +// .await; + +// let fs = FakeFs::new(cx.background()); +// fs.insert_file("/file.rs", Default::default()).await; + +// let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; +// project.update(cx, |project, _| project.languages().add(Arc::new(language))); +// let buffer = project +// .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) +// .await +// .unwrap(); + +// cx.foreground().start_waiting(); +// let fake_server = fake_servers.next().await.unwrap(); + +// let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); +// let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); +// editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); +// assert!(cx.read(|cx| editor.is_dirty(cx))); + +// let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx)); +// fake_server +// .handle_request::(move |params, _| async move { +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path("/file.rs").unwrap() +// ); +// assert_eq!(params.options.tab_size, 4); +// Ok(Some(vec![lsp::TextEdit::new( +// lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)), +// ", ".to_string(), +// )])) +// }) +// .next() +// .await; +// cx.foreground().start_waiting(); +// save.await.unwrap(); +// assert_eq!( +// editor.read_with(cx, |editor, cx| editor.text(cx)), +// "one, two\nthree\n" +// ); +// assert!(!cx.read(|cx| editor.is_dirty(cx))); + +// editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); +// assert!(cx.read(|cx| editor.is_dirty(cx))); + +// // Ensure we can still save even if formatting hangs. +// fake_server.handle_request::( +// move |params, _| async move { +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path("/file.rs").unwrap() +// ); +// futures::future::pending::<()>().await; +// unreachable!() +// }, +// ); +// let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx)); +// cx.foreground().advance_clock(super::FORMAT_TIMEOUT); +// cx.foreground().start_waiting(); +// save.await.unwrap(); +// assert_eq!( +// editor.read_with(cx, |editor, cx| editor.text(cx)), +// "one\ntwo\nthree\n" +// ); +// assert!(!cx.read(|cx| editor.is_dirty(cx))); + +// // Set rust language override and assert overridden tabsize is sent to language server +// update_test_language_settings(cx, |settings| { +// settings.languages.insert( +// "Rust".into(), +// LanguageSettingsContent { +// tab_size: NonZeroU32::new(8), +// ..Default::default() +// }, +// ); +// }); + +// let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx)); +// fake_server +// .handle_request::(move |params, _| async move { +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path("/file.rs").unwrap() +// ); +// assert_eq!(params.options.tab_size, 8); +// Ok(Some(vec![])) +// }) +// .next() +// .await; +// cx.foreground().start_waiting(); +// save.await.unwrap(); +// } + +// #[gpui::test] +// async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer) +// }); + +// let mut language = Language::new( +// LanguageConfig { +// name: "Rust".into(), +// path_suffixes: vec!["rs".to_string()], +// // Enable Prettier formatting for the same buffer, and ensure +// // LSP is called instead of Prettier. +// prettier_parser_name: Some("test_parser".to_string()), +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ); +// let mut fake_servers = language +// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { +// capabilities: lsp::ServerCapabilities { +// document_formatting_provider: Some(lsp::OneOf::Left(true)), +// ..Default::default() +// }, +// ..Default::default() +// })) +// .await; + +// let fs = FakeFs::new(cx.background()); +// fs.insert_file("/file.rs", Default::default()).await; + +// let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; +// project.update(cx, |project, _| { +// project.languages().add(Arc::new(language)); +// }); +// let buffer = project +// .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) +// .await +// .unwrap(); + +// cx.foreground().start_waiting(); +// let fake_server = fake_servers.next().await.unwrap(); + +// let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); +// let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); +// editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); + +// let format = editor.update(cx, |editor, cx| { +// editor.perform_format(project.clone(), FormatTrigger::Manual, cx) +// }); +// fake_server +// .handle_request::(move |params, _| async move { +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path("/file.rs").unwrap() +// ); +// assert_eq!(params.options.tab_size, 4); +// Ok(Some(vec![lsp::TextEdit::new( +// lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)), +// ", ".to_string(), +// )])) +// }) +// .next() +// .await; +// cx.foreground().start_waiting(); +// format.await.unwrap(); +// assert_eq!( +// editor.read_with(cx, |editor, cx| editor.text(cx)), +// "one, two\nthree\n" +// ); + +// editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); +// // Ensure we don't lock if formatting hangs. +// fake_server.handle_request::(move |params, _| async move { +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path("/file.rs").unwrap() +// ); +// futures::future::pending::<()>().await; +// unreachable!() +// }); +// let format = editor.update(cx, |editor, cx| { +// editor.perform_format(project, FormatTrigger::Manual, cx) +// }); +// cx.foreground().advance_clock(super::FORMAT_TIMEOUT); +// cx.foreground().start_waiting(); +// format.await.unwrap(); +// assert_eq!( +// editor.read_with(cx, |editor, cx| editor.text(cx)), +// "one\ntwo\nthree\n" +// ); +// } + +// #[gpui::test] +// async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorLspTestContext::new_rust( +// lsp::ServerCapabilities { +// document_formatting_provider: Some(lsp::OneOf::Left(true)), +// ..Default::default() +// }, +// cx, +// ) +// .await; + +// cx.set_state(indoc! {" +// one.twoˇ +// "}); + +// // The format request takes a long time. When it completes, it inserts +// // a newline and an indent before the `.` +// cx.lsp +// .handle_request::(move |_, cx| { +// let executor = cx.background(); +// async move { +// executor.timer(Duration::from_millis(100)).await; +// Ok(Some(vec![lsp::TextEdit { +// range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)), +// new_text: "\n ".into(), +// }])) +// } +// }); + +// // Submit a format request. +// let format_1 = cx +// .update_editor(|editor, cx| editor.format(&Format, cx)) +// .unwrap(); +// cx.foreground().run_until_parked(); + +// // Submit a second format request. +// let format_2 = cx +// .update_editor(|editor, cx| editor.format(&Format, cx)) +// .unwrap(); +// cx.foreground().run_until_parked(); + +// // Wait for both format requests to complete +// cx.foreground().advance_clock(Duration::from_millis(200)); +// cx.foreground().start_waiting(); +// format_1.await.unwrap(); +// cx.foreground().start_waiting(); +// format_2.await.unwrap(); + +// // The formatting edits only happens once. +// cx.assert_editor_state(indoc! {" +// one +// .twoˇ +// "}); +// } + +// #[gpui::test] +// async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.formatter = Some(language_settings::Formatter::Auto) +// }); + +// let mut cx = EditorLspTestContext::new_rust( +// lsp::ServerCapabilities { +// document_formatting_provider: Some(lsp::OneOf::Left(true)), +// ..Default::default() +// }, +// cx, +// ) +// .await; + +// // Set up a buffer white some trailing whitespace and no trailing newline. +// cx.set_state( +// &[ +// "one ", // +// "twoˇ", // +// "three ", // +// "four", // +// ] +// .join("\n"), +// ); + +// // Submit a format request. +// let format = cx +// .update_editor(|editor, cx| editor.format(&Format, cx)) +// .unwrap(); + +// // Record which buffer changes have been sent to the language server +// let buffer_changes = Arc::new(Mutex::new(Vec::new())); +// cx.lsp +// .handle_notification::({ +// let buffer_changes = buffer_changes.clone(); +// move |params, _| { +// buffer_changes.lock().extend( +// params +// .content_changes +// .into_iter() +// .map(|e| (e.range.unwrap(), e.text)), +// ); +// } +// }); + +// // Handle formatting requests to the language server. +// cx.lsp.handle_request::({ +// let buffer_changes = buffer_changes.clone(); +// move |_, _| { +// // When formatting is requested, trailing whitespace has already been stripped, +// // and the trailing newline has already been added. +// assert_eq!( +// &buffer_changes.lock()[1..], +// &[ +// ( +// lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)), +// "".into() +// ), +// ( +// lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)), +// "".into() +// ), +// ( +// lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)), +// "\n".into() +// ), +// ] +// ); + +// // Insert blank lines between each line of the buffer. +// async move { +// Ok(Some(vec![ +// lsp::TextEdit { +// range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)), +// new_text: "\n".into(), +// }, +// lsp::TextEdit { +// range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)), +// new_text: "\n".into(), +// }, +// ])) +// } +// } +// }); + +// // After formatting the buffer, the trailing whitespace is stripped, +// // a newline is appended, and the edits provided by the language server +// // have been applied. +// format.await.unwrap(); +// cx.assert_editor_state( +// &[ +// "one", // +// "", // +// "twoˇ", // +// "", // +// "three", // +// "four", // +// "", // +// ] +// .join("\n"), +// ); + +// // Undoing the formatting undoes the trailing whitespace removal, the +// // trailing newline, and the LSP edits. +// cx.update_buffer(|buffer, cx| buffer.undo(cx)); +// cx.assert_editor_state( +// &[ +// "one ", // +// "twoˇ", // +// "three ", // +// "four", // +// ] +// .join("\n"), +// ); +// } + +// #[gpui::test] +// async fn test_completion(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorLspTestContext::new_rust( +// lsp::ServerCapabilities { +// completion_provider: Some(lsp::CompletionOptions { +// trigger_characters: Some(vec![".".to_string(), ":".to_string()]), +// resolve_provider: Some(true), +// ..Default::default() +// }), +// ..Default::default() +// }, +// cx, +// ) +// .await; + +// cx.set_state(indoc! {" +// oneˇ +// two +// three +// "}); +// cx.simulate_keystroke("."); +// handle_completion_request( +// &mut cx, +// indoc! {" +// one.|<> +// two +// three +// "}, +// vec!["first_completion", "second_completion"], +// ) +// .await; +// cx.condition(|editor, _| editor.context_menu_visible()) +// .await; +// let apply_additional_edits = cx.update_editor(|editor, cx| { +// editor.context_menu_next(&Default::default(), cx); +// editor +// .confirm_completion(&ConfirmCompletion::default(), cx) +// .unwrap() +// }); +// cx.assert_editor_state(indoc! {" +// one.second_completionˇ +// two +// three +// "}); + +// handle_resolve_completion_request( +// &mut cx, +// Some(vec![ +// ( +// //This overlaps with the primary completion edit which is +// //misbehavior from the LSP spec, test that we filter it out +// indoc! {" +// one.second_ˇcompletion +// two +// threeˇ +// "}, +// "overlapping additional edit", +// ), +// ( +// indoc! {" +// one.second_completion +// two +// threeˇ +// "}, +// "\nadditional edit", +// ), +// ]), +// ) +// .await; +// apply_additional_edits.await.unwrap(); +// cx.assert_editor_state(indoc! {" +// one.second_completionˇ +// two +// three +// additional edit +// "}); + +// cx.set_state(indoc! {" +// one.second_completion +// twoˇ +// threeˇ +// additional edit +// "}); +// cx.simulate_keystroke(" "); +// assert!(cx.editor(|e, _| e.context_menu.read().is_none())); +// cx.simulate_keystroke("s"); +// assert!(cx.editor(|e, _| e.context_menu.read().is_none())); + +// cx.assert_editor_state(indoc! {" +// one.second_completion +// two sˇ +// three sˇ +// additional edit +// "}); +// handle_completion_request( +// &mut cx, +// indoc! {" +// one.second_completion +// two s +// three +// additional edit +// "}, +// vec!["fourth_completion", "fifth_completion", "sixth_completion"], +// ) +// .await; +// cx.condition(|editor, _| editor.context_menu_visible()) +// .await; + +// cx.simulate_keystroke("i"); + +// handle_completion_request( +// &mut cx, +// indoc! {" +// one.second_completion +// two si +// three +// additional edit +// "}, +// vec!["fourth_completion", "fifth_completion", "sixth_completion"], +// ) +// .await; +// cx.condition(|editor, _| editor.context_menu_visible()) +// .await; + +// let apply_additional_edits = cx.update_editor(|editor, cx| { +// editor +// .confirm_completion(&ConfirmCompletion::default(), cx) +// .unwrap() +// }); +// cx.assert_editor_state(indoc! {" +// one.second_completion +// two sixth_completionˇ +// three sixth_completionˇ +// additional edit +// "}); + +// handle_resolve_completion_request(&mut cx, None).await; +// apply_additional_edits.await.unwrap(); + +// cx.update(|cx| { +// cx.update_global::(|settings, cx| { +// settings.update_user_settings::(cx, |settings| { +// settings.show_completions_on_input = Some(false); +// }); +// }) +// }); +// cx.set_state("editorˇ"); +// cx.simulate_keystroke("."); +// assert!(cx.editor(|e, _| e.context_menu.read().is_none())); +// cx.simulate_keystroke("c"); +// cx.simulate_keystroke("l"); +// cx.simulate_keystroke("o"); +// cx.assert_editor_state("editor.cloˇ"); +// assert!(cx.editor(|e, _| e.context_menu.read().is_none())); +// cx.update_editor(|editor, cx| { +// editor.show_completions(&ShowCompletions, cx); +// }); +// handle_completion_request(&mut cx, "editor.", vec!["close", "clobber"]).await; +// cx.condition(|editor, _| editor.context_menu_visible()) +// .await; +// let apply_additional_edits = cx.update_editor(|editor, cx| { +// editor +// .confirm_completion(&ConfirmCompletion::default(), cx) +// .unwrap() +// }); +// cx.assert_editor_state("editor.closeˇ"); +// handle_resolve_completion_request(&mut cx, None).await; +// apply_additional_edits.await.unwrap(); +// } + +// #[gpui::test] +// async fn test_toggle_comment(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); +// let mut cx = EditorTestContext::new(cx).await; +// let language = Arc::new(Language::new( +// LanguageConfig { +// line_comment: Some("// ".into()), +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// )); +// cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); + +// // If multiple selections intersect a line, the line is only toggled once. +// cx.set_state(indoc! {" +// fn a() { +// «//b(); +// ˇ»// «c(); +// //ˇ» d(); +// } +// "}); + +// cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx)); + +// cx.assert_editor_state(indoc! {" +// fn a() { +// «b(); +// c(); +// ˇ» d(); +// } +// "}); + +// // The comment prefix is inserted at the same column for every line in a +// // selection. +// cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx)); + +// cx.assert_editor_state(indoc! {" +// fn a() { +// // «b(); +// // c(); +// ˇ»// d(); +// } +// "}); + +// // If a selection ends at the beginning of a line, that line is not toggled. +// cx.set_selections_state(indoc! {" +// fn a() { +// // b(); +// «// c(); +// ˇ» // d(); +// } +// "}); + +// cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx)); + +// cx.assert_editor_state(indoc! {" +// fn a() { +// // b(); +// «c(); +// ˇ» // d(); +// } +// "}); + +// // If a selection span a single line and is empty, the line is toggled. +// cx.set_state(indoc! {" +// fn a() { +// a(); +// b(); +// ˇ +// } +// "}); + +// cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx)); + +// cx.assert_editor_state(indoc! {" +// fn a() { +// a(); +// b(); +// //•ˇ +// } +// "}); + +// // If a selection span multiple lines, empty lines are not toggled. +// cx.set_state(indoc! {" +// fn a() { +// «a(); + +// c();ˇ» +// } +// "}); + +// cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx)); + +// cx.assert_editor_state(indoc! {" +// fn a() { +// // «a(); + +// // c();ˇ» +// } +// "}); +// } + +// #[gpui::test] +// async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let language = Arc::new(Language::new( +// LanguageConfig { +// line_comment: Some("// ".into()), +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// )); + +// let registry = Arc::new(LanguageRegistry::test()); +// registry.add(language.clone()); + +// let mut cx = EditorTestContext::new(cx).await; +// cx.update_buffer(|buffer, cx| { +// buffer.set_language_registry(registry); +// buffer.set_language(Some(language), cx); +// }); + +// let toggle_comments = &ToggleComments { +// advance_downwards: true, +// }; + +// // Single cursor on one line -> advance +// // Cursor moves horizontally 3 characters as well on non-blank line +// cx.set_state(indoc!( +// "fn a() { +// ˇdog(); +// cat(); +// }" +// )); +// cx.update_editor(|editor, cx| { +// editor.toggle_comments(toggle_comments, cx); +// }); +// cx.assert_editor_state(indoc!( +// "fn a() { +// // dog(); +// catˇ(); +// }" +// )); + +// // Single selection on one line -> don't advance +// cx.set_state(indoc!( +// "fn a() { +// «dog()ˇ»; +// cat(); +// }" +// )); +// cx.update_editor(|editor, cx| { +// editor.toggle_comments(toggle_comments, cx); +// }); +// cx.assert_editor_state(indoc!( +// "fn a() { +// // «dog()ˇ»; +// cat(); +// }" +// )); + +// // Multiple cursors on one line -> advance +// cx.set_state(indoc!( +// "fn a() { +// ˇdˇog(); +// cat(); +// }" +// )); +// cx.update_editor(|editor, cx| { +// editor.toggle_comments(toggle_comments, cx); +// }); +// cx.assert_editor_state(indoc!( +// "fn a() { +// // dog(); +// catˇ(ˇ); +// }" +// )); + +// // Multiple cursors on one line, with selection -> don't advance +// cx.set_state(indoc!( +// "fn a() { +// ˇdˇog«()ˇ»; +// cat(); +// }" +// )); +// cx.update_editor(|editor, cx| { +// editor.toggle_comments(toggle_comments, cx); +// }); +// cx.assert_editor_state(indoc!( +// "fn a() { +// // ˇdˇog«()ˇ»; +// cat(); +// }" +// )); + +// // Single cursor on one line -> advance +// // Cursor moves to column 0 on blank line +// cx.set_state(indoc!( +// "fn a() { +// ˇdog(); + +// cat(); +// }" +// )); +// cx.update_editor(|editor, cx| { +// editor.toggle_comments(toggle_comments, cx); +// }); +// cx.assert_editor_state(indoc!( +// "fn a() { +// // dog(); +// ˇ +// cat(); +// }" +// )); + +// // Single cursor on one line -> advance +// // Cursor starts and ends at column 0 +// cx.set_state(indoc!( +// "fn a() { +// ˇ dog(); +// cat(); +// }" +// )); +// cx.update_editor(|editor, cx| { +// editor.toggle_comments(toggle_comments, cx); +// }); +// cx.assert_editor_state(indoc!( +// "fn a() { +// // dog(); +// ˇ cat(); +// }" +// )); +// } + +// #[gpui::test] +// async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorTestContext::new(cx).await; + +// let html_language = Arc::new( +// Language::new( +// LanguageConfig { +// name: "HTML".into(), +// block_comment: Some(("".into())), +// ..Default::default() +// }, +// Some(tree_sitter_html::language()), +// ) +// .with_injection_query( +// r#" +// (script_element +// (raw_text) @content +// (#set! "language" "javascript")) +// "#, +// ) +// .unwrap(), +// ); + +// let javascript_language = Arc::new(Language::new( +// LanguageConfig { +// name: "JavaScript".into(), +// line_comment: Some("// ".into()), +// ..Default::default() +// }, +// Some(tree_sitter_typescript::language_tsx()), +// )); + +// let registry = Arc::new(LanguageRegistry::test()); +// registry.add(html_language.clone()); +// registry.add(javascript_language.clone()); + +// cx.update_buffer(|buffer, cx| { +// buffer.set_language_registry(registry); +// buffer.set_language(Some(html_language), cx); +// }); + +// // Toggle comments for empty selections +// cx.set_state( +// &r#" +//

A

ˇ +//

B

ˇ +//

C

ˇ +// "# +// .unindent(), +// ); +// cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx)); +// cx.assert_editor_state( +// &r#" +// +// +// +// "# +// .unindent(), +// ); +// cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx)); +// cx.assert_editor_state( +// &r#" +//

A

ˇ +//

B

ˇ +//

C

ˇ +// "# +// .unindent(), +// ); + +// // Toggle comments for mixture of empty and non-empty selections, where +// // multiple selections occupy a given line. +// cx.set_state( +// &r#" +//

+//

ˇ»B

ˇ +//

+//

ˇ»D

ˇ +// "# +// .unindent(), +// ); + +// cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx)); +// cx.assert_editor_state( +// &r#" +// +// +// "# +// .unindent(), +// ); +// cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx)); +// cx.assert_editor_state( +// &r#" +//

+//

ˇ»B

ˇ +//

+//

ˇ»D

ˇ +// "# +// .unindent(), +// ); + +// // Toggle comments when different languages are active for different +// // selections. +// cx.set_state( +// &r#" +// ˇ +// "# +// .unindent(), +// ); +// cx.foreground().run_until_parked(); +// cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx)); +// cx.assert_editor_state( +// &r#" +// +// // ˇvar x = new Y(); +// +// "# +// .unindent(), +// ); +// } + +// #[gpui::test] +// fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(3, 4, 'a'))); +// let multibuffer = cx.add_model(|cx| { +// let mut multibuffer = MultiBuffer::new(0); +// multibuffer.push_excerpts( +// buffer.clone(), +// [ +// ExcerptRange { +// context: Point::new(0, 0)..Point::new(0, 4), +// primary: None, +// }, +// ExcerptRange { +// context: Point::new(1, 0)..Point::new(1, 4), +// primary: None, +// }, +// ], +// cx, +// ); +// assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb"); +// multibuffer +// }); + +// let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx); +// view.update(cx, |view, cx| { +// assert_eq!(view.text(cx), "aaaa\nbbbb"); +// view.change_selections(None, cx, |s| { +// s.select_ranges([ +// Point::new(0, 0)..Point::new(0, 0), +// Point::new(1, 0)..Point::new(1, 0), +// ]) +// }); + +// view.handle_input("X", cx); +// assert_eq!(view.text(cx), "Xaaaa\nXbbbb"); +// assert_eq!( +// view.selections.ranges(cx), +// [ +// Point::new(0, 1)..Point::new(0, 1), +// Point::new(1, 1)..Point::new(1, 1), +// ] +// ); + +// // Ensure the cursor's head is respected when deleting across an excerpt boundary. +// view.change_selections(None, cx, |s| { +// s.select_ranges([Point::new(0, 2)..Point::new(1, 2)]) +// }); +// view.backspace(&Default::default(), cx); +// assert_eq!(view.text(cx), "Xa\nbbb"); +// assert_eq!( +// view.selections.ranges(cx), +// [Point::new(1, 0)..Point::new(1, 0)] +// ); + +// view.change_selections(None, cx, |s| { +// s.select_ranges([Point::new(1, 1)..Point::new(0, 1)]) +// }); +// view.backspace(&Default::default(), cx); +// assert_eq!(view.text(cx), "X\nbb"); +// assert_eq!( +// view.selections.ranges(cx), +// [Point::new(0, 1)..Point::new(0, 1)] +// ); +// }); +// } + +// #[gpui::test] +// fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let markers = vec![('[', ']').into(), ('(', ')').into()]; +// let (initial_text, mut excerpt_ranges) = marked_text_ranges_by( +// indoc! {" +// [aaaa +// (bbbb] +// cccc)", +// }, +// markers.clone(), +// ); +// let excerpt_ranges = markers.into_iter().map(|marker| { +// let context = excerpt_ranges.remove(&marker).unwrap()[0].clone(); +// ExcerptRange { +// context, +// primary: None, +// } +// }); +// let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, initial_text)); +// let multibuffer = cx.add_model(|cx| { +// let mut multibuffer = MultiBuffer::new(0); +// multibuffer.push_excerpts(buffer, excerpt_ranges, cx); +// multibuffer +// }); + +// let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx); +// view.update(cx, |view, cx| { +// let (expected_text, selection_ranges) = marked_text_ranges( +// indoc! {" +// aaaa +// bˇbbb +// bˇbbˇb +// cccc" +// }, +// true, +// ); +// assert_eq!(view.text(cx), expected_text); +// view.change_selections(None, cx, |s| s.select_ranges(selection_ranges)); + +// view.handle_input("X", cx); + +// let (expected_text, expected_selections) = marked_text_ranges( +// indoc! {" +// aaaa +// bXˇbbXb +// bXˇbbXˇb +// cccc" +// }, +// false, +// ); +// assert_eq!(view.text(cx), expected_text); +// assert_eq!(view.selections.ranges(cx), expected_selections); + +// view.newline(&Newline, cx); +// let (expected_text, expected_selections) = marked_text_ranges( +// indoc! {" +// aaaa +// bX +// ˇbbX +// b +// bX +// ˇbbX +// ˇb +// cccc" +// }, +// false, +// ); +// assert_eq!(view.text(cx), expected_text); +// assert_eq!(view.selections.ranges(cx), expected_selections); +// }); +// } + +// #[gpui::test] +// fn test_refresh_selections(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(3, 4, 'a'))); +// let mut excerpt1_id = None; +// let multibuffer = cx.add_model(|cx| { +// let mut multibuffer = MultiBuffer::new(0); +// excerpt1_id = multibuffer +// .push_excerpts( +// buffer.clone(), +// [ +// ExcerptRange { +// context: Point::new(0, 0)..Point::new(1, 4), +// primary: None, +// }, +// ExcerptRange { +// context: Point::new(1, 0)..Point::new(2, 4), +// primary: None, +// }, +// ], +// cx, +// ) +// .into_iter() +// .next(); +// assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc"); +// multibuffer +// }); + +// let editor = cx +// .add_window(|cx| { +// let mut editor = build_editor(multibuffer.clone(), cx); +// let snapshot = editor.snapshot(cx); +// editor.change_selections(None, cx, |s| { +// s.select_ranges([Point::new(1, 3)..Point::new(1, 3)]) +// }); +// editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx); +// assert_eq!( +// editor.selections.ranges(cx), +// [ +// Point::new(1, 3)..Point::new(1, 3), +// Point::new(2, 1)..Point::new(2, 1), +// ] +// ); +// editor +// }) +// .root(cx); + +// // Refreshing selections is a no-op when excerpts haven't changed. +// editor.update(cx, |editor, cx| { +// editor.change_selections(None, cx, |s| s.refresh()); +// assert_eq!( +// editor.selections.ranges(cx), +// [ +// Point::new(1, 3)..Point::new(1, 3), +// Point::new(2, 1)..Point::new(2, 1), +// ] +// ); +// }); + +// multibuffer.update(cx, |multibuffer, cx| { +// multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx); +// }); +// editor.update(cx, |editor, cx| { +// // Removing an excerpt causes the first selection to become degenerate. +// assert_eq!( +// editor.selections.ranges(cx), +// [ +// Point::new(0, 0)..Point::new(0, 0), +// Point::new(0, 1)..Point::new(0, 1) +// ] +// ); + +// // Refreshing selections will relocate the first selection to the original buffer +// // location. +// editor.change_selections(None, cx, |s| s.refresh()); +// assert_eq!( +// editor.selections.ranges(cx), +// [ +// Point::new(0, 1)..Point::new(0, 1), +// Point::new(0, 3)..Point::new(0, 3) +// ] +// ); +// assert!(editor.selections.pending_anchor().is_some()); +// }); +// } + +// #[gpui::test] +// fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(3, 4, 'a'))); +// let mut excerpt1_id = None; +// let multibuffer = cx.add_model(|cx| { +// let mut multibuffer = MultiBuffer::new(0); +// excerpt1_id = multibuffer +// .push_excerpts( +// buffer.clone(), +// [ +// ExcerptRange { +// context: Point::new(0, 0)..Point::new(1, 4), +// primary: None, +// }, +// ExcerptRange { +// context: Point::new(1, 0)..Point::new(2, 4), +// primary: None, +// }, +// ], +// cx, +// ) +// .into_iter() +// .next(); +// assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc"); +// multibuffer +// }); + +// let editor = cx +// .add_window(|cx| { +// let mut editor = build_editor(multibuffer.clone(), cx); +// let snapshot = editor.snapshot(cx); +// editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx); +// assert_eq!( +// editor.selections.ranges(cx), +// [Point::new(1, 3)..Point::new(1, 3)] +// ); +// editor +// }) +// .root(cx); + +// multibuffer.update(cx, |multibuffer, cx| { +// multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx); +// }); +// editor.update(cx, |editor, cx| { +// assert_eq!( +// editor.selections.ranges(cx), +// [Point::new(0, 0)..Point::new(0, 0)] +// ); + +// // Ensure we don't panic when selections are refreshed and that the pending selection is finalized. +// editor.change_selections(None, cx, |s| s.refresh()); +// assert_eq!( +// editor.selections.ranges(cx), +// [Point::new(0, 3)..Point::new(0, 3)] +// ); +// assert!(editor.selections.pending_anchor().is_some()); +// }); +// } + +// #[gpui::test] +// async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let language = Arc::new( +// Language::new( +// LanguageConfig { +// brackets: BracketPairConfig { +// pairs: vec![ +// BracketPair { +// start: "{".to_string(), +// end: "}".to_string(), +// close: true, +// newline: true, +// }, +// BracketPair { +// start: "/* ".to_string(), +// end: " */".to_string(), +// close: true, +// newline: true, +// }, +// ], +// ..Default::default() +// }, +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ) +// .with_indents_query("") +// .unwrap(), +// ); + +// let text = concat!( +// "{ }\n", // +// " x\n", // +// " /* */\n", // +// "x\n", // +// "{{} }\n", // +// ); + +// let buffer = +// cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx)); +// let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); +// let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); +// view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx)) +// .await; + +// view.update(cx, |view, cx| { +// view.change_selections(None, cx, |s| { +// s.select_display_ranges([ +// DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3), +// DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5), +// DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4), +// ]) +// }); +// view.newline(&Newline, cx); + +// assert_eq!( +// view.buffer().read(cx).read(cx).text(), +// concat!( +// "{ \n", // Suppress rustfmt +// "\n", // +// "}\n", // +// " x\n", // +// " /* \n", // +// " \n", // +// " */\n", // +// "x\n", // +// "{{} \n", // +// "}\n", // +// ) +// ); +// }); +// } + +// #[gpui::test] +// fn test_highlighted_ranges(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let editor = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx); +// build_editor(buffer.clone(), cx) +// }) +// .root(cx); + +// editor.update(cx, |editor, cx| { +// struct Type1; +// struct Type2; + +// let buffer = editor.buffer.read(cx).snapshot(cx); + +// let anchor_range = +// |range: Range| buffer.anchor_after(range.start)..buffer.anchor_after(range.end); + +// editor.highlight_background::( +// vec![ +// anchor_range(Point::new(2, 1)..Point::new(2, 3)), +// anchor_range(Point::new(4, 2)..Point::new(4, 4)), +// anchor_range(Point::new(6, 3)..Point::new(6, 5)), +// anchor_range(Point::new(8, 4)..Point::new(8, 6)), +// ], +// |_| Hsla::red(), +// cx, +// ); +// editor.highlight_background::( +// vec![ +// anchor_range(Point::new(3, 2)..Point::new(3, 5)), +// anchor_range(Point::new(5, 3)..Point::new(5, 6)), +// anchor_range(Point::new(7, 4)..Point::new(7, 7)), +// anchor_range(Point::new(9, 5)..Point::new(9, 8)), +// ], +// |_| Hsla::green(), +// cx, +// ); + +// let snapshot = editor.snapshot(cx); +// let mut highlighted_ranges = editor.background_highlights_in_range( +// anchor_range(Point::new(3, 4)..Point::new(7, 4)), +// &snapshot, +// theme::current(cx).as_ref(), +// ); +// // Enforce a consistent ordering based on color without relying on the ordering of the +// // highlight's `TypeId` which is non-deterministic. +// highlighted_ranges.sort_unstable_by_key(|(_, color)| *color); +// assert_eq!( +// highlighted_ranges, +// &[ +// ( +// DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5), +// Hsla::green(), +// ), +// ( +// DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6), +// Hsla::green(), +// ), +// ( +// DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4), +// Hsla::red(), +// ), +// ( +// DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5), +// Hsla::red(), +// ), +// ] +// ); +// assert_eq!( +// editor.background_highlights_in_range( +// anchor_range(Point::new(5, 6)..Point::new(6, 4)), +// &snapshot, +// theme::current(cx).as_ref(), +// ), +// &[( +// DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5), +// Hsla::red(), +// )] +// ); +// }); +// } + +// #[gpui::test] +// async fn test_following(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let fs = FakeFs::new(cx.background()); +// let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; + +// let buffer = project.update(cx, |project, cx| { +// let buffer = project +// .create_buffer(&sample_text(16, 8, 'a'), None, cx) +// .unwrap(); +// cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)) +// }); +// let leader = cx +// .add_window(|cx| build_editor(buffer.clone(), cx)) +// .root(cx); +// let follower = cx +// .update(|cx| { +// cx.add_window( +// WindowOptions { +// bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))), +// ..Default::default() +// }, +// |cx| build_editor(buffer.clone(), cx), +// ) +// }) +// .root(cx); + +// let is_still_following = Rc::new(RefCell::new(true)); +// let follower_edit_event_count = Rc::new(RefCell::new(0)); +// let pending_update = Rc::new(RefCell::new(None)); +// follower.update(cx, { +// let update = pending_update.clone(); +// let is_still_following = is_still_following.clone(); +// let follower_edit_event_count = follower_edit_event_count.clone(); +// |_, cx| { +// cx.subscribe(&leader, move |_, leader, event, cx| { +// leader +// .read(cx) +// .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx); +// }) +// .detach(); + +// cx.subscribe(&follower, move |_, _, event, cx| { +// if Editor::should_unfollow_on_event(event, cx) { +// *is_still_following.borrow_mut() = false; +// } +// if let Event::BufferEdited = event { +// *follower_edit_event_count.borrow_mut() += 1; +// } +// }) +// .detach(); +// } +// }); + +// // Update the selections only +// leader.update(cx, |leader, cx| { +// leader.change_selections(None, cx, |s| s.select_ranges([1..1])); +// }); +// follower +// .update(cx, |follower, cx| { +// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx) +// }) +// .await +// .unwrap(); +// follower.read_with(cx, |follower, cx| { +// assert_eq!(follower.selections.ranges(cx), vec![1..1]); +// }); +// assert_eq!(*is_still_following.borrow(), true); +// assert_eq!(*follower_edit_event_count.borrow(), 0); + +// // Update the scroll position only +// leader.update(cx, |leader, cx| { +// leader.set_scroll_position(vec2f(1.5, 3.5), cx); +// }); +// follower +// .update(cx, |follower, cx| { +// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx) +// }) +// .await +// .unwrap(); +// assert_eq!( +// follower.update(cx, |follower, cx| follower.scroll_position(cx)), +// vec2f(1.5, 3.5) +// ); +// assert_eq!(*is_still_following.borrow(), true); +// assert_eq!(*follower_edit_event_count.borrow(), 0); + +// // Update the selections and scroll position. The follower's scroll position is updated +// // via autoscroll, not via the leader's exact scroll position. +// leader.update(cx, |leader, cx| { +// leader.change_selections(None, cx, |s| s.select_ranges([0..0])); +// leader.request_autoscroll(Autoscroll::newest(), cx); +// leader.set_scroll_position(vec2f(1.5, 3.5), cx); +// }); +// follower +// .update(cx, |follower, cx| { +// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx) +// }) +// .await +// .unwrap(); +// follower.update(cx, |follower, cx| { +// assert_eq!(follower.scroll_position(cx), vec2f(1.5, 0.0)); +// assert_eq!(follower.selections.ranges(cx), vec![0..0]); +// }); +// assert_eq!(*is_still_following.borrow(), true); + +// // Creating a pending selection that precedes another selection +// leader.update(cx, |leader, cx| { +// leader.change_selections(None, cx, |s| s.select_ranges([1..1])); +// leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx); +// }); +// follower +// .update(cx, |follower, cx| { +// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx) +// }) +// .await +// .unwrap(); +// follower.read_with(cx, |follower, cx| { +// assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]); +// }); +// assert_eq!(*is_still_following.borrow(), true); + +// // Extend the pending selection so that it surrounds another selection +// leader.update(cx, |leader, cx| { +// leader.extend_selection(DisplayPoint::new(0, 2), 1, cx); +// }); +// follower +// .update(cx, |follower, cx| { +// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx) +// }) +// .await +// .unwrap(); +// follower.read_with(cx, |follower, cx| { +// assert_eq!(follower.selections.ranges(cx), vec![0..2]); +// }); + +// // Scrolling locally breaks the follow +// follower.update(cx, |follower, cx| { +// let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0); +// follower.set_scroll_anchor( +// ScrollAnchor { +// anchor: top_anchor, +// offset: vec2f(0.0, 0.5), +// }, +// cx, +// ); +// }); +// assert_eq!(*is_still_following.borrow(), false); +// } + +// #[gpui::test] +// async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let fs = FakeFs::new(cx.background()); +// let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; +// let workspace = cx +// .add_window(|cx| Workspace::test_new(project.clone(), cx)) +// .root(cx); +// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + +// let leader = pane.update(cx, |_, cx| { +// let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); +// cx.add_view(|cx| build_editor(multibuffer.clone(), cx)) +// }); + +// // Start following the editor when it has no excerpts. +// let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx)); +// let follower_1 = cx +// .update(|cx| { +// Editor::from_state_proto( +// pane.clone(), +// workspace.clone(), +// ViewId { +// creator: Default::default(), +// id: 0, +// }, +// &mut state_message, +// cx, +// ) +// }) +// .unwrap() +// .await +// .unwrap(); + +// let update_message = Rc::new(RefCell::new(None)); +// follower_1.update(cx, { +// let update = update_message.clone(); +// |_, cx| { +// cx.subscribe(&leader, move |_, leader, event, cx| { +// leader +// .read(cx) +// .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx); +// }) +// .detach(); +// } +// }); + +// let (buffer_1, buffer_2) = project.update(cx, |project, cx| { +// ( +// project +// .create_buffer("abc\ndef\nghi\njkl\n", None, cx) +// .unwrap(), +// project +// .create_buffer("mno\npqr\nstu\nvwx\n", None, cx) +// .unwrap(), +// ) +// }); + +// // Insert some excerpts. +// leader.update(cx, |leader, cx| { +// leader.buffer.update(cx, |multibuffer, cx| { +// let excerpt_ids = multibuffer.push_excerpts( +// buffer_1.clone(), +// [ +// ExcerptRange { +// context: 1..6, +// primary: None, +// }, +// ExcerptRange { +// context: 12..15, +// primary: None, +// }, +// ExcerptRange { +// context: 0..3, +// primary: None, +// }, +// ], +// cx, +// ); +// multibuffer.insert_excerpts_after( +// excerpt_ids[0], +// buffer_2.clone(), +// [ +// ExcerptRange { +// context: 8..12, +// primary: None, +// }, +// ExcerptRange { +// context: 0..6, +// primary: None, +// }, +// ], +// cx, +// ); +// }); +// }); + +// // Apply the update of adding the excerpts. +// follower_1 +// .update(cx, |follower, cx| { +// follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx) +// }) +// .await +// .unwrap(); +// assert_eq!( +// follower_1.read_with(cx, |editor, cx| editor.text(cx)), +// leader.read_with(cx, |editor, cx| editor.text(cx)) +// ); +// update_message.borrow_mut().take(); + +// // Start following separately after it already has excerpts. +// let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx)); +// let follower_2 = cx +// .update(|cx| { +// Editor::from_state_proto( +// pane.clone(), +// workspace.clone(), +// ViewId { +// creator: Default::default(), +// id: 0, +// }, +// &mut state_message, +// cx, +// ) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_eq!( +// follower_2.read_with(cx, |editor, cx| editor.text(cx)), +// leader.read_with(cx, |editor, cx| editor.text(cx)) +// ); + +// // Remove some excerpts. +// leader.update(cx, |leader, cx| { +// leader.buffer.update(cx, |multibuffer, cx| { +// let excerpt_ids = multibuffer.excerpt_ids(); +// multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx); +// multibuffer.remove_excerpts([excerpt_ids[0]], cx); +// }); +// }); + +// // Apply the update of removing the excerpts. +// follower_1 +// .update(cx, |follower, cx| { +// follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx) +// }) +// .await +// .unwrap(); +// follower_2 +// .update(cx, |follower, cx| { +// follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx) +// }) +// .await +// .unwrap(); +// update_message.borrow_mut().take(); +// assert_eq!( +// follower_1.read_with(cx, |editor, cx| editor.text(cx)), +// leader.read_with(cx, |editor, cx| editor.text(cx)) +// ); +// } + +// #[test] +// fn test_combine_syntax_and_fuzzy_match_highlights() { +// let string = "abcdefghijklmnop"; +// let syntax_ranges = [ +// ( +// 0..3, +// HighlightStyle { +// color: Some(Hsla::red()), +// ..Default::default() +// }, +// ), +// ( +// 4..8, +// HighlightStyle { +// color: Some(Hsla::green()), +// ..Default::default() +// }, +// ), +// ]; +// let match_indices = [4, 6, 7, 8]; +// assert_eq!( +// combine_syntax_and_fuzzy_match_highlights( +// string, +// Default::default(), +// syntax_ranges.into_iter(), +// &match_indices, +// ), +// &[ +// ( +// 0..3, +// HighlightStyle { +// color: Some(Hsla::red()), +// ..Default::default() +// }, +// ), +// ( +// 4..5, +// HighlightStyle { +// color: Some(Hsla::green()), +// weight: Some(fonts::Weight::BOLD), +// ..Default::default() +// }, +// ), +// ( +// 5..6, +// HighlightStyle { +// color: Some(Hsla::green()), +// ..Default::default() +// }, +// ), +// ( +// 6..8, +// HighlightStyle { +// color: Some(Hsla::green()), +// weight: Some(fonts::Weight::BOLD), +// ..Default::default() +// }, +// ), +// ( +// 8..9, +// HighlightStyle { +// weight: Some(fonts::Weight::BOLD), +// ..Default::default() +// }, +// ), +// ] +// ); +// } + +// #[gpui::test] +// async fn go_to_prev_overlapping_diagnostic( +// deterministic: Arc, +// cx: &mut gpui::TestAppContext, +// ) { +// init_test(cx, |_| {}); + +// let mut cx = EditorTestContext::new(cx).await; +// let project = cx.update_editor(|editor, _| editor.project.clone().unwrap()); + +// cx.set_state(indoc! {" +// ˇfn func(abc def: i32) -> u32 { +// } +// "}); + +// cx.update(|cx| { +// project.update(cx, |project, cx| { +// project +// .update_diagnostics( +// LanguageServerId(0), +// lsp::PublishDiagnosticsParams { +// uri: lsp::Url::from_file_path("/root/file").unwrap(), +// version: None, +// diagnostics: vec![ +// lsp::Diagnostic { +// range: lsp::Range::new( +// lsp::Position::new(0, 11), +// lsp::Position::new(0, 12), +// ), +// severity: Some(lsp::DiagnosticSeverity::ERROR), +// ..Default::default() +// }, +// lsp::Diagnostic { +// range: lsp::Range::new( +// lsp::Position::new(0, 12), +// lsp::Position::new(0, 15), +// ), +// severity: Some(lsp::DiagnosticSeverity::ERROR), +// ..Default::default() +// }, +// lsp::Diagnostic { +// range: lsp::Range::new( +// lsp::Position::new(0, 25), +// lsp::Position::new(0, 28), +// ), +// severity: Some(lsp::DiagnosticSeverity::ERROR), +// ..Default::default() +// }, +// ], +// }, +// &[], +// cx, +// ) +// .unwrap() +// }); +// }); + +// deterministic.run_until_parked(); + +// cx.update_editor(|editor, cx| { +// editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx); +// }); + +// cx.assert_editor_state(indoc! {" +// fn func(abc def: i32) -> ˇu32 { +// } +// "}); + +// cx.update_editor(|editor, cx| { +// editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx); +// }); + +// cx.assert_editor_state(indoc! {" +// fn func(abc ˇdef: i32) -> u32 { +// } +// "}); + +// cx.update_editor(|editor, cx| { +// editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx); +// }); + +// cx.assert_editor_state(indoc! {" +// fn func(abcˇ def: i32) -> u32 { +// } +// "}); + +// cx.update_editor(|editor, cx| { +// editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx); +// }); + +// cx.assert_editor_state(indoc! {" +// fn func(abc def: i32) -> ˇu32 { +// } +// "}); +// } + +// #[gpui::test] +// async fn go_to_hunk(deterministic: Arc, cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorTestContext::new(cx).await; + +// let diff_base = r#" +// use some::mod; + +// const A: u32 = 42; + +// fn main() { +// println!("hello"); + +// println!("world"); +// } +// "# +// .unindent(); + +// // Edits are modified, removed, modified, added +// cx.set_state( +// &r#" +// use some::modified; + +// ˇ +// fn main() { +// println!("hello there"); + +// println!("around the"); +// println!("world"); +// } +// "# +// .unindent(), +// ); + +// cx.set_diff_base(Some(&diff_base)); +// deterministic.run_until_parked(); + +// cx.update_editor(|editor, cx| { +// //Wrap around the bottom of the buffer +// for _ in 0..3 { +// editor.go_to_hunk(&GoToHunk, cx); +// } +// }); + +// cx.assert_editor_state( +// &r#" +// ˇuse some::modified; + +// fn main() { +// println!("hello there"); + +// println!("around the"); +// println!("world"); +// } +// "# +// .unindent(), +// ); + +// cx.update_editor(|editor, cx| { +// //Wrap around the top of the buffer +// for _ in 0..2 { +// editor.go_to_prev_hunk(&GoToPrevHunk, cx); +// } +// }); + +// cx.assert_editor_state( +// &r#" +// use some::modified; + +// fn main() { +// ˇ println!("hello there"); + +// println!("around the"); +// println!("world"); +// } +// "# +// .unindent(), +// ); + +// cx.update_editor(|editor, cx| { +// editor.go_to_prev_hunk(&GoToPrevHunk, cx); +// }); + +// cx.assert_editor_state( +// &r#" +// use some::modified; + +// ˇ +// fn main() { +// println!("hello there"); + +// println!("around the"); +// println!("world"); +// } +// "# +// .unindent(), +// ); + +// cx.update_editor(|editor, cx| { +// for _ in 0..3 { +// editor.go_to_prev_hunk(&GoToPrevHunk, cx); +// } +// }); + +// cx.assert_editor_state( +// &r#" +// use some::modified; + +// fn main() { +// ˇ println!("hello there"); + +// println!("around the"); +// println!("world"); +// } +// "# +// .unindent(), +// ); + +// cx.update_editor(|editor, cx| { +// editor.fold(&Fold, cx); + +// //Make sure that the fold only gets one hunk +// for _ in 0..4 { +// editor.go_to_hunk(&GoToHunk, cx); +// } +// }); + +// cx.assert_editor_state( +// &r#" +// ˇuse some::modified; + +// fn main() { +// println!("hello there"); + +// println!("around the"); +// println!("world"); +// } +// "# +// .unindent(), +// ); +// } + +// #[test] +// fn test_split_words() { +// fn split<'a>(text: &'a str) -> Vec<&'a str> { +// split_words(text).collect() +// } + +// assert_eq!(split("HelloWorld"), &["Hello", "World"]); +// assert_eq!(split("hello_world"), &["hello_", "world"]); +// assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]); +// assert_eq!(split("Hello_World"), &["Hello_", "World"]); +// assert_eq!(split("helloWOrld"), &["hello", "WOrld"]); +// assert_eq!(split("helloworld"), &["helloworld"]); +// } + +// #[gpui::test] +// async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await; +// let mut assert = |before, after| { +// let _state_context = cx.set_state(before); +// cx.update_editor(|editor, cx| { +// editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx) +// }); +// cx.assert_editor_state(after); +// }; + +// // Outside bracket jumps to outside of matching bracket +// assert("console.logˇ(var);", "console.log(var)ˇ;"); +// assert("console.log(var)ˇ;", "console.logˇ(var);"); + +// // Inside bracket jumps to inside of matching bracket +// assert("console.log(ˇvar);", "console.log(varˇ);"); +// assert("console.log(varˇ);", "console.log(ˇvar);"); + +// // When outside a bracket and inside, favor jumping to the inside bracket +// assert( +// "console.log('foo', [1, 2, 3]ˇ);", +// "console.log(ˇ'foo', [1, 2, 3]);", +// ); +// assert( +// "console.log(ˇ'foo', [1, 2, 3]);", +// "console.log('foo', [1, 2, 3]ˇ);", +// ); + +// // Bias forward if two options are equally likely +// assert( +// "let result = curried_fun()ˇ();", +// "let result = curried_fun()()ˇ;", +// ); + +// // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller +// assert( +// indoc! {" +// function test() { +// console.log('test')ˇ +// }"}, +// indoc! {" +// function test() { +// console.logˇ('test') +// }"}, +// ); +// } + +// #[gpui::test(iterations = 10)] +// async fn test_copilot(deterministic: Arc, cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let (copilot, copilot_lsp) = Copilot::fake(cx); +// cx.update(|cx| cx.set_global(copilot)); +// let mut cx = EditorLspTestContext::new_rust( +// lsp::ServerCapabilities { +// completion_provider: Some(lsp::CompletionOptions { +// trigger_characters: Some(vec![".".to_string(), ":".to_string()]), +// ..Default::default() +// }), +// ..Default::default() +// }, +// cx, +// ) +// .await; + +// // When inserting, ensure autocompletion is favored over Copilot suggestions. +// cx.set_state(indoc! {" +// oneˇ +// two +// three +// "}); +// cx.simulate_keystroke("."); +// let _ = handle_completion_request( +// &mut cx, +// indoc! {" +// one.|<> +// two +// three +// "}, +// vec!["completion_a", "completion_b"], +// ); +// handle_copilot_completion_request( +// &copilot_lsp, +// vec![copilot::request::Completion { +// text: "one.copilot1".into(), +// range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)), +// ..Default::default() +// }], +// vec![], +// ); +// deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); +// cx.update_editor(|editor, cx| { +// assert!(editor.context_menu_visible()); +// assert!(!editor.has_active_copilot_suggestion(cx)); + +// // Confirming a completion inserts it and hides the context menu, without showing +// // the copilot suggestion afterwards. +// editor +// .confirm_completion(&Default::default(), cx) +// .unwrap() +// .detach(); +// assert!(!editor.context_menu_visible()); +// assert!(!editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n"); +// assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n"); +// }); + +// // Ensure Copilot suggestions are shown right away if no autocompletion is available. +// cx.set_state(indoc! {" +// oneˇ +// two +// three +// "}); +// cx.simulate_keystroke("."); +// let _ = handle_completion_request( +// &mut cx, +// indoc! {" +// one.|<> +// two +// three +// "}, +// vec![], +// ); +// handle_copilot_completion_request( +// &copilot_lsp, +// vec![copilot::request::Completion { +// text: "one.copilot1".into(), +// range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)), +// ..Default::default() +// }], +// vec![], +// ); +// deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); +// cx.update_editor(|editor, cx| { +// assert!(!editor.context_menu_visible()); +// assert!(editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n"); +// assert_eq!(editor.text(cx), "one.\ntwo\nthree\n"); +// }); + +// // Reset editor, and ensure autocompletion is still favored over Copilot suggestions. +// cx.set_state(indoc! {" +// oneˇ +// two +// three +// "}); +// cx.simulate_keystroke("."); +// let _ = handle_completion_request( +// &mut cx, +// indoc! {" +// one.|<> +// two +// three +// "}, +// vec!["completion_a", "completion_b"], +// ); +// handle_copilot_completion_request( +// &copilot_lsp, +// vec![copilot::request::Completion { +// text: "one.copilot1".into(), +// range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)), +// ..Default::default() +// }], +// vec![], +// ); +// deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); +// cx.update_editor(|editor, cx| { +// assert!(editor.context_menu_visible()); +// assert!(!editor.has_active_copilot_suggestion(cx)); + +// // When hiding the context menu, the Copilot suggestion becomes visible. +// editor.hide_context_menu(cx); +// assert!(!editor.context_menu_visible()); +// assert!(editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n"); +// assert_eq!(editor.text(cx), "one.\ntwo\nthree\n"); +// }); + +// // Ensure existing completion is interpolated when inserting again. +// cx.simulate_keystroke("c"); +// deterministic.run_until_parked(); +// cx.update_editor(|editor, cx| { +// assert!(!editor.context_menu_visible()); +// assert!(editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n"); +// assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n"); +// }); + +// // After debouncing, new Copilot completions should be requested. +// handle_copilot_completion_request( +// &copilot_lsp, +// vec![copilot::request::Completion { +// text: "one.copilot2".into(), +// range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)), +// ..Default::default() +// }], +// vec![], +// ); +// deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); +// cx.update_editor(|editor, cx| { +// assert!(!editor.context_menu_visible()); +// assert!(editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n"); +// assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n"); + +// // Canceling should remove the active Copilot suggestion. +// editor.cancel(&Default::default(), cx); +// assert!(!editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n"); +// assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n"); + +// // After canceling, tabbing shouldn't insert the previously shown suggestion. +// editor.tab(&Default::default(), cx); +// assert!(!editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n"); +// assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n"); + +// // When undoing the previously active suggestion is shown again. +// editor.undo(&Default::default(), cx); +// assert!(editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n"); +// assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n"); +// }); + +// // If an edit occurs outside of this editor, the suggestion is still correctly interpolated. +// cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx)); +// cx.update_editor(|editor, cx| { +// assert!(editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n"); +// assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n"); + +// // Tabbing when there is an active suggestion inserts it. +// editor.tab(&Default::default(), cx); +// assert!(!editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n"); +// assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n"); + +// // When undoing the previously active suggestion is shown again. +// editor.undo(&Default::default(), cx); +// assert!(editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n"); +// assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n"); + +// // Hide suggestion. +// editor.cancel(&Default::default(), cx); +// assert!(!editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n"); +// assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n"); +// }); + +// // If an edit occurs outside of this editor but no suggestion is being shown, +// // we won't make it visible. +// cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx)); +// cx.update_editor(|editor, cx| { +// assert!(!editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n"); +// assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n"); +// }); + +// // Reset the editor to verify how suggestions behave when tabbing on leading indentation. +// cx.update_editor(|editor, cx| { +// editor.set_text("fn foo() {\n \n}", cx); +// editor.change_selections(None, cx, |s| { +// s.select_ranges([Point::new(1, 2)..Point::new(1, 2)]) +// }); +// }); +// handle_copilot_completion_request( +// &copilot_lsp, +// vec![copilot::request::Completion { +// text: " let x = 4;".into(), +// range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)), +// ..Default::default() +// }], +// vec![], +// ); + +// cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx)); +// deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); +// cx.update_editor(|editor, cx| { +// assert!(editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}"); +// assert_eq!(editor.text(cx), "fn foo() {\n \n}"); + +// // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion. +// editor.tab(&Default::default(), cx); +// assert!(editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.text(cx), "fn foo() {\n \n}"); +// assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}"); + +// // Tabbing again accepts the suggestion. +// editor.tab(&Default::default(), cx); +// assert!(!editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}"); +// assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}"); +// }); +// } + +// #[gpui::test] +// async fn test_copilot_completion_invalidation( +// deterministic: Arc, +// cx: &mut gpui::TestAppContext, +// ) { +// init_test(cx, |_| {}); + +// let (copilot, copilot_lsp) = Copilot::fake(cx); +// cx.update(|cx| cx.set_global(copilot)); +// let mut cx = EditorLspTestContext::new_rust( +// lsp::ServerCapabilities { +// completion_provider: Some(lsp::CompletionOptions { +// trigger_characters: Some(vec![".".to_string(), ":".to_string()]), +// ..Default::default() +// }), +// ..Default::default() +// }, +// cx, +// ) +// .await; + +// cx.set_state(indoc! {" +// one +// twˇ +// three +// "}); + +// handle_copilot_completion_request( +// &copilot_lsp, +// vec![copilot::request::Completion { +// text: "two.foo()".into(), +// range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)), +// ..Default::default() +// }], +// vec![], +// ); +// cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx)); +// deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); +// cx.update_editor(|editor, cx| { +// assert!(editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n"); +// assert_eq!(editor.text(cx), "one\ntw\nthree\n"); + +// editor.backspace(&Default::default(), cx); +// assert!(editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n"); +// assert_eq!(editor.text(cx), "one\nt\nthree\n"); + +// editor.backspace(&Default::default(), cx); +// assert!(editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n"); +// assert_eq!(editor.text(cx), "one\n\nthree\n"); + +// // Deleting across the original suggestion range invalidates it. +// editor.backspace(&Default::default(), cx); +// assert!(!editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one\nthree\n"); +// assert_eq!(editor.text(cx), "one\nthree\n"); + +// // Undoing the deletion restores the suggestion. +// editor.undo(&Default::default(), cx); +// assert!(editor.has_active_copilot_suggestion(cx)); +// assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n"); +// assert_eq!(editor.text(cx), "one\n\nthree\n"); +// }); +// } + +// #[gpui::test] +// async fn test_copilot_multibuffer( +// deterministic: Arc, +// cx: &mut gpui::TestAppContext, +// ) { +// init_test(cx, |_| {}); + +// let (copilot, copilot_lsp) = Copilot::fake(cx); +// cx.update(|cx| cx.set_global(copilot)); + +// let buffer_1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "a = 1\nb = 2\n")); +// let buffer_2 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "c = 3\nd = 4\n")); +// let multibuffer = cx.add_model(|cx| { +// let mut multibuffer = MultiBuffer::new(0); +// multibuffer.push_excerpts( +// buffer_1.clone(), +// [ExcerptRange { +// context: Point::new(0, 0)..Point::new(2, 0), +// primary: None, +// }], +// cx, +// ); +// multibuffer.push_excerpts( +// buffer_2.clone(), +// [ExcerptRange { +// context: Point::new(0, 0)..Point::new(2, 0), +// primary: None, +// }], +// cx, +// ); +// multibuffer +// }); +// let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx); + +// handle_copilot_completion_request( +// &copilot_lsp, +// vec![copilot::request::Completion { +// text: "b = 2 + a".into(), +// range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)), +// ..Default::default() +// }], +// vec![], +// ); +// editor.update(cx, |editor, cx| { +// // Ensure copilot suggestions are shown for the first excerpt. +// editor.change_selections(None, cx, |s| { +// s.select_ranges([Point::new(1, 5)..Point::new(1, 5)]) +// }); +// editor.next_copilot_suggestion(&Default::default(), cx); +// }); +// deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); +// editor.update(cx, |editor, cx| { +// assert!(editor.has_active_copilot_suggestion(cx)); +// assert_eq!( +// editor.display_text(cx), +// "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n" +// ); +// assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n"); +// }); + +// handle_copilot_completion_request( +// &copilot_lsp, +// vec![copilot::request::Completion { +// text: "d = 4 + c".into(), +// range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)), +// ..Default::default() +// }], +// vec![], +// ); +// editor.update(cx, |editor, cx| { +// // Move to another excerpt, ensuring the suggestion gets cleared. +// editor.change_selections(None, cx, |s| { +// s.select_ranges([Point::new(4, 5)..Point::new(4, 5)]) +// }); +// assert!(!editor.has_active_copilot_suggestion(cx)); +// assert_eq!( +// editor.display_text(cx), +// "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n" +// ); +// assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n"); + +// // Type a character, ensuring we don't even try to interpolate the previous suggestion. +// editor.handle_input(" ", cx); +// assert!(!editor.has_active_copilot_suggestion(cx)); +// assert_eq!( +// editor.display_text(cx), +// "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n" +// ); +// assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n"); +// }); + +// // Ensure the new suggestion is displayed when the debounce timeout expires. +// deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); +// editor.update(cx, |editor, cx| { +// assert!(editor.has_active_copilot_suggestion(cx)); +// assert_eq!( +// editor.display_text(cx), +// "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n" +// ); +// assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n"); +// }); +// } + +// #[gpui::test] +// async fn test_copilot_disabled_globs( +// deterministic: Arc, +// cx: &mut gpui::TestAppContext, +// ) { +// init_test(cx, |settings| { +// settings +// .copilot +// .get_or_insert(Default::default()) +// .disabled_globs = Some(vec![".env*".to_string()]); +// }); + +// let (copilot, copilot_lsp) = Copilot::fake(cx); +// cx.update(|cx| cx.set_global(copilot)); + +// let fs = FakeFs::new(cx.background()); +// fs.insert_tree( +// "/test", +// json!({ +// ".env": "SECRET=something\n", +// "README.md": "hello\n" +// }), +// ) +// .await; +// let project = Project::test(fs, ["/test".as_ref()], cx).await; + +// let private_buffer = project +// .update(cx, |project, cx| { +// project.open_local_buffer("/test/.env", cx) +// }) +// .await +// .unwrap(); +// let public_buffer = project +// .update(cx, |project, cx| { +// project.open_local_buffer("/test/README.md", cx) +// }) +// .await +// .unwrap(); + +// let multibuffer = cx.add_model(|cx| { +// let mut multibuffer = MultiBuffer::new(0); +// multibuffer.push_excerpts( +// private_buffer.clone(), +// [ExcerptRange { +// context: Point::new(0, 0)..Point::new(1, 0), +// primary: None, +// }], +// cx, +// ); +// multibuffer.push_excerpts( +// public_buffer.clone(), +// [ExcerptRange { +// context: Point::new(0, 0)..Point::new(1, 0), +// primary: None, +// }], +// cx, +// ); +// multibuffer +// }); +// let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx); + +// let mut copilot_requests = copilot_lsp +// .handle_request::(move |_params, _cx| async move { +// Ok(copilot::request::GetCompletionsResult { +// completions: vec![copilot::request::Completion { +// text: "next line".into(), +// range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)), +// ..Default::default() +// }], +// }) +// }); + +// editor.update(cx, |editor, cx| { +// editor.change_selections(None, cx, |selections| { +// selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)]) +// }); +// editor.next_copilot_suggestion(&Default::default(), cx); +// }); + +// deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); +// assert!(copilot_requests.try_next().is_err()); + +// editor.update(cx, |editor, cx| { +// editor.change_selections(None, cx, |s| { +// s.select_ranges([Point::new(2, 0)..Point::new(2, 0)]) +// }); +// editor.next_copilot_suggestion(&Default::default(), cx); +// }); + +// deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); +// assert!(copilot_requests.try_next().is_ok()); +// } + +// #[gpui::test] +// async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut language = Language::new( +// LanguageConfig { +// name: "Rust".into(), +// path_suffixes: vec!["rs".to_string()], +// brackets: BracketPairConfig { +// pairs: vec![BracketPair { +// start: "{".to_string(), +// end: "}".to_string(), +// close: true, +// newline: true, +// }], +// disabled_scopes_by_bracket_ix: Vec::new(), +// }, +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ); +// let mut fake_servers = language +// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { +// capabilities: lsp::ServerCapabilities { +// document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions { +// first_trigger_character: "{".to_string(), +// more_trigger_character: None, +// }), +// ..Default::default() +// }, +// ..Default::default() +// })) +// .await; + +// let fs = FakeFs::new(cx.background()); +// fs.insert_tree( +// "/a", +// json!({ +// "main.rs": "fn main() { let a = 5; }", +// "other.rs": "// Test file", +// }), +// ) +// .await; +// let project = Project::test(fs, ["/a".as_ref()], cx).await; +// project.update(cx, |project, _| project.languages().add(Arc::new(language))); +// let workspace = cx +// .add_window(|cx| Workspace::test_new(project.clone(), cx)) +// .root(cx); +// let worktree_id = workspace.update(cx, |workspace, cx| { +// workspace.project().read_with(cx, |project, cx| { +// project.worktrees(cx).next().unwrap().read(cx).id() +// }) +// }); + +// let buffer = project +// .update(cx, |project, cx| { +// project.open_local_buffer("/a/main.rs", cx) +// }) +// .await +// .unwrap(); +// cx.foreground().run_until_parked(); +// cx.foreground().start_waiting(); +// let fake_server = fake_servers.next().await.unwrap(); +// let editor_handle = workspace +// .update(cx, |workspace, cx| { +// workspace.open_path((worktree_id, "main.rs"), None, true, cx) +// }) +// .await +// .unwrap() +// .downcast::() +// .unwrap(); + +// fake_server.handle_request::(|params, _| async move { +// assert_eq!( +// params.text_document_position.text_document.uri, +// lsp::Url::from_file_path("/a/main.rs").unwrap(), +// ); +// assert_eq!( +// params.text_document_position.position, +// lsp::Position::new(0, 21), +// ); + +// Ok(Some(vec![lsp::TextEdit { +// new_text: "]".to_string(), +// range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)), +// }])) +// }); + +// editor_handle.update(cx, |editor, cx| { +// cx.focus(&editor_handle); +// editor.change_selections(None, cx, |s| { +// s.select_ranges([Point::new(0, 21)..Point::new(0, 20)]) +// }); +// editor.handle_input("{", cx); +// }); + +// cx.foreground().run_until_parked(); + +// buffer.read_with(cx, |buffer, _| { +// assert_eq!( +// buffer.text(), +// "fn main() { let a = {5}; }", +// "No extra braces from on type formatting should appear in the buffer" +// ) +// }); +// } + +// #[gpui::test] +// async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let language_name: Arc = "Rust".into(); +// let mut language = Language::new( +// LanguageConfig { +// name: Arc::clone(&language_name), +// path_suffixes: vec!["rs".to_string()], +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ); + +// let server_restarts = Arc::new(AtomicUsize::new(0)); +// let closure_restarts = Arc::clone(&server_restarts); +// let language_server_name = "test language server"; +// let mut fake_servers = language +// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { +// name: language_server_name, +// initialization_options: Some(json!({ +// "testOptionValue": true +// })), +// initializer: Some(Box::new(move |fake_server| { +// let task_restarts = Arc::clone(&closure_restarts); +// fake_server.handle_request::(move |_, _| { +// task_restarts.fetch_add(1, atomic::Ordering::Release); +// futures::future::ready(Ok(())) +// }); +// })), +// ..Default::default() +// })) +// .await; + +// let fs = FakeFs::new(cx.background()); +// fs.insert_tree( +// "/a", +// json!({ +// "main.rs": "fn main() { let a = 5; }", +// "other.rs": "// Test file", +// }), +// ) +// .await; +// let project = Project::test(fs, ["/a".as_ref()], cx).await; +// project.update(cx, |project, _| project.languages().add(Arc::new(language))); +// let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let _buffer = project +// .update(cx, |project, cx| { +// project.open_local_buffer("/a/main.rs", cx) +// }) +// .await +// .unwrap(); +// let _fake_server = fake_servers.next().await.unwrap(); +// update_test_language_settings(cx, |language_settings| { +// language_settings.languages.insert( +// Arc::clone(&language_name), +// LanguageSettingsContent { +// tab_size: NonZeroU32::new(8), +// ..Default::default() +// }, +// ); +// }); +// cx.foreground().run_until_parked(); +// assert_eq!( +// server_restarts.load(atomic::Ordering::Acquire), +// 0, +// "Should not restart LSP server on an unrelated change" +// ); + +// update_test_project_settings(cx, |project_settings| { +// project_settings.lsp.insert( +// "Some other server name".into(), +// LspSettings { +// initialization_options: Some(json!({ +// "some other init value": false +// })), +// }, +// ); +// }); +// cx.foreground().run_until_parked(); +// assert_eq!( +// server_restarts.load(atomic::Ordering::Acquire), +// 0, +// "Should not restart LSP server on an unrelated LSP settings change" +// ); + +// update_test_project_settings(cx, |project_settings| { +// project_settings.lsp.insert( +// language_server_name.into(), +// LspSettings { +// initialization_options: Some(json!({ +// "anotherInitValue": false +// })), +// }, +// ); +// }); +// cx.foreground().run_until_parked(); +// assert_eq!( +// server_restarts.load(atomic::Ordering::Acquire), +// 1, +// "Should restart LSP server on a related LSP settings change" +// ); + +// update_test_project_settings(cx, |project_settings| { +// project_settings.lsp.insert( +// language_server_name.into(), +// LspSettings { +// initialization_options: Some(json!({ +// "anotherInitValue": false +// })), +// }, +// ); +// }); +// cx.foreground().run_until_parked(); +// assert_eq!( +// server_restarts.load(atomic::Ordering::Acquire), +// 1, +// "Should not restart LSP server on a related LSP settings change that is the same" +// ); + +// update_test_project_settings(cx, |project_settings| { +// project_settings.lsp.insert( +// language_server_name.into(), +// LspSettings { +// initialization_options: None, +// }, +// ); +// }); +// cx.foreground().run_until_parked(); +// assert_eq!( +// server_restarts.load(atomic::Ordering::Acquire), +// 2, +// "Should restart LSP server on another related LSP settings change" +// ); +// } + +// #[gpui::test] +// async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorLspTestContext::new_rust( +// lsp::ServerCapabilities { +// completion_provider: Some(lsp::CompletionOptions { +// trigger_characters: Some(vec![".".to_string()]), +// resolve_provider: Some(true), +// ..Default::default() +// }), +// ..Default::default() +// }, +// cx, +// ) +// .await; + +// cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"}); +// cx.simulate_keystroke("."); +// let completion_item = lsp::CompletionItem { +// label: "some".into(), +// kind: Some(lsp::CompletionItemKind::SNIPPET), +// detail: Some("Wrap the expression in an `Option::Some`".to_string()), +// documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { +// kind: lsp::MarkupKind::Markdown, +// value: "```rust\nSome(2)\n```".to_string(), +// })), +// deprecated: Some(false), +// sort_text: Some("fffffff2".to_string()), +// filter_text: Some("some".to_string()), +// insert_text_format: Some(lsp::InsertTextFormat::SNIPPET), +// text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { +// range: lsp::Range { +// start: lsp::Position { +// line: 0, +// character: 22, +// }, +// end: lsp::Position { +// line: 0, +// character: 22, +// }, +// }, +// new_text: "Some(2)".to_string(), +// })), +// additional_text_edits: Some(vec![lsp::TextEdit { +// range: lsp::Range { +// start: lsp::Position { +// line: 0, +// character: 20, +// }, +// end: lsp::Position { +// line: 0, +// character: 22, +// }, +// }, +// new_text: "".to_string(), +// }]), +// ..Default::default() +// }; + +// let closure_completion_item = completion_item.clone(); +// let mut request = cx.handle_request::(move |_, _, _| { +// let task_completion_item = closure_completion_item.clone(); +// async move { +// Ok(Some(lsp::CompletionResponse::Array(vec![ +// task_completion_item, +// ]))) +// } +// }); + +// request.next().await; + +// cx.condition(|editor, _| editor.context_menu_visible()) +// .await; +// let apply_additional_edits = cx.update_editor(|editor, cx| { +// editor +// .confirm_completion(&ConfirmCompletion::default(), cx) +// .unwrap() +// }); +// cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"}); + +// cx.handle_request::(move |_, _, _| { +// let task_completion_item = completion_item.clone(); +// async move { Ok(task_completion_item) } +// }) +// .next() +// .await +// .unwrap(); +// apply_additional_edits.await.unwrap(); +// cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"}); +// } + +// #[gpui::test] +// async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorLspTestContext::new( +// Language::new( +// LanguageConfig { +// path_suffixes: vec!["jsx".into()], +// overrides: [( +// "element".into(), +// LanguageConfigOverride { +// word_characters: Override::Set(['-'].into_iter().collect()), +// ..Default::default() +// }, +// )] +// .into_iter() +// .collect(), +// ..Default::default() +// }, +// Some(tree_sitter_typescript::language_tsx()), +// ) +// .with_override_query("(jsx_self_closing_element) @element") +// .unwrap(), +// lsp::ServerCapabilities { +// completion_provider: Some(lsp::CompletionOptions { +// trigger_characters: Some(vec![":".to_string()]), +// ..Default::default() +// }), +// ..Default::default() +// }, +// cx, +// ) +// .await; + +// cx.lsp +// .handle_request::(move |_, _| async move { +// Ok(Some(lsp::CompletionResponse::Array(vec![ +// lsp::CompletionItem { +// label: "bg-blue".into(), +// ..Default::default() +// }, +// lsp::CompletionItem { +// label: "bg-red".into(), +// ..Default::default() +// }, +// lsp::CompletionItem { +// label: "bg-yellow".into(), +// ..Default::default() +// }, +// ]))) +// }); + +// cx.set_state(r#"

"#); + +// // Trigger completion when typing a dash, because the dash is an extra +// // word character in the 'element' scope, which contains the cursor. +// cx.simulate_keystroke("-"); +// cx.foreground().run_until_parked(); +// cx.update_editor(|editor, _| { +// if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { +// assert_eq!( +// menu.matches.iter().map(|m| &m.string).collect::>(), +// &["bg-red", "bg-blue", "bg-yellow"] +// ); +// } else { +// panic!("expected completion menu to be open"); +// } +// }); + +// cx.simulate_keystroke("l"); +// cx.foreground().run_until_parked(); +// cx.update_editor(|editor, _| { +// if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { +// assert_eq!( +// menu.matches.iter().map(|m| &m.string).collect::>(), +// &["bg-blue", "bg-yellow"] +// ); +// } else { +// panic!("expected completion menu to be open"); +// } +// }); + +// // When filtering completions, consider the character after the '-' to +// // be the start of a subword. +// cx.set_state(r#"

"#); +// cx.simulate_keystroke("l"); +// cx.foreground().run_until_parked(); +// cx.update_editor(|editor, _| { +// if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { +// assert_eq!( +// menu.matches.iter().map(|m| &m.string).collect::>(), +// &["bg-yellow"] +// ); +// } else { +// panic!("expected completion menu to be open"); +// } +// }); +// } + +// #[gpui::test] +// async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.formatter = Some(language_settings::Formatter::Prettier) +// }); + +// let mut language = Language::new( +// LanguageConfig { +// name: "Rust".into(), +// path_suffixes: vec!["rs".to_string()], +// prettier_parser_name: Some("test_parser".to_string()), +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ); + +// let test_plugin = "test_plugin"; +// let _ = language +// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { +// prettier_plugins: vec![test_plugin], +// ..Default::default() +// })) +// .await; + +// let fs = FakeFs::new(cx.background()); +// fs.insert_file("/file.rs", Default::default()).await; + +// let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; +// let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX; +// project.update(cx, |project, _| { +// project.languages().add(Arc::new(language)); +// }); +// let buffer = project +// .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) +// .await +// .unwrap(); + +// let buffer_text = "one\ntwo\nthree\n"; +// let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); +// let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); +// editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx)); + +// let format = editor.update(cx, |editor, cx| { +// editor.perform_format(project.clone(), FormatTrigger::Manual, cx) +// }); +// format.await.unwrap(); +// assert_eq!( +// editor.read_with(cx, |editor, cx| editor.text(cx)), +// buffer_text.to_string() + prettier_format_suffix, +// "Test prettier formatting was not applied to the original buffer text", +// ); + +// update_test_language_settings(cx, |settings| { +// settings.defaults.formatter = Some(language_settings::Formatter::Auto) +// }); +// let format = editor.update(cx, |editor, cx| { +// editor.perform_format(project.clone(), FormatTrigger::Manual, cx) +// }); +// format.await.unwrap(); +// assert_eq!( +// editor.read_with(cx, |editor, cx| editor.text(cx)), +// buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix, +// "Autoformatting (via test prettier) was not applied to the original buffer text", +// ); +// } + +// fn empty_range(row: usize, column: usize) -> Range { +// let point = DisplayPoint::new(row as u32, column as u32); +// point..point +// } + +// fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext) { +// let (text, ranges) = marked_text_ranges(marked_text, true); +// assert_eq!(view.text(cx), text); +// assert_eq!( +// view.selections.ranges(cx), +// ranges, +// "Assert selections are {}", +// marked_text +// ); +// } + +// /// Handle completion request passing a marked string specifying where the completion +// /// should be triggered from using '|' character, what range should be replaced, and what completions +// /// should be returned using '<' and '>' to delimit the range +// pub fn handle_completion_request<'a>( +// cx: &mut EditorLspTestContext<'a>, +// marked_string: &str, +// completions: Vec<&'static str>, +// ) -> impl Future { +// let complete_from_marker: TextRangeMarker = '|'.into(); +// let replace_range_marker: TextRangeMarker = ('<', '>').into(); +// let (_, mut marked_ranges) = marked_text_ranges_by( +// marked_string, +// vec![complete_from_marker.clone(), replace_range_marker.clone()], +// ); + +// let complete_from_position = +// cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start); +// let replace_range = +// cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone()); + +// let mut request = cx.handle_request::(move |url, params, _| { +// let completions = completions.clone(); +// async move { +// assert_eq!(params.text_document_position.text_document.uri, url.clone()); +// assert_eq!( +// params.text_document_position.position, +// complete_from_position +// ); +// Ok(Some(lsp::CompletionResponse::Array( +// completions +// .iter() +// .map(|completion_text| lsp::CompletionItem { +// label: completion_text.to_string(), +// text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { +// range: replace_range, +// new_text: completion_text.to_string(), +// })), +// ..Default::default() +// }) +// .collect(), +// ))) +// } +// }); + +// async move { +// request.next().await; +// } +// } + +// fn handle_resolve_completion_request<'a>( +// cx: &mut EditorLspTestContext<'a>, +// edits: Option>, +// ) -> impl Future { +// let edits = edits.map(|edits| { +// edits +// .iter() +// .map(|(marked_string, new_text)| { +// let (_, marked_ranges) = marked_text_ranges(marked_string, false); +// let replace_range = cx.to_lsp_range(marked_ranges[0].clone()); +// lsp::TextEdit::new(replace_range, new_text.to_string()) +// }) +// .collect::>() +// }); + +// let mut request = +// cx.handle_request::(move |_, _, _| { +// let edits = edits.clone(); +// async move { +// Ok(lsp::CompletionItem { +// additional_text_edits: edits, +// ..Default::default() +// }) +// } +// }); + +// async move { +// request.next().await; +// } +// } + +// fn handle_copilot_completion_request( +// lsp: &lsp::FakeLanguageServer, +// completions: Vec, +// completions_cycling: Vec, +// ) { +// lsp.handle_request::(move |_params, _cx| { +// let completions = completions.clone(); +// async move { +// Ok(copilot::request::GetCompletionsResult { +// completions: completions.clone(), +// }) +// } +// }); +// lsp.handle_request::(move |_params, _cx| { +// let completions_cycling = completions_cycling.clone(); +// async move { +// Ok(copilot::request::GetCompletionsResult { +// completions: completions_cycling.clone(), +// }) +// } +// }); +// } + +// pub(crate) fn update_test_language_settings( +// cx: &mut TestAppContext, +// f: impl Fn(&mut AllLanguageSettingsContent), +// ) { +// cx.update(|cx| { +// cx.update_global::(|store, cx| { +// store.update_user_settings::(cx, f); +// }); +// }); +// } + +// pub(crate) fn update_test_project_settings( +// cx: &mut TestAppContext, +// f: impl Fn(&mut ProjectSettings), +// ) { +// cx.update(|cx| { +// cx.update_global::(|store, cx| { +// store.update_user_settings::(cx, f); +// }); +// }); +// } + +// pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) { +// cx.foreground().forbid_parking(); + +// cx.update(|cx| { +// cx.set_global(SettingsStore::test(cx)); +// theme::init((), cx); +// client::init_settings(cx); +// language::init(cx); +// Project::init_settings(cx); +// workspace::init_settings(cx); +// crate::init(cx); +// }); + +// update_test_language_settings(cx, f); +// } diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs new file mode 100644 index 0000000000..645cdc7646 --- /dev/null +++ b/crates/editor2/src/element.rs @@ -0,0 +1,3488 @@ +use super::{ + display_map::ToDisplayPoint, DisplayPoint, Editor, EditorSnapshot, ToPoint, MAX_LINE_LEN, +}; +use crate::{ + display_map::{BlockStyle, DisplaySnapshot}, + EditorStyle, +}; +use anyhow::Result; +use gpui::{ + black, px, relative, AnyElement, Bounds, Element, Hsla, Line, Pixels, Size, Style, TextRun, + TextSystem, +}; +use language::{CursorShape, Selection}; +use smallvec::SmallVec; +use std::{ops::Range, sync::Arc}; +use sum_tree::Bias; + +enum FoldMarkers {} + +struct SelectionLayout { + head: DisplayPoint, + cursor_shape: CursorShape, + is_newest: bool, + is_local: bool, + range: Range, + active_rows: Range, +} + +impl SelectionLayout { + fn new( + selection: Selection, + line_mode: bool, + cursor_shape: CursorShape, + map: &DisplaySnapshot, + is_newest: bool, + is_local: bool, + ) -> Self { + let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot)); + let display_selection = point_selection.map(|p| p.to_display_point(map)); + let mut range = display_selection.range(); + let mut head = display_selection.head(); + let mut active_rows = map.prev_line_boundary(point_selection.start).1.row() + ..map.next_line_boundary(point_selection.end).1.row(); + + // vim visual line mode + if line_mode { + let point_range = map.expand_to_line(point_selection.range()); + range = point_range.start.to_display_point(map)..point_range.end.to_display_point(map); + } + + // any vim visual mode (including line mode) + if cursor_shape == CursorShape::Block && !range.is_empty() && !selection.reversed { + if head.column() > 0 { + head = map.clip_point(DisplayPoint::new(head.row(), head.column() - 1), Bias::Left) + } else if head.row() > 0 && head != map.max_point() { + head = map.clip_point( + DisplayPoint::new(head.row() - 1, map.line_len(head.row() - 1)), + Bias::Left, + ); + // updating range.end is a no-op unless you're cursor is + // on the newline containing a multi-buffer divider + // in which case the clip_point may have moved the head up + // an additional row. + range.end = DisplayPoint::new(head.row() + 1, 0); + active_rows.end = head.row(); + } + } + + Self { + head, + cursor_shape, + is_newest, + is_local, + range, + active_rows, + } + } +} + +pub struct EditorElement { + style: Arc, +} + +impl EditorElement { + pub fn new(style: EditorStyle) -> Self { + Self { + style: Arc::new(style), + } + } + + // fn attach_mouse_handlers( + // position_map: &Arc, + // has_popovers: bool, + // visible_bounds: Bounds, + // text_bounds: Bounds, + // gutter_bounds: Bounds, + // bounds: Bounds, + // cx: &mut ViewContext, + // ) { + // enum EditorElementMouseHandlers {} + // let view_id = cx.view_id(); + // cx.scene().push_mouse_region( + // MouseRegion::new::(view_id, view_id, visible_bounds) + // .on_down(MouseButton::Left, { + // let position_map = position_map.clone(); + // move |event, editor, cx| { + // if !Self::mouse_down( + // editor, + // event.platform_event, + // position_map.as_ref(), + // text_bounds, + // gutter_bounds, + // cx, + // ) { + // cx.propagate_event(); + // } + // } + // }) + // .on_down(MouseButton::Right, { + // let position_map = position_map.clone(); + // move |event, editor, cx| { + // if !Self::mouse_right_down( + // editor, + // event.position, + // position_map.as_ref(), + // text_bounds, + // cx, + // ) { + // cx.propagate_event(); + // } + // } + // }) + // .on_up(MouseButton::Left, { + // let position_map = position_map.clone(); + // move |event, editor, cx| { + // if !Self::mouse_up( + // editor, + // event.position, + // event.cmd, + // event.shift, + // event.alt, + // position_map.as_ref(), + // text_bounds, + // cx, + // ) { + // cx.propagate_event() + // } + // } + // }) + // .on_drag(MouseButton::Left, { + // let position_map = position_map.clone(); + // move |event, editor, cx| { + // if event.end { + // return; + // } + + // if !Self::mouse_dragged( + // editor, + // event.platform_event, + // position_map.as_ref(), + // text_bounds, + // cx, + // ) { + // cx.propagate_event() + // } + // } + // }) + // .on_move({ + // let position_map = position_map.clone(); + // move |event, editor, cx| { + // if !Self::mouse_moved( + // editor, + // event.platform_event, + // &position_map, + // text_bounds, + // cx, + // ) { + // cx.propagate_event() + // } + // } + // }) + // .on_move_out(move |_, editor: &mut Editor, cx| { + // if has_popovers { + // hide_hover(editor, cx); + // } + // }) + // .on_scroll({ + // let position_map = position_map.clone(); + // move |event, editor, cx| { + // if !Self::scroll( + // editor, + // event.position, + // *event.delta.raw(), + // event.delta.precise(), + // &position_map, + // bounds, + // cx, + // ) { + // cx.propagate_event() + // } + // } + // }), + // ); + + // enum GutterHandlers {} + // let view_id = cx.view_id(); + // let region_id = cx.view_id() + 1; + // cx.scene().push_mouse_region( + // MouseRegion::new::(view_id, region_id, gutter_bounds).on_hover( + // |hover, editor: &mut Editor, cx| { + // editor.gutter_hover( + // &GutterHover { + // hovered: hover.started, + // }, + // cx, + // ); + // }, + // ), + // ) + // } + + // fn mouse_down( + // editor: &mut Editor, + // MouseButtonEvent { + // position, + // modifiers: + // Modifiers { + // shift, + // ctrl, + // alt, + // cmd, + // .. + // }, + // mut click_count, + // .. + // }: MouseButtonEvent, + // position_map: &PositionMap, + // text_bounds: Bounds, + // gutter_bounds: Bounds, + // cx: &mut EventContext, + // ) -> bool { + // if gutter_bounds.contains_point(position) { + // click_count = 3; // Simulate triple-click when clicking the gutter to select lines + // } else if !text_bounds.contains_point(position) { + // return false; + // } + + // let point_for_position = position_map.point_for_position(text_bounds, position); + // let position = point_for_position.previous_valid; + // if shift && alt { + // editor.select( + // SelectPhase::BeginColumnar { + // position, + // goal_column: point_for_position.exact_unclipped.column(), + // }, + // cx, + // ); + // } else if shift && !ctrl && !alt && !cmd { + // editor.select( + // SelectPhase::Extend { + // position, + // click_count, + // }, + // cx, + // ); + // } else { + // editor.select( + // SelectPhase::Begin { + // position, + // add: alt, + // click_count, + // }, + // cx, + // ); + // } + + // true + // } + + // fn mouse_right_down( + // editor: &mut Editor, + // position: gpui::Point, + // position_map: &PositionMap, + // text_bounds: Bounds, + // cx: &mut EventContext, + // ) -> bool { + // if !text_bounds.contains_point(position) { + // return false; + // } + // let point_for_position = position_map.point_for_position(text_bounds, position); + // mouse_context_menu::deploy_context_menu( + // editor, + // position, + // point_for_position.previous_valid, + // cx, + // ); + // true + // } + + // fn mouse_up( + // editor: &mut Editor, + // position: gpui::Point, + // cmd: bool, + // shift: bool, + // alt: bool, + // position_map: &PositionMap, + // text_bounds: Bounds, + // cx: &mut EventContext, + // ) -> bool { + // let end_selection = editor.has_pending_selection(); + // let pending_nonempty_selections = editor.has_pending_nonempty_selection(); + + // if end_selection { + // editor.select(SelectPhase::End, cx); + // } + + // if !pending_nonempty_selections && cmd && text_bounds.contains_point(position) { + // let point = position_map.point_for_position(text_bounds, position); + // let could_be_inlay = point.as_valid().is_none(); + // if shift || could_be_inlay { + // go_to_fetched_type_definition(editor, point, alt, cx); + // } else { + // go_to_fetched_definition(editor, point, alt, cx); + // } + + // return true; + // } + + // end_selection + // } + + // fn mouse_dragged( + // editor: &mut Editor, + // MouseMovedEvent { + // modifiers: Modifiers { cmd, shift, .. }, + // position, + // .. + // }: MouseMovedEvent, + // position_map: &PositionMap, + // text_bounds: Bounds, + // cx: &mut EventContext, + // ) -> bool { + // // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed + // // Don't trigger hover popover if mouse is hovering over context menu + // let point = if text_bounds.contains_point(position) { + // position_map + // .point_for_position(text_bounds, position) + // .as_valid() + // } else { + // None + // }; + + // update_go_to_definition_link( + // editor, + // point.map(GoToDefinitionTrigger::Text), + // cmd, + // shift, + // cx, + // ); + + // if editor.has_pending_selection() { + // let mut scroll_delta = gpui::Point::zero(); + + // let vertical_margin = position_map.line_height.min(text_bounds.height() / 3.0); + // let top = text_bounds.origin_y() + vertical_margin; + // let bottom = text_bounds.lower_left().y() - vertical_margin; + // if position.y() < top { + // scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y())) + // } + // if position.y() > bottom { + // scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y() - bottom)) + // } + + // let horizontal_margin = position_map.line_height.min(text_bounds.width() / 3.0); + // let left = text_bounds.origin_x() + horizontal_margin; + // let right = text_bounds.upper_right().x() - horizontal_margin; + // if position.x() < left { + // scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta( + // left - position.x(), + // )) + // } + // if position.x() > right { + // scroll_delta.set_x(scale_horizontal_mouse_autoscroll_delta( + // position.x() - right, + // )) + // } + + // let point_for_position = position_map.point_for_position(text_bounds, position); + + // editor.select( + // SelectPhase::Update { + // position: point_for_position.previous_valid, + // goal_column: point_for_position.exact_unclipped.column(), + // scroll_position: (position_map.snapshot.scroll_position() + scroll_delta) + // .clamp(gpui::Point::zero(), position_map.scroll_max), + // }, + // cx, + // ); + // hover_at(editor, point, cx); + // true + // } else { + // hover_at(editor, point, cx); + // false + // } + // } + + // fn mouse_moved( + // editor: &mut Editor, + // MouseMovedEvent { + // modifiers: Modifiers { shift, cmd, .. }, + // position, + // .. + // }: MouseMovedEvent, + // position_map: &PositionMap, + // text_bounds: Bounds, + // cx: &mut ViewContext, + // ) -> bool { + // // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed + // // Don't trigger hover popover if mouse is hovering over context menu + // if text_bounds.contains_point(position) { + // let point_for_position = position_map.point_for_position(text_bounds, position); + // match point_for_position.as_valid() { + // Some(point) => { + // update_go_to_definition_link( + // editor, + // Some(GoToDefinitionTrigger::Text(point)), + // cmd, + // shift, + // cx, + // ); + // hover_at(editor, Some(point), cx); + // } + // None => { + // update_inlay_link_and_hover_points( + // &position_map.snapshot, + // point_for_position, + // editor, + // cmd, + // shift, + // cx, + // ); + // } + // } + // } else { + // update_go_to_definition_link(editor, None, cmd, shift, cx); + // hover_at(editor, None, cx); + // } + + // true + // } + + // fn scroll( + // editor: &mut Editor, + // position: gpui::Point, + // mut delta: gpui::Point, + // precise: bool, + // position_map: &PositionMap, + // bounds: Bounds, + // cx: &mut ViewContext, + // ) -> bool { + // if !bounds.contains_point(position) { + // return false; + // } + + // let line_height = position_map.line_height; + // let max_glyph_width = position_map.em_width; + + // let axis = if precise { + // //Trackpad + // position_map.snapshot.ongoing_scroll.filter(&mut delta) + // } else { + // //Not trackpad + // delta *= vec2f(max_glyph_width, line_height); + // None //Resets ongoing scroll + // }; + + // let scroll_position = position_map.snapshot.scroll_position(); + // let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width; + // let y = (scroll_position.y() * line_height - delta.y()) / line_height; + // let scroll_position = vec2f(x, y).clamp(gpui::Point::zero(), position_map.scroll_max); + // editor.scroll(scroll_position, axis, cx); + + // true + // } + + // fn paint_background( + // &self, + // gutter_bounds: Bounds, + // text_bounds: Bounds, + // layout: &LayoutState, + // cx: &mut ViewContext, + // ) { + // let bounds = gutter_bounds.union_rect(text_bounds); + // let scroll_top = + // layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height; + // cx.scene().push_quad(Quad { + // bounds: gutter_bounds, + // background: Some(self.style.gutter_background), + // border: Border::new(0., Color::transparent_black()).into(), + // corner_radii: Default::default(), + // }); + // cx.scene().push_quad(Quad { + // bounds: text_bounds, + // background: Some(self.style.background), + // border: Border::new(0., Color::transparent_black()).into(), + // corner_radii: Default::default(), + // }); + + // if let EditorMode::Full = layout.mode { + // let mut active_rows = layout.active_rows.iter().peekable(); + // while let Some((start_row, contains_non_empty_selection)) = active_rows.next() { + // let mut end_row = *start_row; + // while active_rows.peek().map_or(false, |r| { + // *r.0 == end_row + 1 && r.1 == contains_non_empty_selection + // }) { + // active_rows.next().unwrap(); + // end_row += 1; + // } + + // if !contains_non_empty_selection { + // let origin = vec2f( + // bounds.origin_x(), + // bounds.origin_y() + (layout.position_map.line_height * *start_row as f32) + // - scroll_top, + // ); + // let size = vec2f( + // bounds.width(), + // layout.position_map.line_height * (end_row - start_row + 1) as f32, + // ); + // cx.scene().push_quad(Quad { + // bounds: Bounds::new(origin, size), + // background: Some(self.style.active_line_background), + // border: Border::default().into(), + // corner_radii: Default::default(), + // }); + // } + // } + + // if let Some(highlighted_rows) = &layout.highlighted_rows { + // let origin = vec2f( + // bounds.origin_x(), + // bounds.origin_y() + // + (layout.position_map.line_height * highlighted_rows.start as f32) + // - scroll_top, + // ); + // let size = vec2f( + // bounds.width(), + // layout.position_map.line_height * highlighted_rows.len() as f32, + // ); + // cx.scene().push_quad(Quad { + // bounds: Bounds::new(origin, size), + // background: Some(self.style.highlighted_line_background), + // border: Border::default().into(), + // corner_radii: Default::default(), + // }); + // } + + // let scroll_left = + // layout.position_map.snapshot.scroll_position().x() * layout.position_map.em_width; + + // for (wrap_position, active) in layout.wrap_guides.iter() { + // let x = + // (text_bounds.origin_x() + wrap_position + layout.position_map.em_width / 2.) + // - scroll_left; + + // if x < text_bounds.origin_x() + // || (layout.show_scrollbars && x > self.scrollbar_left(&bounds)) + // { + // continue; + // } + + // let color = if *active { + // self.style.active_wrap_guide + // } else { + // self.style.wrap_guide + // }; + // cx.scene().push_quad(Quad { + // bounds: Bounds::new( + // vec2f(x, text_bounds.origin_y()), + // vec2f(1., text_bounds.height()), + // ), + // background: Some(color), + // border: Border::new(0., Color::transparent_black()).into(), + // corner_radii: Default::default(), + // }); + // } + // } + // } + + // fn paint_gutter( + // &mut self, + // bounds: Bounds, + // visible_bounds: Bounds, + // layout: &mut LayoutState, + // editor: &mut Editor, + // cx: &mut ViewContext, + // ) { + // let line_height = layout.position_map.line_height; + + // let scroll_position = layout.position_map.snapshot.scroll_position(); + // let scroll_top = scroll_position.y() * line_height; + + // let show_gutter = matches!( + // settings::get::(cx).git.git_gutter, + // Some(GitGutterSetting::TrackedFiles) + // ); + + // if show_gutter { + // Self::paint_diff_hunks(bounds, layout, cx); + // } + + // for (ix, line) in layout.line_number_layouts.iter().enumerate() { + // if let Some(line) = line { + // let line_origin = bounds.origin() + // + vec2f( + // bounds.width() - line.width() - layout.gutter_padding, + // ix as f32 * line_height - (scroll_top % line_height), + // ); + + // line.paint(line_origin, visible_bounds, line_height, cx); + // } + // } + + // for (ix, fold_indicator) in layout.fold_indicators.iter_mut().enumerate() { + // if let Some(indicator) = fold_indicator.as_mut() { + // let position = vec2f( + // bounds.width() - layout.gutter_padding, + // ix as f32 * line_height - (scroll_top % line_height), + // ); + // let centering_offset = vec2f( + // (layout.gutter_padding + layout.gutter_margin - indicator.size().x()) / 2., + // (line_height - indicator.size().y()) / 2., + // ); + + // let indicator_origin = bounds.origin() + position + centering_offset; + + // indicator.paint(indicator_origin, visible_bounds, editor, cx); + // } + // } + + // if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() { + // let mut x = 0.; + // let mut y = *row as f32 * line_height - scroll_top; + // x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.; + // y += (line_height - indicator.size().y()) / 2.; + // indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, editor, cx); + // } + // } + + // fn paint_diff_hunks(bounds: Bounds, layout: &mut LayoutState, cx: &mut ViewContext) { + // let diff_style = &theme::current(cx).editor.diff.clone(); + // let line_height = layout.position_map.line_height; + + // let scroll_position = layout.position_map.snapshot.scroll_position(); + // let scroll_top = scroll_position.y() * line_height; + + // for hunk in &layout.display_hunks { + // let (display_row_range, status) = match hunk { + // //TODO: This rendering is entirely a horrible hack + // &DisplayDiffHunk::Folded { display_row: row } => { + // let start_y = row as f32 * line_height - scroll_top; + // let end_y = start_y + line_height; + + // let width = diff_style.removed_width_em * line_height; + // let highlight_origin = bounds.origin() + vec2f(-width, start_y); + // let highlight_size = vec2f(width * 2., end_y - start_y); + // let highlight_bounds = Bounds::new(highlight_origin, highlight_size); + + // cx.scene().push_quad(Quad { + // bounds: highlight_bounds, + // background: Some(diff_style.modified), + // border: Border::new(0., Color::transparent_black()).into(), + // corner_radii: (1. * line_height).into(), + // }); + + // continue; + // } + + // DisplayDiffHunk::Unfolded { + // display_row_range, + // status, + // } => (display_row_range, status), + // }; + + // let color = match status { + // DiffHunkStatus::Added => diff_style.inserted, + // DiffHunkStatus::Modified => diff_style.modified, + + // //TODO: This rendering is entirely a horrible hack + // DiffHunkStatus::Removed => { + // let row = display_row_range.start; + + // let offset = line_height / 2.; + // let start_y = row as f32 * line_height - offset - scroll_top; + // let end_y = start_y + line_height; + + // let width = diff_style.removed_width_em * line_height; + // let highlight_origin = bounds.origin() + vec2f(-width, start_y); + // let highlight_size = vec2f(width * 2., end_y - start_y); + // let highlight_bounds = Bounds::new(highlight_origin, highlight_size); + + // cx.scene().push_quad(Quad { + // bounds: highlight_bounds, + // background: Some(diff_style.deleted), + // border: Border::new(0., Color::transparent_black()).into(), + // corner_radii: (1. * line_height).into(), + // }); + + // continue; + // } + // }; + + // let start_row = display_row_range.start; + // let end_row = display_row_range.end; + + // let start_y = start_row as f32 * line_height - scroll_top; + // let end_y = end_row as f32 * line_height - scroll_top; + + // let width = diff_style.width_em * line_height; + // let highlight_origin = bounds.origin() + vec2f(-width, start_y); + // let highlight_size = vec2f(width * 2., end_y - start_y); + // let highlight_bounds = Bounds::new(highlight_origin, highlight_size); + + // cx.scene().push_quad(Quad { + // bounds: highlight_bounds, + // background: Some(color), + // border: Border::new(0., Color::transparent_black()).into(), + // corner_radii: (diff_style.corner_radius * line_height).into(), + // }); + // } + // } + + // fn paint_text( + // &mut self, + // bounds: Bounds, + // visible_bounds: Bounds, + // layout: &mut LayoutState, + // editor: &mut Editor, + // cx: &mut ViewContext, + // ) { + // let style = &self.style; + // let scroll_position = layout.position_map.snapshot.scroll_position(); + // let start_row = layout.visible_display_row_range.start; + // let scroll_top = scroll_position.y() * layout.position_map.line_height; + // let max_glyph_width = layout.position_map.em_width; + // let scroll_left = scroll_position.x() * max_glyph_width; + // let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.); + // let line_end_overshoot = 0.15 * layout.position_map.line_height; + // let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces; + + // cx.scene().push_layer(Some(bounds)); + + // cx.scene().push_cursor_region(CursorRegion { + // bounds, + // style: if !editor.link_go_to_definition_state.definitions.is_empty() { + // CursorStyle::PointingHand + // } else { + // CursorStyle::IBeam + // }, + // }); + + // let fold_corner_radius = + // self.style.folds.ellipses.corner_radius_factor * layout.position_map.line_height; + // for (id, range, color) in layout.fold_ranges.iter() { + // self.paint_highlighted_range( + // range.clone(), + // *color, + // fold_corner_radius, + // fold_corner_radius * 2., + // layout, + // content_origin, + // scroll_top, + // scroll_left, + // bounds, + // cx, + // ); + + // for bound in range_to_bounds( + // &range, + // content_origin, + // scroll_left, + // scroll_top, + // &layout.visible_display_row_range, + // line_end_overshoot, + // &layout.position_map, + // ) { + // cx.scene().push_cursor_region(CursorRegion { + // bounds: bound, + // style: CursorStyle::PointingHand, + // }); + + // let display_row = range.start.row(); + + // let buffer_row = DisplayPoint::new(display_row, 0) + // .to_point(&layout.position_map.snapshot.display_snapshot) + // .row; + + // let view_id = cx.view_id(); + // cx.scene().push_mouse_region( + // MouseRegion::new::(view_id, *id as usize, bound) + // .on_click(MouseButton::Left, move |_, editor: &mut Editor, cx| { + // editor.unfold_at(&UnfoldAt { buffer_row }, cx) + // }) + // .with_notify_on_hover(true) + // .with_notify_on_click(true), + // ) + // } + // } + + // for (range, color) in &layout.highlighted_ranges { + // self.paint_highlighted_range( + // range.clone(), + // *color, + // 0., + // line_end_overshoot, + // layout, + // content_origin, + // scroll_top, + // scroll_left, + // bounds, + // cx, + // ); + // } + + // let mut cursors = SmallVec::<[Cursor; 32]>::new(); + // let corner_radius = 0.15 * layout.position_map.line_height; + // let mut invisible_display_ranges = SmallVec::<[Range; 32]>::new(); + + // for (selection_style, selections) in &layout.selections { + // for selection in selections { + // self.paint_highlighted_range( + // selection.range.clone(), + // selection_style.selection, + // corner_radius, + // corner_radius * 2., + // layout, + // content_origin, + // scroll_top, + // scroll_left, + // bounds, + // cx, + // ); + + // if selection.is_local && !selection.range.is_empty() { + // invisible_display_ranges.push(selection.range.clone()); + // } + // if !selection.is_local || editor.show_local_cursors(cx) { + // let cursor_position = selection.head; + // if layout + // .visible_display_row_range + // .contains(&cursor_position.row()) + // { + // let cursor_row_layout = &layout.position_map.line_layouts + // [(cursor_position.row() - start_row) as usize] + // .line; + // 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.position_map.em_width; + // } + // let block_text = if let CursorShape::Block = selection.cursor_shape { + // layout + // .position_map + // .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.chars().count(), + // RunStyle { + // font_id, + // color: style.background, + // underline: Default::default(), + // }, + // )], + // )) + // }) + // } else { + // None + // }; + + // let x = cursor_character_x - scroll_left; + // let y = cursor_position.row() as f32 * layout.position_map.line_height + // - scroll_top; + // if selection.is_newest { + // editor.pixel_position_of_newest_cursor = Some(vec2f( + // bounds.origin_x() + x + block_width / 2., + // bounds.origin_y() + y + layout.position_map.line_height / 2., + // )); + // } + // cursors.push(Cursor { + // color: selection_style.cursor, + // block_width, + // origin: vec2f(x, y), + // line_height: layout.position_map.line_height, + // shape: selection.cursor_shape, + // block_text, + // }); + // } + // } + // } + // } + + // if let Some(visible_text_bounds) = bounds.intersection(visible_bounds) { + // for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() { + // let row = start_row + ix as u32; + // line_with_invisibles.draw( + // layout, + // row, + // scroll_top, + // content_origin, + // scroll_left, + // visible_text_bounds, + // whitespace_setting, + // &invisible_display_ranges, + // visible_bounds, + // cx, + // ) + // } + // } + + // cx.scene().push_layer(Some(bounds)); + // for cursor in cursors { + // cursor.paint(content_origin, cx); + // } + // cx.scene().pop_layer(); + + // if let Some((position, context_menu)) = layout.context_menu.as_mut() { + // cx.scene().push_stacking_context(None, None); + // let cursor_row_layout = + // &layout.position_map.line_layouts[(position.row() - start_row) as usize].line; + // let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left; + // let y = (position.row() + 1) as f32 * layout.position_map.line_height - scroll_top; + // let mut list_origin = content_origin + vec2f(x, y); + // let list_width = context_menu.size().x(); + // let list_height = context_menu.size().y(); + + // // Snap the right edge of the list to the right edge of the window if + // // its horizontal bounds overflow. + // if list_origin.x() + list_width > cx.window_size().x() { + // list_origin.set_x((cx.window_size().x() - list_width).max(0.)); + // } + + // if list_origin.y() + list_height > bounds.max_y() { + // list_origin.set_y(list_origin.y() - layout.position_map.line_height - list_height); + // } + + // context_menu.paint( + // list_origin, + // Bounds::from_points(gpui::Point::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor + // editor, + // cx, + // ); + + // cx.scene().pop_stacking_context(); + // } + + // if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() { + // cx.scene().push_stacking_context(None, None); + + // // This is safe because we check on layout whether the required row is available + // let hovered_row_layout = + // &layout.position_map.line_layouts[(position.row() - start_row) as usize].line; + + // // Minimum required size: Take the first popover, and add 1.5 times the minimum popover + // // height. This is the size we will use to decide whether to render popovers above or below + // // the hovered line. + // let first_size = hover_popovers[0].size(); + // let height_to_reserve = first_size.y() + // + 1.5 * MIN_POPOVER_LINE_HEIGHT as f32 * layout.position_map.line_height; + + // // Compute Hovered Point + // let x = hovered_row_layout.x_for_index(position.column() as usize) - scroll_left; + // let y = position.row() as f32 * layout.position_map.line_height - scroll_top; + // let hovered_point = content_origin + vec2f(x, y); + + // if hovered_point.y() - height_to_reserve > 0.0 { + // // There is enough space above. Render popovers above the hovered point + // let mut current_y = hovered_point.y(); + // for hover_popover in hover_popovers { + // let size = hover_popover.size(); + // let mut popover_origin = vec2f(hovered_point.x(), current_y - size.y()); + + // let x_out_of_bounds = bounds.max_x() - (popover_origin.x() + size.x()); + // if x_out_of_bounds < 0.0 { + // popover_origin.set_x(popover_origin.x() + x_out_of_bounds); + // } + + // hover_popover.paint( + // popover_origin, + // Bounds::from_points(gpui::Point::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor + // editor, + // cx, + // ); + + // current_y = popover_origin.y() - HOVER_POPOVER_GAP; + // } + // } else { + // // There is not enough space above. Render popovers below the hovered point + // let mut current_y = hovered_point.y() + layout.position_map.line_height; + // for hover_popover in hover_popovers { + // let size = hover_popover.size(); + // let mut popover_origin = vec2f(hovered_point.x(), current_y); + + // let x_out_of_bounds = bounds.max_x() - (popover_origin.x() + size.x()); + // if x_out_of_bounds < 0.0 { + // popover_origin.set_x(popover_origin.x() + x_out_of_bounds); + // } + + // hover_popover.paint( + // popover_origin, + // Bounds::from_points(gpui::Point::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor + // editor, + // cx, + // ); + + // current_y = popover_origin.y() + size.y() + HOVER_POPOVER_GAP; + // } + // } + + // cx.scene().pop_stacking_context(); + // } + + // cx.scene().pop_layer(); + // } + + // fn scrollbar_left(&self, bounds: &Bounds) -> f32 { + // bounds.max_x() - self.style.theme.scrollbar.width + // } + + // fn paint_scrollbar( + // &mut self, + // bounds: Bounds, + // layout: &mut LayoutState, + // editor: &Editor, + // cx: &mut ViewContext, + // ) { + // enum ScrollbarMouseHandlers {} + // if layout.mode != EditorMode::Full { + // return; + // } + + // let style = &self.style.theme.scrollbar; + + // let top = bounds.min_y(); + // let bottom = bounds.max_y(); + // let right = bounds.max_x(); + // let left = self.scrollbar_left(&bounds); + // let row_range = &layout.scrollbar_row_range; + // let max_row = layout.max_row as f32 + (row_range.end - row_range.start); + + // let mut height = bounds.height(); + // let mut first_row_y_offset = 0.0; + + // // Impose a minimum height on the scrollbar thumb + // let row_height = height / max_row; + // let min_thumb_height = + // style.min_height_factor * cx.font_cache.line_height(self.style.text.font_size); + // let thumb_height = (row_range.end - row_range.start) * row_height; + // if thumb_height < min_thumb_height { + // first_row_y_offset = (min_thumb_height - thumb_height) / 2.0; + // height -= min_thumb_height - thumb_height; + // } + + // let y_for_row = |row: f32| -> f32 { top + first_row_y_offset + row * row_height }; + + // let thumb_top = y_for_row(row_range.start) - first_row_y_offset; + // let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset; + // let track_bounds = Bounds::from_points(vec2f(left, top), vec2f(right, bottom)); + // let thumb_bounds = Bounds::from_points(vec2f(left, thumb_top), vec2f(right, thumb_bottom)); + + // if layout.show_scrollbars { + // cx.scene().push_quad(Quad { + // bounds: track_bounds, + // border: style.track.border.into(), + // background: style.track.background_color, + // ..Default::default() + // }); + // let scrollbar_settings = settings::get::(cx).scrollbar; + // let theme = theme::current(cx); + // let scrollbar_theme = &theme.editor.scrollbar; + // if layout.is_singleton && scrollbar_settings.selections { + // let start_anchor = Anchor::min(); + // let end_anchor = Anchor::max(); + // let color = scrollbar_theme.selections; + // let border = Border { + // width: 1., + // color: style.thumb.border.color, + // overlay: false, + // top: false, + // right: true, + // bottom: false, + // left: true, + // }; + // let mut push_region = |start: DisplayPoint, end: DisplayPoint| { + // let start_y = y_for_row(start.row() as f32); + // let mut end_y = y_for_row(end.row() as f32); + // if end_y - start_y < 1. { + // end_y = start_y + 1.; + // } + // let bounds = Bounds::from_points(vec2f(left, start_y), vec2f(right, end_y)); + + // cx.scene().push_quad(Quad { + // bounds, + // background: Some(color), + // border: border.into(), + // corner_radii: style.thumb.corner_radii.into(), + // }) + // }; + // let background_ranges = editor + // .background_highlight_row_ranges::( + // start_anchor..end_anchor, + // &layout.position_map.snapshot, + // 50000, + // ); + // for row in background_ranges { + // let start = row.start(); + // let end = row.end(); + // push_region(*start, *end); + // } + // } + + // if layout.is_singleton && scrollbar_settings.git_diff { + // let diff_style = scrollbar_theme.git.clone(); + // for hunk in layout + // .position_map + // .snapshot + // .buffer_snapshot + // .git_diff_hunks_in_range(0..(max_row.floor() as u32)) + // { + // let start_display = Point::new(hunk.buffer_range.start, 0) + // .to_display_point(&layout.position_map.snapshot.display_snapshot); + // let end_display = Point::new(hunk.buffer_range.end, 0) + // .to_display_point(&layout.position_map.snapshot.display_snapshot); + // let start_y = y_for_row(start_display.row() as f32); + // let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end { + // y_for_row((end_display.row() + 1) as f32) + // } else { + // y_for_row((end_display.row()) as f32) + // }; + + // if end_y - start_y < 1. { + // end_y = start_y + 1.; + // } + // let bounds = Bounds::from_points(vec2f(left, start_y), vec2f(right, end_y)); + + // let color = match hunk.status() { + // DiffHunkStatus::Added => diff_style.inserted, + // DiffHunkStatus::Modified => diff_style.modified, + // DiffHunkStatus::Removed => diff_style.deleted, + // }; + + // let border = Border { + // width: 1., + // color: style.thumb.border.color, + // overlay: false, + // top: false, + // right: true, + // bottom: false, + // left: true, + // }; + + // cx.scene().push_quad(Quad { + // bounds, + // background: Some(color), + // border: border.into(), + // corner_radii: style.thumb.corner_radii.into(), + // }) + // } + // } + + // cx.scene().push_quad(Quad { + // bounds: thumb_bounds, + // border: style.thumb.border.into(), + // background: style.thumb.background_color, + // corner_radii: style.thumb.corner_radii.into(), + // }); + // } + + // cx.scene().push_cursor_region(CursorRegion { + // bounds: track_bounds, + // style: CursorStyle::Arrow, + // }); + // let region_id = cx.view_id(); + // cx.scene().push_mouse_region( + // MouseRegion::new::(region_id, region_id, track_bounds) + // .on_move(move |event, editor: &mut Editor, cx| { + // if event.pressed_button.is_none() { + // editor.scroll_manager.show_scrollbar(cx); + // } + // }) + // .on_down(MouseButton::Left, { + // let row_range = row_range.clone(); + // move |event, editor: &mut Editor, cx| { + // let y = event.position.y(); + // if y < thumb_top || thumb_bottom < y { + // let center_row = ((y - top) * max_row as f32 / height).round() as u32; + // let top_row = center_row + // .saturating_sub((row_range.end - row_range.start) as u32 / 2); + // let mut position = editor.scroll_position(cx); + // position.set_y(top_row as f32); + // editor.set_scroll_position(position, cx); + // } else { + // editor.scroll_manager.show_scrollbar(cx); + // } + // } + // }) + // .on_drag(MouseButton::Left, { + // move |event, editor: &mut Editor, cx| { + // if event.end { + // return; + // } + + // let y = event.prev_mouse_position.y(); + // let new_y = event.position.y(); + // if thumb_top < y && y < thumb_bottom { + // let mut position = editor.scroll_position(cx); + // position.set_y(position.y() + (new_y - y) * (max_row as f32) / height); + // if position.y() < 0.0 { + // position.set_y(0.); + // } + // editor.set_scroll_position(position, cx); + // } + // } + // }), + // ); + // } + + // #[allow(clippy::too_many_arguments)] + // fn paint_highlighted_range( + // &self, + // range: Range, + // color: Color, + // corner_radius: f32, + // line_end_overshoot: f32, + // layout: &LayoutState, + // content_origin: gpui::Point, + // scroll_top: f32, + // scroll_left: f32, + // bounds: Bounds, + // cx: &mut ViewContext, + // ) { + // let start_row = layout.visible_display_row_range.start; + // let end_row = layout.visible_display_row_range.end; + // if range.start != range.end { + // let row_range = if range.end.column() == 0 { + // cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row) + // } else { + // cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row) + // }; + + // let highlighted_range = HighlightedRange { + // color, + // line_height: layout.position_map.line_height, + // corner_radius, + // start_y: content_origin.y() + // + row_range.start as f32 * layout.position_map.line_height + // - scroll_top, + // lines: row_range + // .into_iter() + // .map(|row| { + // let line_layout = + // &layout.position_map.line_layouts[(row - start_row) as usize].line; + // HighlightedRangeLine { + // start_x: if row == range.start.row() { + // content_origin.x() + // + line_layout.x_for_index(range.start.column() as usize) + // - scroll_left + // } else { + // content_origin.x() - scroll_left + // }, + // end_x: if row == range.end.row() { + // content_origin.x() + // + line_layout.x_for_index(range.end.column() as usize) + // - scroll_left + // } else { + // content_origin.x() + line_layout.width() + line_end_overshoot + // - scroll_left + // }, + // } + // }) + // .collect(), + // }; + + // highlighted_range.paint(bounds, cx); + // } + // } + + // fn paint_blocks( + // &mut self, + // bounds: Bounds, + // visible_bounds: Bounds, + // layout: &mut LayoutState, + // editor: &mut Editor, + // cx: &mut ViewContext, + // ) { + // let scroll_position = layout.position_map.snapshot.scroll_position(); + // let scroll_left = scroll_position.x() * layout.position_map.em_width; + // let scroll_top = scroll_position.y() * layout.position_map.line_height; + + // for block in &mut layout.blocks { + // let mut origin = bounds.origin() + // + vec2f( + // 0., + // block.row as f32 * layout.position_map.line_height - scroll_top, + // ); + // if !matches!(block.style, BlockStyle::Sticky) { + // origin += vec2f(-scroll_left, 0.); + // } + // block.element.paint(origin, visible_bounds, editor, cx); + // } + // } + + // fn column_pixels(&self, column: usize, cx: &ViewContext) -> f32 { + // let style = &self.style; + + // cx.text_layout_cache() + // .layout_str( + // " ".repeat(column).as_str(), + // style.text.font_size, + // &[( + // column, + // RunStyle { + // font_id: style.text.font_id, + // color: Color::black(), + // underline: Default::default(), + // }, + // )], + // ) + // .width() + // } + + // fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext) -> f32 { + // let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1; + // self.column_pixels(digit_count, cx) + // } + + //Folds contained in a hunk are ignored apart from shrinking visual size + //If a fold contains any hunks then that fold line is marked as modified + // fn layout_git_gutters( + // &self, + // display_rows: Range, + // snapshot: &EditorSnapshot, + // ) -> Vec { + // let buffer_snapshot = &snapshot.buffer_snapshot; + + // let buffer_start_row = DisplayPoint::new(display_rows.start, 0) + // .to_point(snapshot) + // .row; + // let buffer_end_row = DisplayPoint::new(display_rows.end, 0) + // .to_point(snapshot) + // .row; + + // buffer_snapshot + // .git_diff_hunks_in_range(buffer_start_row..buffer_end_row) + // .map(|hunk| diff_hunk_to_display(hunk, snapshot)) + // .dedup() + // .collect() + // } + + // fn calculate_relative_line_numbers( + // &self, + // snapshot: &EditorSnapshot, + // rows: &Range, + // relative_to: Option, + // ) -> HashMap { + // let mut relative_rows: HashMap = Default::default(); + // let Some(relative_to) = relative_to else { + // return relative_rows; + // }; + + // let start = rows.start.min(relative_to); + // let end = rows.end.max(relative_to); + + // let buffer_rows = snapshot + // .buffer_rows(start) + // .take(1 + (end - start) as usize) + // .collect::>(); + + // let head_idx = relative_to - start; + // let mut delta = 1; + // let mut i = head_idx + 1; + // while i < buffer_rows.len() as u32 { + // if buffer_rows[i as usize].is_some() { + // if rows.contains(&(i + start)) { + // relative_rows.insert(i + start, delta); + // } + // delta += 1; + // } + // i += 1; + // } + // delta = 1; + // i = head_idx.min(buffer_rows.len() as u32 - 1); + // while i > 0 && buffer_rows[i as usize].is_none() { + // i -= 1; + // } + + // while i > 0 { + // i -= 1; + // if buffer_rows[i as usize].is_some() { + // if rows.contains(&(i + start)) { + // relative_rows.insert(i + start, delta); + // } + // delta += 1; + // } + // } + + // relative_rows + // } + + // fn layout_line_numbers( + // &self, + // rows: Range, + // active_rows: &BTreeMap, + // newest_selection_head: DisplayPoint, + // is_singleton: bool, + // snapshot: &EditorSnapshot, + // cx: &ViewContext, + // ) -> ( + // Vec>, + // Vec>, + // ) { + // let style = &self.style; + // let include_line_numbers = snapshot.mode == EditorMode::Full; + // let mut line_number_layouts = Vec::with_capacity(rows.len()); + // let mut fold_statuses = Vec::with_capacity(rows.len()); + // let mut line_number = String::new(); + // let is_relative = settings::get::(cx).relative_line_numbers; + // let relative_to = if is_relative { + // Some(newest_selection_head.row()) + // } else { + // None + // }; + + // let relative_rows = self.calculate_relative_line_numbers(&snapshot, &rows, relative_to); + + // for (ix, row) in snapshot + // .buffer_rows(rows.start) + // .take((rows.end - rows.start) as usize) + // .enumerate() + // { + // let display_row = rows.start + ix as u32; + // let (active, color) = if active_rows.contains_key(&display_row) { + // (true, style.line_number_active) + // } else { + // (false, style.line_number) + // }; + // if let Some(buffer_row) = row { + // if include_line_numbers { + // line_number.clear(); + // let default_number = buffer_row + 1; + // let number = relative_rows + // .get(&(ix as u32 + rows.start)) + // .unwrap_or(&default_number); + // write!(&mut line_number, "{}", number).unwrap(); + // line_number_layouts.push(Some(cx.text_layout_cache().layout_str( + // &line_number, + // style.text.font_size, + // &[( + // line_number.len(), + // RunStyle { + // font_id: style.text.font_id, + // color, + // underline: Default::default(), + // }, + // )], + // ))); + // fold_statuses.push( + // is_singleton + // .then(|| { + // snapshot + // .fold_for_line(buffer_row) + // .map(|fold_status| (fold_status, buffer_row, active)) + // }) + // .flatten(), + // ) + // } + // } else { + // fold_statuses.push(None); + // line_number_layouts.push(None); + // } + // } + + // (line_number_layouts, fold_statuses) + // } + + // fn layout_lines( + // &mut self, + // rows: Range, + // line_number_layouts: &[Option], + // snapshot: &EditorSnapshot, + // cx: &ViewContext, + // ) -> Vec { + // if rows.start >= rows.end { + // return Vec::new(); + // } + + // // When the editor is empty and unfocused, then show the placeholder. + // if snapshot.is_empty() { + // let placeholder_style = self + // .style + // .placeholder_text + // .as_ref() + // .unwrap_or(&self.style.text); + // let placeholder_text = snapshot.placeholder_text(); + // let placeholder_lines = placeholder_text + // .as_ref() + // .map_or("", AsRef::as_ref) + // .split('\n') + // .skip(rows.start as usize) + // .chain(iter::repeat("")) + // .take(rows.len()); + // placeholder_lines + // .map(|line| { + // cx.text_layout_cache().layout_str( + // line, + // placeholder_style.font_size, + // &[( + // line.len(), + // RunStyle { + // font_id: placeholder_style.font_id, + // color: placeholder_style.color, + // underline: Default::default(), + // }, + // )], + // ) + // }) + // .map(|line| LineWithInvisibles { + // line, + // invisibles: Vec::new(), + // }) + // .collect() + // } else { + // let style = &self.style; + // let chunks = snapshot.highlighted_chunks(rows.clone(), true, style); + + // LineWithInvisibles::from_chunks( + // chunks, + // &style.text, + // cx.text_layout_cache(), + // cx.font_cache(), + // MAX_LINE_LEN, + // rows.len() as usize, + // line_number_layouts, + // snapshot.mode, + // ) + // } + // } + + // #[allow(clippy::too_many_arguments)] + // fn layout_blocks( + // &mut self, + // rows: Range, + // snapshot: &EditorSnapshot, + // editor_width: f32, + // scroll_width: f32, + // gutter_padding: f32, + // gutter_width: f32, + // em_width: f32, + // text_x: f32, + // line_height: f32, + // style: &EditorStyle, + // line_layouts: &[LineWithInvisibles], + // editor: &mut Editor, + // cx: &mut ViewContext, + // ) -> (f32, Vec) { + // let mut block_id = 0; + // let scroll_x = snapshot.scroll_anchor.offset.x(); + // let (fixed_blocks, non_fixed_blocks) = snapshot + // .blocks_in_range(rows.clone()) + // .partition::, _>(|(_, block)| match block { + // TransformBlock::ExcerptHeader { .. } => false, + // TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed, + // }); + // let mut render_block = |block: &TransformBlock, width: f32, block_id: usize| { + // let mut element = match block { + // TransformBlock::Custom(block) => { + // let align_to = block + // .position() + // .to_point(&snapshot.buffer_snapshot) + // .to_display_point(snapshot); + // let anchor_x = text_x + // + if rows.contains(&align_to.row()) { + // line_layouts[(align_to.row() - rows.start) as usize] + // .line + // .x_for_index(align_to.column() as usize) + // } else { + // layout_line(align_to.row(), snapshot, style, cx.text_layout_cache()) + // .x_for_index(align_to.column() as usize) + // }; + + // block.render(&mut BlockContext { + // view_context: cx, + // anchor_x, + // gutter_padding, + // line_height, + // scroll_x, + // gutter_width, + // em_width, + // block_id, + // }) + // } + // TransformBlock::ExcerptHeader { + // id, + // buffer, + // range, + // starts_new_buffer, + // .. + // } => { + // let tooltip_style = theme::current(cx).tooltip.clone(); + // let include_root = editor + // .project + // .as_ref() + // .map(|project| project.read(cx).visible_worktrees(cx).count() > 1) + // .unwrap_or_default(); + // let jump_icon = project::File::from_dyn(buffer.file()).map(|file| { + // let jump_path = ProjectPath { + // worktree_id: file.worktree_id(cx), + // path: file.path.clone(), + // }; + // let jump_anchor = range + // .primary + // .as_ref() + // .map_or(range.context.start, |primary| primary.start); + // let jump_position = language::ToPoint::to_point(&jump_anchor, buffer); + + // enum JumpIcon {} + // MouseEventHandler::new::((*id).into(), cx, |state, _| { + // let style = style.jump_icon.style_for(state); + // Svg::new("icons/arrow_up_right.svg") + // .with_color(style.color) + // .constrained() + // .with_width(style.icon_width) + // .aligned() + // .contained() + // .with_style(style.container) + // .constrained() + // .with_width(style.button_width) + // .with_height(style.button_width) + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_click(MouseButton::Left, move |_, editor, cx| { + // if let Some(workspace) = editor + // .workspace + // .as_ref() + // .and_then(|(workspace, _)| workspace.upgrade(cx)) + // { + // workspace.update(cx, |workspace, cx| { + // Editor::jump( + // workspace, + // jump_path.clone(), + // jump_position, + // jump_anchor, + // cx, + // ); + // }); + // } + // }) + // .with_tooltip::( + // (*id).into(), + // "Jump to Buffer".to_string(), + // Some(Box::new(crate::OpenExcerpts)), + // tooltip_style.clone(), + // cx, + // ) + // .aligned() + // .flex_float() + // }); + + // if *starts_new_buffer { + // let editor_font_size = style.text.font_size; + // let style = &style.diagnostic_path_header; + // let font_size = (style.text_scale_factor * editor_font_size).round(); + + // let path = buffer.resolve_file_path(cx, include_root); + // let mut filename = None; + // let mut parent_path = None; + // // Can't use .and_then() because `.file_name()` and `.parent()` return references :( + // if let Some(path) = path { + // filename = path.file_name().map(|f| f.to_string_lossy().to_string()); + // parent_path = + // path.parent().map(|p| p.to_string_lossy().to_string() + "/"); + // } + + // Flex::row() + // .with_child( + // Label::new( + // filename.unwrap_or_else(|| "untitled".to_string()), + // style.filename.text.clone().with_font_size(font_size), + // ) + // .contained() + // .with_style(style.filename.container) + // .aligned(), + // ) + // .with_children(parent_path.map(|path| { + // Label::new(path, style.path.text.clone().with_font_size(font_size)) + // .contained() + // .with_style(style.path.container) + // .aligned() + // })) + // .with_children(jump_icon) + // .contained() + // .with_style(style.container) + // .with_padding_left(gutter_padding) + // .with_padding_right(gutter_padding) + // .expanded() + // .into_any_named("path header block") + // } else { + // let text_style = style.text.clone(); + // Flex::row() + // .with_child(Label::new("⋯", text_style)) + // .with_children(jump_icon) + // .contained() + // .with_padding_left(gutter_padding) + // .with_padding_right(gutter_padding) + // .expanded() + // .into_any_named("collapsed context") + // } + // } + // }; + + // element.layout( + // SizeConstraint { + // min: gpui::Point::zero(), + // max: vec2f(width, block.height() as f32 * line_height), + // }, + // editor, + // cx, + // ); + // element + // }; + + // let mut fixed_block_max_width = 0f32; + // let mut blocks = Vec::new(); + // for (row, block) in fixed_blocks { + // let element = render_block(block, f32::INFINITY, block_id); + // block_id += 1; + // fixed_block_max_width = fixed_block_max_width.max(element.size().x() + em_width); + // blocks.push(BlockLayout { + // row, + // element, + // style: BlockStyle::Fixed, + // }); + // } + // for (row, block) in non_fixed_blocks { + // let style = match block { + // TransformBlock::Custom(block) => block.style(), + // TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky, + // }; + // let width = match style { + // BlockStyle::Sticky => editor_width, + // BlockStyle::Flex => editor_width + // .max(fixed_block_max_width) + // .max(gutter_width + scroll_width), + // BlockStyle::Fixed => unreachable!(), + // }; + // let element = render_block(block, width, block_id); + // block_id += 1; + // blocks.push(BlockLayout { + // row, + // element, + // style, + // }); + // } + // ( + // scroll_width.max(fixed_block_max_width - gutter_width), + // blocks, + // ) + // } +} + +#[derive(Debug)] +pub struct LineWithInvisibles { + pub line: Line, + invisibles: Vec, +} + +// impl LineWithInvisibles { +// fn from_chunks<'a>( +// chunks: impl Iterator>, +// text_style: &TextStyle, +// text_layout_cache: &TextLayoutCache, +// font_cache: &Arc, +// max_line_len: usize, +// max_line_count: usize, +// line_number_layouts: &[Option], +// editor_mode: EditorMode, +// ) -> Vec { +// let mut layouts = Vec::with_capacity(max_line_count); +// let mut line = String::new(); +// let mut invisibles = Vec::new(); +// let mut styles = Vec::new(); +// let mut non_whitespace_added = false; +// let mut row = 0; +// let mut line_exceeded_max_len = false; +// for highlighted_chunk in chunks.chain([HighlightedChunk { +// chunk: "\n", +// style: None, +// is_tab: false, +// }]) { +// for (ix, mut line_chunk) in highlighted_chunk.chunk.split('\n').enumerate() { +// if ix > 0 { +// layouts.push(Self { +// line: text_layout_cache.layout_str(&line, text_style.font_size, &styles), +// invisibles: invisibles.drain(..).collect(), +// }); + +// line.clear(); +// styles.clear(); +// row += 1; +// line_exceeded_max_len = false; +// non_whitespace_added = false; +// if row == max_line_count { +// return layouts; +// } +// } + +// if !line_chunk.is_empty() && !line_exceeded_max_len { +// let text_style = if let Some(style) = highlighted_chunk.style { +// text_style +// .clone() +// .highlight(style, font_cache) +// .map(Cow::Owned) +// .unwrap_or_else(|_| Cow::Borrowed(text_style)) +// } else { +// Cow::Borrowed(text_style) +// }; + +// if line.len() + line_chunk.len() > max_line_len { +// let mut chunk_len = max_line_len - line.len(); +// while !line_chunk.is_char_boundary(chunk_len) { +// chunk_len -= 1; +// } +// line_chunk = &line_chunk[..chunk_len]; +// line_exceeded_max_len = true; +// } + +// styles.push(( +// line_chunk.len(), +// RunStyle { +// font_id: text_style.font_id, +// color: text_style.color, +// underline: text_style.underline, +// }, +// )); + +// if editor_mode == EditorMode::Full { +// // Line wrap pads its contents with fake whitespaces, +// // avoid printing them +// let inside_wrapped_string = line_number_layouts +// .get(row) +// .and_then(|layout| layout.as_ref()) +// .is_none(); +// if highlighted_chunk.is_tab { +// if non_whitespace_added || !inside_wrapped_string { +// invisibles.push(Invisible::Tab { +// line_start_offset: line.len(), +// }); +// } +// } else { +// invisibles.extend( +// line_chunk +// .chars() +// .enumerate() +// .filter(|(_, line_char)| { +// let is_whitespace = line_char.is_whitespace(); +// non_whitespace_added |= !is_whitespace; +// is_whitespace +// && (non_whitespace_added || !inside_wrapped_string) +// }) +// .map(|(whitespace_index, _)| Invisible::Whitespace { +// line_offset: line.len() + whitespace_index, +// }), +// ) +// } +// } + +// line.push_str(line_chunk); +// } +// } +// } + +// layouts +// } + +// fn draw( +// &self, +// layout: &LayoutState, +// row: u32, +// scroll_top: f32, +// content_origin: gpui::Point, +// scroll_left: f32, +// visible_text_bounds: Bounds, +// whitespace_setting: ShowWhitespaceSetting, +// selection_ranges: &[Range], +// visible_bounds: Bounds, +// cx: &mut ViewContext, +// ) { +// let line_height = layout.position_map.line_height; +// let line_y = row as f32 * line_height - scroll_top; + +// self.line.paint( +// content_origin + vec2f(-scroll_left, line_y), +// visible_text_bounds, +// line_height, +// cx, +// ); + +// self.draw_invisibles( +// &selection_ranges, +// layout, +// content_origin, +// scroll_left, +// line_y, +// row, +// visible_bounds, +// line_height, +// whitespace_setting, +// cx, +// ); +// } + +// fn draw_invisibles( +// &self, +// selection_ranges: &[Range], +// layout: &LayoutState, +// content_origin: gpui::Point, +// scroll_left: f32, +// line_y: f32, +// row: u32, +// visible_bounds: Bounds, +// line_height: f32, +// whitespace_setting: ShowWhitespaceSetting, +// cx: &mut ViewContext, +// ) { +// let allowed_invisibles_regions = match whitespace_setting { +// ShowWhitespaceSetting::None => return, +// ShowWhitespaceSetting::Selection => Some(selection_ranges), +// ShowWhitespaceSetting::All => None, +// }; + +// for invisible in &self.invisibles { +// let (&token_offset, invisible_symbol) = match invisible { +// Invisible::Tab { line_start_offset } => (line_start_offset, &layout.tab_invisible), +// Invisible::Whitespace { line_offset } => (line_offset, &layout.space_invisible), +// }; + +// let x_offset = self.line.x_for_index(token_offset); +// let invisible_offset = +// (layout.position_map.em_width - invisible_symbol.width()).max(0.0) / 2.0; +// let origin = content_origin + vec2f(-scroll_left + x_offset + invisible_offset, line_y); + +// if let Some(allowed_regions) = allowed_invisibles_regions { +// let invisible_point = DisplayPoint::new(row, token_offset as u32); +// if !allowed_regions +// .iter() +// .any(|region| region.start <= invisible_point && invisible_point < region.end) +// { +// continue; +// } +// } +// invisible_symbol.paint(origin, visible_bounds, line_height, cx); +// } +// } +// } + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Invisible { + Tab { line_start_offset: usize }, + Whitespace { line_offset: usize }, +} + +impl Element for EditorElement { + type ElementState = (); + + fn id(&self) -> Option { + None + } + + fn initialize( + &mut self, + view_state: &mut Editor, + element_state: Option, + cx: &mut gpui::ViewContext, + ) -> Self::ElementState { + () + } + + fn layout( + &mut self, + view_state: &mut Editor, + element_state: &mut Self::ElementState, + cx: &mut gpui::ViewContext, + ) -> gpui::LayoutId { + let mut style = Style::default(); + style.size.width = relative(1.).into(); + style.size.height = relative(1.).into(); + cx.request_layout(&style, None) + } + + fn paint( + &mut self, + bounds: Bounds, + view_state: &mut Editor, + element_state: &mut Self::ElementState, + cx: &mut gpui::ViewContext, + ) { + let text_style = cx.text_style(); + + let layout_text = cx.text_system().layout_text( + "hello world", + text_style.font_size * cx.rem_size(), + &[text_style.to_run("hello world".len())], + None, + ); + } +} + +// impl EditorElement { +// type LayoutState = LayoutState; +// type PaintState = (); + +// fn layout( +// &mut self, +// constraint: SizeConstraint, +// editor: &mut Editor, +// cx: &mut ViewContext, +// ) -> (gpui::Point, Self::LayoutState) { +// let mut size = constraint.max; +// if size.x().is_infinite() { +// unimplemented!("we don't yet handle an infinite width constraint on buffer elements"); +// } + +// let snapshot = editor.snapshot(cx); +// let style = self.style.clone(); + +// let line_height = (style.text.font_size * style.line_height_scalar).round(); + +// let gutter_padding; +// let gutter_width; +// let gutter_margin; +// if snapshot.show_gutter { +// let em_width = style.text.em_width(cx.font_cache()); +// gutter_padding = (em_width * style.gutter_padding_factor).round(); +// gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0; +// gutter_margin = -style.text.descent(cx.font_cache()); +// } else { +// gutter_padding = 0.0; +// gutter_width = 0.0; +// gutter_margin = 0.0; +// }; + +// let text_width = size.x() - gutter_width; +// let em_width = style.text.em_width(cx.font_cache()); +// let em_advance = style.text.em_advance(cx.font_cache()); +// let overscroll = vec2f(em_width, 0.); +// let snapshot = { +// editor.set_visible_line_count(size.y() / line_height, cx); + +// let editor_width = text_width - gutter_margin - overscroll.x() - em_width; +// let wrap_width = match editor.soft_wrap_mode(cx) { +// SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance, +// SoftWrap::EditorWidth => editor_width, +// SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance), +// }; + +// if editor.set_wrap_width(Some(wrap_width), cx) { +// editor.snapshot(cx) +// } else { +// snapshot +// } +// }; + +// let wrap_guides = editor +// .wrap_guides(cx) +// .iter() +// .map(|(guide, active)| (self.column_pixels(*guide, cx), *active)) +// .collect(); + +// let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height; +// if let EditorMode::AutoHeight { max_lines } = snapshot.mode { +// size.set_y( +// scroll_height +// .min(constraint.max_along(Axis::Vertical)) +// .max(constraint.min_along(Axis::Vertical)) +// .max(line_height) +// .min(line_height * max_lines as f32), +// ) +// } else if let EditorMode::SingleLine = snapshot.mode { +// size.set_y(line_height.max(constraint.min_along(Axis::Vertical))) +// } else if size.y().is_infinite() { +// size.set_y(scroll_height); +// } +// let gutter_size = vec2f(gutter_width, size.y()); +// let text_size = vec2f(text_width, size.y()); + +// let autoscroll_horizontally = editor.autoscroll_vertically(size.y(), line_height, cx); +// let mut snapshot = editor.snapshot(cx); + +// let scroll_position = snapshot.scroll_position(); +// // 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 = scroll_position.y() as u32; +// let height_in_lines = size.y() / line_height; +// let max_row = snapshot.max_point().row(); + +// // Add 1 to ensure selections bleed off screen +// let end_row = 1 + cmp::min( +// (scroll_position.y() + height_in_lines).ceil() as u32, +// max_row, +// ); + +// let start_anchor = if start_row == 0 { +// Anchor::min() +// } else { +// snapshot +// .buffer_snapshot +// .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left)) +// }; +// let end_anchor = if end_row > max_row { +// Anchor::max() +// } else { +// snapshot +// .buffer_snapshot +// .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right)) +// }; + +// let mut selections: Vec<(SelectionStyle, Vec)> = Vec::new(); +// let mut active_rows = BTreeMap::new(); +// let mut fold_ranges = Vec::new(); +// let is_singleton = editor.is_singleton(cx); + +// let highlighted_rows = editor.highlighted_rows(); +// let theme = theme::current(cx); +// let highlighted_ranges = editor.background_highlights_in_range( +// start_anchor..end_anchor, +// &snapshot.display_snapshot, +// theme.as_ref(), +// ); + +// fold_ranges.extend( +// snapshot +// .folds_in_range(start_anchor..end_anchor) +// .map(|anchor| { +// let start = anchor.start.to_point(&snapshot.buffer_snapshot); +// ( +// start.row, +// start.to_display_point(&snapshot.display_snapshot) +// ..anchor.end.to_display_point(&snapshot), +// ) +// }), +// ); + +// let mut newest_selection_head = None; + +// if editor.show_local_selections { +// let mut local_selections: Vec> = editor +// .selections +// .disjoint_in_range(start_anchor..end_anchor, cx); +// local_selections.extend(editor.selections.pending(cx)); +// let mut layouts = Vec::new(); +// let newest = editor.selections.newest(cx); +// for selection in local_selections.drain(..) { +// let is_empty = selection.start == selection.end; +// let is_newest = selection == newest; + +// let layout = SelectionLayout::new( +// selection, +// editor.selections.line_mode, +// editor.cursor_shape, +// &snapshot.display_snapshot, +// is_newest, +// true, +// ); +// if is_newest { +// newest_selection_head = Some(layout.head); +// } + +// for row in cmp::max(layout.active_rows.start, start_row) +// ..=cmp::min(layout.active_rows.end, end_row) +// { +// let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty); +// *contains_non_empty_selection |= !is_empty; +// } +// layouts.push(layout); +// } + +// selections.push((style.selection, layouts)); +// } + +// if let Some(collaboration_hub) = &editor.collaboration_hub { +// // When following someone, render the local selections in their color. +// if let Some(leader_id) = editor.leader_peer_id { +// if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) { +// if let Some(participant_index) = collaboration_hub +// .user_participant_indices(cx) +// .get(&collaborator.user_id) +// { +// if let Some((local_selection_style, _)) = selections.first_mut() { +// *local_selection_style = +// style.selection_style_for_room_participant(participant_index.0); +// } +// } +// } +// } + +// let mut remote_selections = HashMap::default(); +// for selection in snapshot.remote_selections_in_range( +// &(start_anchor..end_anchor), +// collaboration_hub.as_ref(), +// cx, +// ) { +// let selection_style = if let Some(participant_index) = selection.participant_index { +// style.selection_style_for_room_participant(participant_index.0) +// } else { +// style.absent_selection +// }; + +// // Don't re-render the leader's selections, since the local selections +// // match theirs. +// if Some(selection.peer_id) == editor.leader_peer_id { +// continue; +// } + +// remote_selections +// .entry(selection.replica_id) +// .or_insert((selection_style, Vec::new())) +// .1 +// .push(SelectionLayout::new( +// selection.selection, +// selection.line_mode, +// selection.cursor_shape, +// &snapshot.display_snapshot, +// false, +// false, +// )); +// } + +// selections.extend(remote_selections.into_values()); +// } + +// let scrollbar_settings = &settings::get::(cx).scrollbar; +// let show_scrollbars = match scrollbar_settings.show { +// ShowScrollbar::Auto => { +// // Git +// (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs()) +// || +// // Selections +// (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty()) +// // Scrollmanager +// || editor.scroll_manager.scrollbars_visible() +// } +// ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(), +// ShowScrollbar::Always => true, +// ShowScrollbar::Never => false, +// }; + +// let fold_ranges: Vec<(BufferRow, Range, Color)> = fold_ranges +// .into_iter() +// .map(|(id, fold)| { +// let color = self +// .style +// .folds +// .ellipses +// .background +// .style_for(&mut cx.mouse_state::(id as usize)) +// .color; + +// (id, fold, color) +// }) +// .collect(); + +// let head_for_relative = newest_selection_head.unwrap_or_else(|| { +// let newest = editor.selections.newest::(cx); +// SelectionLayout::new( +// newest, +// editor.selections.line_mode, +// editor.cursor_shape, +// &snapshot.display_snapshot, +// true, +// true, +// ) +// .head +// }); + +// let (line_number_layouts, fold_statuses) = self.layout_line_numbers( +// start_row..end_row, +// &active_rows, +// head_for_relative, +// is_singleton, +// &snapshot, +// cx, +// ); + +// let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot); + +// let scrollbar_row_range = scroll_position.y()..(scroll_position.y() + height_in_lines); + +// let mut max_visible_line_width = 0.0; +// let line_layouts = +// self.layout_lines(start_row..end_row, &line_number_layouts, &snapshot, cx); +// for line_with_invisibles in &line_layouts { +// if line_with_invisibles.line.width() > max_visible_line_width { +// max_visible_line_width = line_with_invisibles.line.width(); +// } +// } + +// let style = self.style.clone(); +// let longest_line_width = layout_line( +// snapshot.longest_row(), +// &snapshot, +// &style, +// cx.text_layout_cache(), +// ) +// .width(); +// let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x(); +// let em_width = style.text.em_width(cx.font_cache()); +// let (scroll_width, blocks) = self.layout_blocks( +// start_row..end_row, +// &snapshot, +// size.x(), +// scroll_width, +// gutter_padding, +// gutter_width, +// em_width, +// gutter_width + gutter_margin, +// line_height, +// &style, +// &line_layouts, +// editor, +// cx, +// ); + +// let scroll_max = vec2f( +// ((scroll_width - text_size.x()) / em_width).max(0.0), +// max_row as f32, +// ); + +// let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x()); + +// let autoscrolled = if autoscroll_horizontally { +// editor.autoscroll_horizontally( +// start_row, +// text_size.x(), +// scroll_width, +// em_width, +// &line_layouts, +// cx, +// ) +// } else { +// false +// }; + +// if clamped || autoscrolled { +// snapshot = editor.snapshot(cx); +// } + +// let style = editor.style(cx); + +// let mut context_menu = None; +// let mut code_actions_indicator = None; +// if let Some(newest_selection_head) = newest_selection_head { +// if (start_row..end_row).contains(&newest_selection_head.row()) { +// if editor.context_menu_visible() { +// context_menu = +// editor.render_context_menu(newest_selection_head, style.clone(), cx); +// } + +// let active = matches!( +// editor.context_menu.read().as_ref(), +// Some(crate::ContextMenu::CodeActions(_)) +// ); + +// code_actions_indicator = editor +// .render_code_actions_indicator(&style, active, cx) +// .map(|indicator| (newest_selection_head.row(), indicator)); +// } +// } + +// let visible_rows = start_row..start_row + line_layouts.len() as u32; +// let mut hover = editor.hover_state.render( +// &snapshot, +// &style, +// visible_rows, +// editor.workspace.as_ref().map(|(w, _)| w.clone()), +// cx, +// ); +// let mode = editor.mode; + +// let mut fold_indicators = editor.render_fold_indicators( +// fold_statuses, +// &style, +// editor.gutter_hovered, +// line_height, +// gutter_margin, +// cx, +// ); + +// if let Some((_, context_menu)) = context_menu.as_mut() { +// context_menu.layout( +// SizeConstraint { +// min: gpui::Point::zero(), +// max: vec2f( +// cx.window_size().x() * 0.7, +// (12. * line_height).min((size.y() - line_height) / 2.), +// ), +// }, +// editor, +// cx, +// ); +// } + +// if let Some((_, indicator)) = code_actions_indicator.as_mut() { +// indicator.layout( +// SizeConstraint::strict_along( +// Axis::Vertical, +// line_height * style.code_actions.vertical_scale, +// ), +// editor, +// cx, +// ); +// } + +// for fold_indicator in fold_indicators.iter_mut() { +// if let Some(indicator) = fold_indicator.as_mut() { +// indicator.layout( +// SizeConstraint::strict_along( +// Axis::Vertical, +// line_height * style.code_actions.vertical_scale, +// ), +// editor, +// cx, +// ); +// } +// } + +// if let Some((_, hover_popovers)) = hover.as_mut() { +// for hover_popover in hover_popovers.iter_mut() { +// hover_popover.layout( +// SizeConstraint { +// min: gpui::Point::zero(), +// max: vec2f( +// (120. * em_width) // Default size +// .min(size.x() / 2.) // Shrink to half of the editor width +// .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters +// (16. * line_height) // Default size +// .min(size.y() / 2.) // Shrink to half of the editor height +// .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines +// ), +// }, +// editor, +// cx, +// ); +// } +// } + +// let invisible_symbol_font_size = self.style.text.font_size / 2.0; +// let invisible_symbol_style = RunStyle { +// color: self.style.whitespace, +// font_id: self.style.text.font_id, +// underline: Default::default(), +// }; + +// ( +// size, +// LayoutState { +// mode, +// position_map: Arc::new(PositionMap { +// size, +// scroll_max, +// line_layouts, +// line_height, +// em_width, +// em_advance, +// snapshot, +// }), +// visible_display_row_range: start_row..end_row, +// wrap_guides, +// gutter_size, +// gutter_padding, +// text_size, +// scrollbar_row_range, +// show_scrollbars, +// is_singleton, +// max_row, +// gutter_margin, +// active_rows, +// highlighted_rows, +// highlighted_ranges, +// fold_ranges, +// line_number_layouts, +// display_hunks, +// blocks, +// selections, +// context_menu, +// code_actions_indicator, +// fold_indicators, +// tab_invisible: cx.text_layout_cache().layout_str( +// "→", +// invisible_symbol_font_size, +// &[("→".len(), invisible_symbol_style)], +// ), +// space_invisible: cx.text_layout_cache().layout_str( +// "•", +// invisible_symbol_font_size, +// &[("•".len(), invisible_symbol_style)], +// ), +// hover_popovers: hover, +// }, +// ) +// } + +// fn paint( +// &mut self, +// bounds: Bounds, +// visible_bounds: Bounds, +// layout: &mut Self::LayoutState, +// editor: &mut Editor, +// cx: &mut ViewContext, +// ) -> Self::PaintState { +// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); +// cx.scene().push_layer(Some(visible_bounds)); + +// let gutter_bounds = Bounds::new(bounds.origin(), layout.gutter_size); +// let text_bounds = Bounds::new( +// bounds.origin() + vec2f(layout.gutter_size.x(), 0.0), +// layout.text_size, +// ); + +// Self::attach_mouse_handlers( +// &layout.position_map, +// layout.hover_popovers.is_some(), +// visible_bounds, +// text_bounds, +// gutter_bounds, +// bounds, +// cx, +// ); + +// self.paint_background(gutter_bounds, text_bounds, layout, cx); +// if layout.gutter_size.x() > 0. { +// self.paint_gutter(gutter_bounds, visible_bounds, layout, editor, cx); +// } +// self.paint_text(text_bounds, visible_bounds, layout, editor, cx); + +// cx.scene().push_layer(Some(bounds)); +// if !layout.blocks.is_empty() { +// self.paint_blocks(bounds, visible_bounds, layout, editor, cx); +// } +// self.paint_scrollbar(bounds, layout, &editor, cx); +// cx.scene().pop_layer(); +// cx.scene().pop_layer(); +// } + +// fn rect_for_text_range( +// &self, +// range_utf16: Range, +// bounds: Bounds, +// _: Bounds, +// layout: &Self::LayoutState, +// _: &Self::PaintState, +// _: &Editor, +// _: &ViewContext, +// ) -> Option> { +// let text_bounds = Bounds::new( +// bounds.origin() + vec2f(layout.gutter_size.x(), 0.0), +// layout.text_size, +// ); +// let content_origin = text_bounds.origin() + vec2f(layout.gutter_margin, 0.); +// let scroll_position = layout.position_map.snapshot.scroll_position(); +// let start_row = scroll_position.y() as u32; +// let scroll_top = scroll_position.y() * layout.position_map.line_height; +// let scroll_left = scroll_position.x() * layout.position_map.em_width; + +// let range_start = OffsetUtf16(range_utf16.start) +// .to_display_point(&layout.position_map.snapshot.display_snapshot); +// if range_start.row() < start_row { +// return None; +// } + +// let line = &layout +// .position_map +// .line_layouts +// .get((range_start.row() - start_row) as usize)? +// .line; +// let range_start_x = line.x_for_index(range_start.column() as usize); +// let range_start_y = range_start.row() as f32 * layout.position_map.line_height; +// Some(Bounds::new( +// content_origin +// + vec2f( +// range_start_x, +// range_start_y + layout.position_map.line_height, +// ) +// - vec2f(scroll_left, scroll_top), +// vec2f( +// layout.position_map.em_width, +// layout.position_map.line_height, +// ), +// )) +// } + +// fn debug( +// &self, +// bounds: Bounds, +// _: &Self::LayoutState, +// _: &Self::PaintState, +// _: &Editor, +// _: &ViewContext, +// ) -> json::Value { +// json!({ +// "type": "BufferElement", +// "bounds": bounds.to_json() +// }) +// } +// } + +type BufferRow = u32; + +// pub struct LayoutState { +// position_map: Arc, +// gutter_size: gpui::Point, +// gutter_padding: f32, +// gutter_margin: f32, +// text_size: gpui::Point, +// mode: EditorMode, +// wrap_guides: SmallVec<[(f32, bool); 2]>, +// visible_display_row_range: Range, +// active_rows: BTreeMap, +// highlighted_rows: Option>, +// line_number_layouts: Vec>, +// display_hunks: Vec, +// blocks: Vec, +// highlighted_ranges: Vec<(Range, Color)>, +// fold_ranges: Vec<(BufferRow, Range, Color)>, +// selections: Vec<(SelectionStyle, Vec)>, +// scrollbar_row_range: Range, +// show_scrollbars: bool, +// is_singleton: bool, +// max_row: u32, +// context_menu: Option<(DisplayPoint, AnyElement)>, +// code_actions_indicator: Option<(u32, AnyElement)>, +// hover_popovers: Option<(DisplayPoint, Vec>)>, +// fold_indicators: Vec>>, +// tab_invisible: Line, +// space_invisible: Line, +// } + +struct PositionMap { + size: Size, + line_height: Pixels, + scroll_max: Size, + em_width: Pixels, + em_advance: Pixels, + line_layouts: Vec, + snapshot: EditorSnapshot, +} + +#[derive(Debug, Copy, Clone)] +pub struct PointForPosition { + pub previous_valid: DisplayPoint, + pub next_valid: DisplayPoint, + pub exact_unclipped: DisplayPoint, + pub column_overshoot_after_line_end: u32, +} + +impl PointForPosition { + #[cfg(test)] + pub fn valid(valid: DisplayPoint) -> Self { + Self { + previous_valid: valid, + next_valid: valid, + exact_unclipped: valid, + column_overshoot_after_line_end: 0, + } + } + + pub fn as_valid(&self) -> Option { + if self.previous_valid == self.exact_unclipped && self.next_valid == self.exact_unclipped { + Some(self.previous_valid) + } else { + None + } + } +} + +impl PositionMap { + fn point_for_position( + &self, + text_bounds: Bounds, + position: gpui::Point, + ) -> PointForPosition { + let scroll_position = self.snapshot.scroll_position(); + let position = position - text_bounds.origin; + let y = position.y.max(px(0.)).min(self.size.width); + let x = position.x + (scroll_position.x * self.em_width); + let row = (f32::from(y / self.line_height) + scroll_position.y) as u32; + + let (column, x_overshoot_after_line_end) = if let Some(line) = self + .line_layouts + .get(row as usize - scroll_position.y as usize) + .map(|&LineWithInvisibles { ref line, .. }| line) + { + if let Some(ix) = line.index_for_x(x) { + (ix as u32, px(0.)) + } else { + (line.len as u32, px(0.).max(x - line.width())) + } + } else { + (0, x) + }; + + let mut exact_unclipped = DisplayPoint::new(row, column); + let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left); + let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right); + + let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance).into(); + *exact_unclipped.column_mut() += column_overshoot_after_line_end; + PointForPosition { + previous_valid, + next_valid, + exact_unclipped, + column_overshoot_after_line_end, + } + } +} + +struct BlockLayout { + row: u32, + element: AnyElement, + style: BlockStyle, +} + +fn layout_line( + row: u32, + snapshot: &EditorSnapshot, + style: &EditorStyle, + rem_size: Pixels, + text_system: &TextSystem, +) -> Result> { + let mut line = snapshot.line(row); + + if line.len() > MAX_LINE_LEN { + let mut len = MAX_LINE_LEN; + while !line.is_char_boundary(len) { + len -= 1; + } + + line.truncate(len); + } + + text_system.layout_text( + &line, + style.text.font_size * rem_size, + &[TextRun { + len: snapshot.line_len(row) as usize, + font: style.text.font(), + color: black(), + underline: Default::default(), + }], + None, + ) +} + +#[derive(Debug)] +pub struct Cursor { + origin: gpui::Point, + block_width: Pixels, + line_height: Pixels, + color: Hsla, + shape: CursorShape, + block_text: Option, +} + +impl Cursor { + // pub fn new( + // origin: gpui::Point, + // block_width: f32, + // line_height: f32, + // color: Color, + // shape: CursorShape, + // block_text: Option, + // ) -> Cursor { + // Cursor { + // origin, + // block_width, + // line_height, + // color, + // shape, + // block_text, + // } + // } + + // pub fn bounding_rect(&self, origin: gpui::Point) -> Bounds { + // Bounds::new( + // self.origin + origin, + // vec2f(self.block_width, self.line_height), + // ) + // } + + // pub fn paint(&self, origin: gpui::Point, cx: &mut WindowContext) { + // let bounds = match self.shape { + // CursorShape::Bar => Bounds::new(self.origin + origin, vec2f(2.0, self.line_height)), + // CursorShape::Block | CursorShape::Hollow => Bounds::new( + // self.origin + origin, + // vec2f(self.block_width, self.line_height), + // ), + // CursorShape::Underscore => Bounds::new( + // self.origin + origin + gpui::Point::new(0.0, self.line_height - 2.0), + // vec2f(self.block_width, 2.0), + // ), + // }; + + // //Draw background or border quad + // if matches!(self.shape, CursorShape::Hollow) { + // cx.scene().push_quad(Quad { + // bounds, + // background: None, + // border: Border::all(1., self.color).into(), + // corner_radii: Default::default(), + // }); + // } else { + // cx.scene().push_quad(Quad { + // bounds, + // background: Some(self.color), + // border: Default::default(), + // corner_radii: Default::default(), + // }); + // } + + // if let Some(block_text) = &self.block_text { + // block_text.paint(self.origin + origin, bounds, self.line_height, cx); + // } + // } + + // pub fn shape(&self) -> CursorShape { + // self.shape + // } +} + +#[derive(Debug)] +pub struct HighlightedRange { + pub start_y: Pixels, + pub line_height: Pixels, + pub lines: Vec, + pub color: Hsla, + pub corner_radius: Pixels, +} + +#[derive(Debug)] +pub struct HighlightedRangeLine { + pub start_x: f32, + pub end_x: f32, +} + +impl HighlightedRange { + // pub fn paint(&self, bounds: Bounds, cx: &mut WindowContext) { + // if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x { + // self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx); + // self.paint_lines( + // self.start_y + self.line_height, + // &self.lines[1..], + // bounds, + // cx, + // ); + // } else { + // self.paint_lines(self.start_y, &self.lines, bounds, cx); + // } + // } + + // fn paint_lines( + // &self, + // start_y: f32, + // lines: &[HighlightedRangeLine], + // bounds: Bounds, + // cx: &mut WindowContext, + // ) { + // if lines.is_empty() { + // return; + // } + + // let mut path = PathBuilder::new(); + // let first_line = lines.first().unwrap(); + // let last_line = lines.last().unwrap(); + + // let first_top_left = vec2f(first_line.start_x, start_y); + // let first_top_right = vec2f(first_line.end_x, start_y); + + // let curve_height = vec2f(0., self.corner_radius); + // let curve_width = |start_x: f32, end_x: f32| { + // let max = (end_x - start_x) / 2.; + // let width = if max < self.corner_radius { + // max + // } else { + // self.corner_radius + // }; + + // vec2f(width, 0.) + // }; + + // let top_curve_width = curve_width(first_line.start_x, first_line.end_x); + // path.reset(first_top_right - top_curve_width); + // path.curve_to(first_top_right + curve_height, first_top_right); + + // let mut iter = lines.iter().enumerate().peekable(); + // while let Some((ix, line)) = iter.next() { + // let bottom_right = vec2f(line.end_x, start_y + (ix + 1) as f32 * self.line_height); + + // if let Some((_, next_line)) = iter.peek() { + // let next_top_right = vec2f(next_line.end_x, bottom_right.y()); + + // match next_top_right.x().partial_cmp(&bottom_right.x()).unwrap() { + // Ordering::Equal => { + // path.line_to(bottom_right); + // } + // Ordering::Less => { + // let curve_width = curve_width(next_top_right.x(), bottom_right.x()); + // path.line_to(bottom_right - curve_height); + // if self.corner_radius > 0. { + // path.curve_to(bottom_right - curve_width, bottom_right); + // } + // path.line_to(next_top_right + curve_width); + // if self.corner_radius > 0. { + // path.curve_to(next_top_right + curve_height, next_top_right); + // } + // } + // Ordering::Greater => { + // let curve_width = curve_width(bottom_right.x(), next_top_right.x()); + // path.line_to(bottom_right - curve_height); + // if self.corner_radius > 0. { + // path.curve_to(bottom_right + curve_width, bottom_right); + // } + // path.line_to(next_top_right - curve_width); + // if self.corner_radius > 0. { + // path.curve_to(next_top_right + curve_height, next_top_right); + // } + // } + // } + // } else { + // let curve_width = curve_width(line.start_x, line.end_x); + // path.line_to(bottom_right - curve_height); + // if self.corner_radius > 0. { + // path.curve_to(bottom_right - curve_width, bottom_right); + // } + + // let bottom_left = vec2f(line.start_x, bottom_right.y()); + // path.line_to(bottom_left + curve_width); + // if self.corner_radius > 0. { + // path.curve_to(bottom_left - curve_height, bottom_left); + // } + // } + // } + + // if first_line.start_x > last_line.start_x { + // let curve_width = curve_width(last_line.start_x, first_line.start_x); + // let second_top_left = vec2f(last_line.start_x, start_y + self.line_height); + // path.line_to(second_top_left + curve_height); + // if self.corner_radius > 0. { + // path.curve_to(second_top_left + curve_width, second_top_left); + // } + // let first_bottom_left = vec2f(first_line.start_x, second_top_left.y()); + // path.line_to(first_bottom_left - curve_width); + // if self.corner_radius > 0. { + // path.curve_to(first_bottom_left - curve_height, first_bottom_left); + // } + // } + + // path.line_to(first_top_left + curve_height); + // if self.corner_radius > 0. { + // path.curve_to(first_top_left + top_curve_width, first_top_left); + // } + // path.line_to(first_top_right - top_curve_width); + + // cx.scene().push_path(path.build(self.color, Some(bounds))); + // } +} + +// fn range_to_bounds( +// range: &Range, +// content_origin: gpui::Point, +// scroll_left: f32, +// scroll_top: f32, +// visible_row_range: &Range, +// line_end_overshoot: f32, +// position_map: &PositionMap, +// ) -> impl Iterator> { +// let mut bounds: SmallVec<[Bounds; 1]> = SmallVec::new(); + +// if range.start == range.end { +// return bounds.into_iter(); +// } + +// let start_row = visible_row_range.start; +// let end_row = visible_row_range.end; + +// let row_range = if range.end.column() == 0 { +// cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row) +// } else { +// cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row) +// }; + +// let first_y = +// content_origin.y() + row_range.start as f32 * position_map.line_height - scroll_top; + +// for (idx, row) in row_range.enumerate() { +// let line_layout = &position_map.line_layouts[(row - start_row) as usize].line; + +// let start_x = if row == range.start.row() { +// content_origin.x() + line_layout.x_for_index(range.start.column() as usize) +// - scroll_left +// } else { +// content_origin.x() - scroll_left +// }; + +// let end_x = if row == range.end.row() { +// content_origin.x() + line_layout.x_for_index(range.end.column() as usize) - scroll_left +// } else { +// content_origin.x() + line_layout.width() + line_end_overshoot - scroll_left +// }; + +// bounds.push(Bounds::from_points( +// vec2f(start_x, first_y + position_map.line_height * idx as f32), +// vec2f(end_x, first_y + position_map.line_height * (idx + 1) as f32), +// )) +// } + +// bounds.into_iter() +// } + +pub fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 { + delta.powf(1.5) / 100.0 +} + +fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 { + delta.powf(1.2) / 300.0 +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::{ +// display_map::{BlockDisposition, BlockProperties}, +// editor_tests::{init_test, update_test_language_settings}, +// Editor, MultiBuffer, +// }; +// use gpui::TestAppContext; +// use language::language_settings; +// use log::info; +// use std::{num::NonZeroU32, sync::Arc}; +// use util::test::sample_text; + +// #[gpui::test] +// fn test_layout_line_numbers(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); +// let editor = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx); +// Editor::new(EditorMode::Full, buffer, None, None, cx) +// }) +// .root(cx); +// let element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx))); + +// let layouts = editor.update(cx, |editor, cx| { +// let snapshot = editor.snapshot(cx); +// element +// .layout_line_numbers( +// 0..6, +// &Default::default(), +// DisplayPoint::new(0, 0), +// false, +// &snapshot, +// cx, +// ) +// .0 +// }); +// assert_eq!(layouts.len(), 6); + +// let relative_rows = editor.update(cx, |editor, cx| { +// let snapshot = editor.snapshot(cx); +// element.calculate_relative_line_numbers(&snapshot, &(0..6), Some(3)) +// }); +// assert_eq!(relative_rows[&0], 3); +// assert_eq!(relative_rows[&1], 2); +// assert_eq!(relative_rows[&2], 1); +// // current line has no relative number +// assert_eq!(relative_rows[&4], 1); +// assert_eq!(relative_rows[&5], 2); + +// // works if cursor is before screen +// let relative_rows = editor.update(cx, |editor, cx| { +// let snapshot = editor.snapshot(cx); + +// element.calculate_relative_line_numbers(&snapshot, &(3..6), Some(1)) +// }); +// assert_eq!(relative_rows.len(), 3); +// assert_eq!(relative_rows[&3], 2); +// assert_eq!(relative_rows[&4], 3); +// assert_eq!(relative_rows[&5], 4); + +// // works if cursor is after screen +// let relative_rows = editor.update(cx, |editor, cx| { +// let snapshot = editor.snapshot(cx); + +// element.calculate_relative_line_numbers(&snapshot, &(0..3), Some(6)) +// }); +// assert_eq!(relative_rows.len(), 3); +// assert_eq!(relative_rows[&0], 5); +// assert_eq!(relative_rows[&1], 4); +// assert_eq!(relative_rows[&2], 3); +// } + +// #[gpui::test] +// async fn test_vim_visual_selections(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let editor = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx); +// Editor::new(EditorMode::Full, buffer, None, None, cx) +// }) +// .root(cx); +// let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx))); +// let (_, state) = editor.update(cx, |editor, cx| { +// editor.cursor_shape = CursorShape::Block; +// editor.change_selections(None, cx, |s| { +// s.select_ranges([ +// Point::new(0, 0)..Point::new(1, 0), +// Point::new(3, 2)..Point::new(3, 3), +// Point::new(5, 6)..Point::new(6, 0), +// ]); +// }); +// element.layout( +// SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)), +// editor, +// cx, +// ) +// }); +// assert_eq!(state.selections.len(), 1); +// let local_selections = &state.selections[0].1; +// assert_eq!(local_selections.len(), 3); +// // moves cursor back one line +// assert_eq!(local_selections[0].head, DisplayPoint::new(0, 6)); +// assert_eq!( +// local_selections[0].range, +// DisplayPoint::new(0, 0)..DisplayPoint::new(1, 0) +// ); + +// // moves cursor back one column +// assert_eq!( +// local_selections[1].range, +// DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3) +// ); +// assert_eq!(local_selections[1].head, DisplayPoint::new(3, 2)); + +// // leaves cursor on the max point +// assert_eq!( +// local_selections[2].range, +// DisplayPoint::new(5, 6)..DisplayPoint::new(6, 0) +// ); +// assert_eq!(local_selections[2].head, DisplayPoint::new(6, 0)); + +// // active lines does not include 1 (even though the range of the selection does) +// assert_eq!( +// state.active_rows.keys().cloned().collect::>(), +// vec![0, 3, 5, 6] +// ); + +// // multi-buffer support +// // in DisplayPoint co-ordinates, this is what we're dealing with: +// // 0: [[file +// // 1: header]] +// // 2: aaaaaa +// // 3: bbbbbb +// // 4: cccccc +// // 5: +// // 6: ... +// // 7: ffffff +// // 8: gggggg +// // 9: hhhhhh +// // 10: +// // 11: [[file +// // 12: header]] +// // 13: bbbbbb +// // 14: cccccc +// // 15: dddddd +// let editor = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_multi( +// [ +// ( +// &(sample_text(8, 6, 'a') + "\n"), +// vec![ +// Point::new(0, 0)..Point::new(3, 0), +// Point::new(4, 0)..Point::new(7, 0), +// ], +// ), +// ( +// &(sample_text(8, 6, 'a') + "\n"), +// vec![Point::new(1, 0)..Point::new(3, 0)], +// ), +// ], +// cx, +// ); +// Editor::new(EditorMode::Full, buffer, None, None, cx) +// }) +// .root(cx); +// let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx))); +// let (_, state) = editor.update(cx, |editor, cx| { +// editor.cursor_shape = CursorShape::Block; +// editor.change_selections(None, cx, |s| { +// s.select_display_ranges([ +// DisplayPoint::new(4, 0)..DisplayPoint::new(7, 0), +// DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0), +// ]); +// }); +// element.layout( +// SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)), +// editor, +// cx, +// ) +// }); + +// assert_eq!(state.selections.len(), 1); +// let local_selections = &state.selections[0].1; +// assert_eq!(local_selections.len(), 2); + +// // moves cursor on excerpt boundary back a line +// // and doesn't allow selection to bleed through +// assert_eq!( +// local_selections[0].range, +// DisplayPoint::new(4, 0)..DisplayPoint::new(6, 0) +// ); +// assert_eq!(local_selections[0].head, DisplayPoint::new(5, 0)); + +// // moves cursor on buffer boundary back two lines +// // and doesn't allow selection to bleed through +// assert_eq!( +// local_selections[1].range, +// DisplayPoint::new(10, 0)..DisplayPoint::new(11, 0) +// ); +// assert_eq!(local_selections[1].head, DisplayPoint::new(10, 0)); +// } + +// #[gpui::test] +// fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); + +// let editor = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple("", cx); +// Editor::new(EditorMode::Full, buffer, None, None, cx) +// }) +// .root(cx); + +// editor.update(cx, |editor, cx| { +// editor.set_placeholder_text("hello", cx); +// editor.insert_blocks( +// [BlockProperties { +// style: BlockStyle::Fixed, +// disposition: BlockDisposition::Above, +// height: 3, +// position: Anchor::min(), +// render: Arc::new(|_| Empty::new().into_any()), +// }], +// None, +// cx, +// ); + +// // Blur the editor so that it displays placeholder text. +// cx.blur(); +// }); + +// let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx))); +// let (size, mut state) = editor.update(cx, |editor, cx| { +// element.layout( +// SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)), +// editor, +// cx, +// ) +// }); + +// assert_eq!(state.position_map.line_layouts.len(), 4); +// assert_eq!( +// state +// .line_number_layouts +// .iter() +// .map(Option::is_some) +// .collect::>(), +// &[false, false, false, true] +// ); + +// // Don't panic. +// let bounds = Bounds::new(Default::default(), size); +// editor.update(cx, |editor, cx| { +// element.paint(bounds, bounds, &mut state, editor, cx); +// }); +// } + +// #[gpui::test] +// fn test_all_invisibles_drawing(cx: &mut TestAppContext) { +// const TAB_SIZE: u32 = 4; + +// let input_text = "\t \t|\t| a b"; +// let expected_invisibles = vec![ +// Invisible::Tab { +// line_start_offset: 0, +// }, +// Invisible::Whitespace { +// line_offset: TAB_SIZE as usize, +// }, +// Invisible::Tab { +// line_start_offset: TAB_SIZE as usize + 1, +// }, +// Invisible::Tab { +// line_start_offset: TAB_SIZE as usize * 2 + 1, +// }, +// Invisible::Whitespace { +// line_offset: TAB_SIZE as usize * 3 + 1, +// }, +// Invisible::Whitespace { +// line_offset: TAB_SIZE as usize * 3 + 3, +// }, +// ]; +// assert_eq!( +// expected_invisibles.len(), +// input_text +// .chars() +// .filter(|initial_char| initial_char.is_whitespace()) +// .count(), +// "Hardcoded expected invisibles differ from the actual ones in '{input_text}'" +// ); + +// init_test(cx, |s| { +// s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All); +// s.defaults.tab_size = NonZeroU32::new(TAB_SIZE); +// }); + +// let actual_invisibles = +// collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, 500.0); + +// assert_eq!(expected_invisibles, actual_invisibles); +// } + +// #[gpui::test] +// fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) { +// init_test(cx, |s| { +// s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All); +// s.defaults.tab_size = NonZeroU32::new(4); +// }); + +// for editor_mode_without_invisibles in [ +// EditorMode::SingleLine, +// EditorMode::AutoHeight { max_lines: 100 }, +// ] { +// let invisibles = collect_invisibles_from_new_editor( +// cx, +// editor_mode_without_invisibles, +// "\t\t\t| | a b", +// 500.0, +// ); +// assert!(invisibles.is_empty(), +// "For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}"); +// } +// } + +// #[gpui::test] +// fn test_wrapped_invisibles_drawing(cx: &mut TestAppContext) { +// let tab_size = 4; +// let input_text = "a\tbcd ".repeat(9); +// let repeated_invisibles = [ +// Invisible::Tab { +// line_start_offset: 1, +// }, +// Invisible::Whitespace { +// line_offset: tab_size as usize + 3, +// }, +// Invisible::Whitespace { +// line_offset: tab_size as usize + 4, +// }, +// Invisible::Whitespace { +// line_offset: tab_size as usize + 5, +// }, +// ]; +// let expected_invisibles = std::iter::once(repeated_invisibles) +// .cycle() +// .take(9) +// .flatten() +// .collect::>(); +// assert_eq!( +// expected_invisibles.len(), +// input_text +// .chars() +// .filter(|initial_char| initial_char.is_whitespace()) +// .count(), +// "Hardcoded expected invisibles differ from the actual ones in '{input_text}'" +// ); +// info!("Expected invisibles: {expected_invisibles:?}"); + +// init_test(cx, |_| {}); + +// // Put the same string with repeating whitespace pattern into editors of various size, +// // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point. +// let resize_step = 10.0; +// let mut editor_width = 200.0; +// while editor_width <= 1000.0 { +// update_test_language_settings(cx, |s| { +// s.defaults.tab_size = NonZeroU32::new(tab_size); +// s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All); +// s.defaults.preferred_line_length = Some(editor_width as u32); +// s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength); +// }); + +// let actual_invisibles = +// collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, editor_width); + +// // Whatever the editor size is, ensure it has the same invisible kinds in the same order +// // (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets). +// let mut i = 0; +// for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() { +// i = actual_index; +// match expected_invisibles.get(i) { +// Some(expected_invisible) => match (expected_invisible, actual_invisible) { +// (Invisible::Whitespace { .. }, Invisible::Whitespace { .. }) +// | (Invisible::Tab { .. }, Invisible::Tab { .. }) => {} +// _ => { +// panic!("At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}") +// } +// }, +// None => panic!("Unexpected extra invisible {actual_invisible:?} at index {i}"), +// } +// } +// let missing_expected_invisibles = &expected_invisibles[i + 1..]; +// assert!( +// missing_expected_invisibles.is_empty(), +// "Missing expected invisibles after index {i}: {missing_expected_invisibles:?}" +// ); + +// editor_width += resize_step; +// } +// } + +// fn collect_invisibles_from_new_editor( +// cx: &mut TestAppContext, +// editor_mode: EditorMode, +// input_text: &str, +// editor_width: f32, +// ) -> Vec { +// info!( +// "Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'" +// ); +// let editor = cx +// .add_window(|cx| { +// let buffer = MultiBuffer::build_simple(&input_text, cx); +// Editor::new(editor_mode, buffer, None, None, cx) +// }) +// .root(cx); + +// let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx))); +// let (_, layout_state) = editor.update(cx, |editor, cx| { +// editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx); +// editor.set_wrap_width(Some(editor_width), cx); + +// element.layout( +// SizeConstraint::new(vec2f(editor_width, 500.), vec2f(editor_width, 500.)), +// editor, +// cx, +// ) +// }); + +// layout_state +// .position_map +// .line_layouts +// .iter() +// .map(|line_with_invisibles| &line_with_invisibles.invisibles) +// .flatten() +// .cloned() +// .collect() +// } +// } diff --git a/crates/editor2/src/git.rs b/crates/editor2/src/git.rs new file mode 100644 index 0000000000..e04372f0a7 --- /dev/null +++ b/crates/editor2/src/git.rs @@ -0,0 +1,282 @@ +use std::ops::Range; + +use git::diff::{DiffHunk, DiffHunkStatus}; +use language::Point; + +use crate::{ + display_map::{DisplaySnapshot, ToDisplayPoint}, + AnchorRangeExt, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum DisplayDiffHunk { + Folded { + display_row: u32, + }, + + Unfolded { + display_row_range: Range, + status: DiffHunkStatus, + }, +} + +impl DisplayDiffHunk { + pub fn start_display_row(&self) -> u32 { + match self { + &DisplayDiffHunk::Folded { display_row } => display_row, + DisplayDiffHunk::Unfolded { + display_row_range, .. + } => display_row_range.start, + } + } + + pub fn contains_display_row(&self, display_row: u32) -> bool { + let range = match self { + &DisplayDiffHunk::Folded { display_row } => display_row..=display_row, + + DisplayDiffHunk::Unfolded { + display_row_range, .. + } => display_row_range.start..=display_row_range.end, + }; + + range.contains(&display_row) + } +} + +pub fn diff_hunk_to_display(hunk: DiffHunk, snapshot: &DisplaySnapshot) -> DisplayDiffHunk { + let hunk_start_point = Point::new(hunk.buffer_range.start, 0); + let hunk_start_point_sub = Point::new(hunk.buffer_range.start.saturating_sub(1), 0); + let hunk_end_point_sub = Point::new( + hunk.buffer_range + .end + .saturating_sub(1) + .max(hunk.buffer_range.start), + 0, + ); + + let is_removal = hunk.status() == DiffHunkStatus::Removed; + + let folds_start = Point::new(hunk.buffer_range.start.saturating_sub(2), 0); + let folds_end = Point::new(hunk.buffer_range.end + 2, 0); + let folds_range = folds_start..folds_end; + + let containing_fold = snapshot.folds_in_range(folds_range).find(|fold_range| { + let fold_point_range = fold_range.to_point(&snapshot.buffer_snapshot); + let fold_point_range = fold_point_range.start..=fold_point_range.end; + + let folded_start = fold_point_range.contains(&hunk_start_point); + let folded_end = fold_point_range.contains(&hunk_end_point_sub); + let folded_start_sub = fold_point_range.contains(&hunk_start_point_sub); + + (folded_start && folded_end) || (is_removal && folded_start_sub) + }); + + if let Some(fold) = containing_fold { + let row = fold.start.to_display_point(snapshot).row(); + DisplayDiffHunk::Folded { display_row: row } + } else { + let start = hunk_start_point.to_display_point(snapshot).row(); + + let hunk_end_row = hunk.buffer_range.end.max(hunk.buffer_range.start); + let hunk_end_point = Point::new(hunk_end_row, 0); + let end = hunk_end_point.to_display_point(snapshot).row(); + + DisplayDiffHunk::Unfolded { + display_row_range: start..end, + status: hunk.status(), + } + } +} + +// #[cfg(any(test, feature = "test_support"))] +// mod tests { +// // use crate::editor_tests::init_test; +// use crate::Point; +// use gpui::TestAppContext; +// use multi_buffer::{ExcerptRange, MultiBuffer}; +// use project::{FakeFs, Project}; +// use unindent::Unindent; +// #[gpui::test] +// async fn test_diff_hunks_in_range(cx: &mut TestAppContext) { +// use git::diff::DiffHunkStatus; +// init_test(cx, |_| {}); + +// let fs = FakeFs::new(cx.background()); +// let project = Project::test(fs, [], cx).await; + +// // buffer has two modified hunks with two rows each +// let buffer_1 = project +// .update(cx, |project, cx| { +// project.create_buffer( +// " +// 1.zero +// 1.ONE +// 1.TWO +// 1.three +// 1.FOUR +// 1.FIVE +// 1.six +// " +// .unindent() +// .as_str(), +// None, +// cx, +// ) +// }) +// .unwrap(); +// buffer_1.update(cx, |buffer, cx| { +// buffer.set_diff_base( +// Some( +// " +// 1.zero +// 1.one +// 1.two +// 1.three +// 1.four +// 1.five +// 1.six +// " +// .unindent(), +// ), +// cx, +// ); +// }); + +// // buffer has a deletion hunk and an insertion hunk +// let buffer_2 = project +// .update(cx, |project, cx| { +// project.create_buffer( +// " +// 2.zero +// 2.one +// 2.two +// 2.three +// 2.four +// 2.five +// 2.six +// " +// .unindent() +// .as_str(), +// None, +// cx, +// ) +// }) +// .unwrap(); +// buffer_2.update(cx, |buffer, cx| { +// buffer.set_diff_base( +// Some( +// " +// 2.zero +// 2.one +// 2.one-and-a-half +// 2.two +// 2.three +// 2.four +// 2.six +// " +// .unindent(), +// ), +// cx, +// ); +// }); + +// cx.foreground().run_until_parked(); + +// let multibuffer = cx.add_model(|cx| { +// let mut multibuffer = MultiBuffer::new(0); +// multibuffer.push_excerpts( +// buffer_1.clone(), +// [ +// // excerpt ends in the middle of a modified hunk +// ExcerptRange { +// context: Point::new(0, 0)..Point::new(1, 5), +// primary: Default::default(), +// }, +// // excerpt begins in the middle of a modified hunk +// ExcerptRange { +// context: Point::new(5, 0)..Point::new(6, 5), +// primary: Default::default(), +// }, +// ], +// cx, +// ); +// multibuffer.push_excerpts( +// buffer_2.clone(), +// [ +// // excerpt ends at a deletion +// ExcerptRange { +// context: Point::new(0, 0)..Point::new(1, 5), +// primary: Default::default(), +// }, +// // excerpt starts at a deletion +// ExcerptRange { +// context: Point::new(2, 0)..Point::new(2, 5), +// primary: Default::default(), +// }, +// // excerpt fully contains a deletion hunk +// ExcerptRange { +// context: Point::new(1, 0)..Point::new(2, 5), +// primary: Default::default(), +// }, +// // excerpt fully contains an insertion hunk +// ExcerptRange { +// context: Point::new(4, 0)..Point::new(6, 5), +// primary: Default::default(), +// }, +// ], +// cx, +// ); +// multibuffer +// }); + +// let snapshot = multibuffer.read_with(cx, |b, cx| b.snapshot(cx)); + +// assert_eq!( +// snapshot.text(), +// " +// 1.zero +// 1.ONE +// 1.FIVE +// 1.six +// 2.zero +// 2.one +// 2.two +// 2.one +// 2.two +// 2.four +// 2.five +// 2.six" +// .unindent() +// ); + +// let expected = [ +// (DiffHunkStatus::Modified, 1..2), +// (DiffHunkStatus::Modified, 2..3), +// //TODO: Define better when and where removed hunks show up at range extremities +// (DiffHunkStatus::Removed, 6..6), +// (DiffHunkStatus::Removed, 8..8), +// (DiffHunkStatus::Added, 10..11), +// ]; + +// assert_eq!( +// snapshot +// .git_diff_hunks_in_range(0..12) +// .map(|hunk| (hunk.status(), hunk.buffer_range)) +// .collect::>(), +// &expected, +// ); + +// assert_eq!( +// snapshot +// .git_diff_hunks_in_range_rev(0..12) +// .map(|hunk| (hunk.status(), hunk.buffer_range)) +// .collect::>(), +// expected +// .iter() +// .rev() +// .cloned() +// .collect::>() +// .as_slice(), +// ); +// } +// } diff --git a/crates/editor2/src/highlight_matching_bracket.rs b/crates/editor2/src/highlight_matching_bracket.rs new file mode 100644 index 0000000000..fd8fb6b097 --- /dev/null +++ b/crates/editor2/src/highlight_matching_bracket.rs @@ -0,0 +1,138 @@ +use gpui::ViewContext; + +use crate::{Editor, RangeToAnchorExt}; + +enum MatchingBracketHighlight {} + +pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewContext) { + // editor.clear_background_highlights::(cx); + + let newest_selection = editor.selections.newest::(cx); + // Don't highlight brackets if the selection isn't empty + if !newest_selection.is_empty() { + return; + } + + let head = newest_selection.head(); + let snapshot = editor.snapshot(cx); + if let Some((opening_range, closing_range)) = snapshot + .buffer_snapshot + .innermost_enclosing_bracket_ranges(head..head) + { + editor.highlight_background::( + vec![ + opening_range.to_anchors(&snapshot.buffer_snapshot), + closing_range.to_anchors(&snapshot.buffer_snapshot), + ], + |theme| todo!("theme.editor.document_highlight_read_background"), + cx, + ) + } +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext}; +// use indoc::indoc; +// use language::{BracketPair, BracketPairConfig, Language, LanguageConfig}; + +// #[gpui::test] +// async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorLspTestContext::new( +// Language::new( +// LanguageConfig { +// name: "Rust".into(), +// path_suffixes: vec!["rs".to_string()], +// brackets: BracketPairConfig { +// pairs: vec![ +// BracketPair { +// start: "{".to_string(), +// end: "}".to_string(), +// close: false, +// newline: true, +// }, +// BracketPair { +// start: "(".to_string(), +// end: ")".to_string(), +// close: false, +// newline: true, +// }, +// ], +// ..Default::default() +// }, +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ) +// .with_brackets_query(indoc! {r#" +// ("{" @open "}" @close) +// ("(" @open ")" @close) +// "#}) +// .unwrap(), +// Default::default(), +// cx, +// ) +// .await; + +// // positioning cursor inside bracket highlights both +// cx.set_state(indoc! {r#" +// pub fn test("Test ˇargument") { +// another_test(1, 2, 3); +// } +// "#}); +// cx.assert_editor_background_highlights::(indoc! {r#" +// pub fn test«(»"Test argument"«)» { +// another_test(1, 2, 3); +// } +// "#}); + +// cx.set_state(indoc! {r#" +// pub fn test("Test argument") { +// another_test(1, ˇ2, 3); +// } +// "#}); +// cx.assert_editor_background_highlights::(indoc! {r#" +// pub fn test("Test argument") { +// another_test«(»1, 2, 3«)»; +// } +// "#}); + +// cx.set_state(indoc! {r#" +// pub fn test("Test argument") { +// anotherˇ_test(1, 2, 3); +// } +// "#}); +// cx.assert_editor_background_highlights::(indoc! {r#" +// pub fn test("Test argument") «{» +// another_test(1, 2, 3); +// «}» +// "#}); + +// // positioning outside of brackets removes highlight +// cx.set_state(indoc! {r#" +// pub fˇn test("Test argument") { +// another_test(1, 2, 3); +// } +// "#}); +// cx.assert_editor_background_highlights::(indoc! {r#" +// pub fn test("Test argument") { +// another_test(1, 2, 3); +// } +// "#}); + +// // non empty selection dismisses highlight +// cx.set_state(indoc! {r#" +// pub fn test("Te«st argˇ»ument") { +// another_test(1, 2, 3); +// } +// "#}); +// cx.assert_editor_background_highlights::(indoc! {r#" +// pub fn test("Test argument") { +// another_test(1, 2, 3); +// } +// "#}); +// } +// } diff --git a/crates/editor2/src/hover_popover.rs b/crates/editor2/src/hover_popover.rs new file mode 100644 index 0000000000..9eddd259af --- /dev/null +++ b/crates/editor2/src/hover_popover.rs @@ -0,0 +1,1331 @@ +use crate::{ + display_map::InlayOffset, + link_go_to_definition::{InlayHighlight, RangeInEditor}, + Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, + ExcerptId, RangeToAnchorExt, +}; +use futures::FutureExt; +use gpui::{AnyElement, AppContext, Model, Task, ViewContext, WeakView}; +use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown}; +use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; +use settings::Settings; +use std::{ops::Range, sync::Arc, time::Duration}; +use util::TryFutureExt; +use workspace::Workspace; + +pub const HOVER_DELAY_MILLIS: u64 = 350; +pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200; + +pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.; +pub const MIN_POPOVER_LINE_HEIGHT: f32 = 4.; +pub const HOVER_POPOVER_GAP: f32 = 10.; + +// actions!(editor, [Hover]); + +pub fn init(cx: &mut AppContext) { + // cx.add_action(hover); +} + +// todo!() +// /// Bindable action which uses the most recent selection head to trigger a hover +// pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext) { +// let head = editor.selections.newest_display(cx).head(); +// show_hover(editor, head, true, cx); +// } + +/// The internal hover action dispatches between `show_hover` or `hide_hover` +/// depending on whether a point to hover over is provided. +pub fn hover_at(editor: &mut Editor, point: Option, cx: &mut ViewContext) { + if EditorSettings::get_global(cx).hover_popover_enabled { + if let Some(point) = point { + show_hover(editor, point, false, cx); + } else { + hide_hover(editor, cx); + } + } +} + +pub struct InlayHover { + pub excerpt: ExcerptId, + pub range: InlayHighlight, + pub tooltip: HoverBlock, +} + +pub fn find_hovered_hint_part( + label_parts: Vec, + hint_start: InlayOffset, + hovered_offset: InlayOffset, +) -> Option<(InlayHintLabelPart, Range)> { + if hovered_offset >= hint_start { + let mut hovered_character = (hovered_offset - hint_start).0; + let mut part_start = hint_start; + for part in label_parts { + let part_len = part.value.chars().count(); + if hovered_character > part_len { + hovered_character -= part_len; + part_start.0 += part_len; + } else { + let part_end = InlayOffset(part_start.0 + part_len); + return Some((part, part_start..part_end)); + } + } + } + None +} + +pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut ViewContext) { + todo!() + // if EditorSettings::get_global(cx).hover_popover_enabled { + // if editor.pending_rename.is_some() { + // return; + // } + + // let Some(project) = editor.project.clone() else { + // return; + // }; + + // if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover { + // if let RangeInEditor::Inlay(range) = symbol_range { + // if range == &inlay_hover.range { + // // Hover triggered from same location as last time. Don't show again. + // return; + // } + // } + // hide_hover(editor, cx); + // } + + // let task = cx.spawn(|this, mut cx| { + // async move { + // cx.background_executor() + // .timer(Duration::from_millis(HOVER_DELAY_MILLIS)) + // .await; + // this.update(&mut cx, |this, _| { + // this.hover_state.diagnostic_popover = None; + // })?; + + // let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?; + // let blocks = vec![inlay_hover.tooltip]; + // let parsed_content = parse_blocks(&blocks, &language_registry, None).await; + + // let hover_popover = InfoPopover { + // project: project.clone(), + // symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()), + // blocks, + // parsed_content, + // }; + + // this.update(&mut cx, |this, cx| { + // // Highlight the selected symbol using a background highlight + // this.highlight_inlay_background::( + // vec![inlay_hover.range], + // |theme| theme.editor.hover_popover.highlight, + // cx, + // ); + // this.hover_state.info_popover = Some(hover_popover); + // cx.notify(); + // })?; + + // anyhow::Ok(()) + // } + // .log_err() + // }); + + // editor.hover_state.info_task = Some(task); + // } +} + +/// Hides the type information popup. +/// Triggered by the `Hover` action when the cursor is not over a symbol or when the +/// selections changed. +pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext) -> bool { + let did_hide = editor.hover_state.info_popover.take().is_some() + | editor.hover_state.diagnostic_popover.take().is_some(); + + editor.hover_state.info_task = None; + editor.hover_state.triggered_from = None; + + // todo!() + // editor.clear_background_highlights::(cx); + + if did_hide { + cx.notify(); + } + + did_hide +} + +/// Queries the LSP and shows type info and documentation +/// about the symbol the mouse is currently hovering over. +/// Triggered by the `Hover` action when the cursor may be over a symbol. +fn show_hover( + editor: &mut Editor, + point: DisplayPoint, + ignore_timeout: bool, + cx: &mut ViewContext, +) { + if editor.pending_rename.is_some() { + return; + } + + let snapshot = editor.snapshot(cx); + let multibuffer_offset = point.to_offset(&snapshot.display_snapshot, Bias::Left); + + let (buffer, buffer_position) = if let Some(output) = editor + .buffer + .read(cx) + .text_anchor_for_position(multibuffer_offset, cx) + { + output + } else { + return; + }; + + let excerpt_id = if let Some((excerpt_id, _, _)) = editor + .buffer() + .read(cx) + .excerpt_containing(multibuffer_offset, cx) + { + excerpt_id + } else { + return; + }; + + let project = if let Some(project) = editor.project.clone() { + project + } else { + return; + }; + + if !ignore_timeout { + if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover { + if symbol_range + .as_text_range() + .map(|range| { + range + .to_offset(&snapshot.buffer_snapshot) + .contains(&multibuffer_offset) + }) + .unwrap_or(false) + { + // Hover triggered from same location as last time. Don't show again. + return; + } else { + hide_hover(editor, cx); + } + } + } + + // Get input anchor + let anchor = snapshot + .buffer_snapshot + .anchor_at(multibuffer_offset, Bias::Left); + + // Don't request again if the location is the same as the previous request + if let Some(triggered_from) = &editor.hover_state.triggered_from { + if triggered_from + .cmp(&anchor, &snapshot.buffer_snapshot) + .is_eq() + { + return; + } + } + + let task = cx.spawn(|this, mut cx| { + async move { + // If we need to delay, delay a set amount initially before making the lsp request + let delay = if !ignore_timeout { + // Construct delay task to wait for later + let total_delay = Some( + cx.background_executor() + .timer(Duration::from_millis(HOVER_DELAY_MILLIS)), + ); + + cx.background_executor() + .timer(Duration::from_millis(HOVER_REQUEST_DELAY_MILLIS)) + .await; + total_delay + } else { + None + }; + + // query the LSP for hover info + let hover_request = cx.update(|_, cx| { + project.update(cx, |project, cx| { + project.hover(&buffer, buffer_position, cx) + }) + })?; + + if let Some(delay) = delay { + delay.await; + } + + // If there's a diagnostic, assign it on the hover state and notify + let local_diagnostic = snapshot + .buffer_snapshot + .diagnostics_in_range::<_, usize>(multibuffer_offset..multibuffer_offset, false) + // Find the entry with the most specific range + .min_by_key(|entry| entry.range.end - entry.range.start) + .map(|entry| DiagnosticEntry { + diagnostic: entry.diagnostic, + range: entry.range.to_anchors(&snapshot.buffer_snapshot), + }); + + // Pull the primary diagnostic out so we can jump to it if the popover is clicked + let primary_diagnostic = local_diagnostic.as_ref().and_then(|local_diagnostic| { + snapshot + .buffer_snapshot + .diagnostic_group::(local_diagnostic.diagnostic.group_id) + .find(|diagnostic| diagnostic.diagnostic.is_primary) + .map(|entry| DiagnosticEntry { + diagnostic: entry.diagnostic, + range: entry.range.to_anchors(&snapshot.buffer_snapshot), + }) + }); + + this.update(&mut cx, |this, _| { + this.hover_state.diagnostic_popover = + local_diagnostic.map(|local_diagnostic| DiagnosticPopover { + local_diagnostic, + primary_diagnostic, + }); + })?; + + let hover_result = hover_request.await.ok().flatten(); + let hover_popover = match hover_result { + Some(hover_result) if !hover_result.is_empty() => { + // Create symbol range of anchors for highlighting and filtering of future requests. + let range = if let Some(range) = hover_result.range { + let start = snapshot + .buffer_snapshot + .anchor_in_excerpt(excerpt_id.clone(), range.start); + let end = snapshot + .buffer_snapshot + .anchor_in_excerpt(excerpt_id.clone(), range.end); + + start..end + } else { + anchor..anchor + }; + + let language_registry = + project.update(&mut cx, |p, _| p.languages().clone())?; + let blocks = hover_result.contents; + let language = hover_result.language; + let parsed_content = parse_blocks(&blocks, &language_registry, language).await; + + Some(InfoPopover { + project: project.clone(), + symbol_range: RangeInEditor::Text(range), + blocks, + parsed_content, + }) + } + + _ => None, + }; + + this.update(&mut cx, |this, cx| { + todo!(); + // if let Some(symbol_range) = hover_popover + // .as_ref() + // .and_then(|hover_popover| hover_popover.symbol_range.as_text_range()) + // { + // // Highlight the selected symbol using a background highlight + // this.highlight_background::( + // vec![symbol_range], + // |theme| theme.editor.hover_popover.highlight, + // cx, + // ); + // } else { + // this.clear_background_highlights::(cx); + // } + // + // this.hover_state.info_popover = hover_popover; + // cx.notify(); + })?; + + Ok::<_, anyhow::Error>(()) + } + .log_err() + }); + + editor.hover_state.info_task = Some(task); +} + +async fn parse_blocks( + blocks: &[HoverBlock], + language_registry: &Arc, + language: Option>, +) -> markdown::ParsedMarkdown { + let mut text = String::new(); + let mut highlights = Vec::new(); + let mut region_ranges = Vec::new(); + let mut regions = Vec::new(); + + for block in blocks { + match &block.kind { + HoverBlockKind::PlainText => { + markdown::new_paragraph(&mut text, &mut Vec::new()); + text.push_str(&block.text); + } + + HoverBlockKind::Markdown => { + markdown::parse_markdown_block( + &block.text, + language_registry, + language.clone(), + &mut text, + &mut highlights, + &mut region_ranges, + &mut regions, + ) + .await + } + + HoverBlockKind::Code { language } => { + if let Some(language) = language_registry + .language_for_name(language) + .now_or_never() + .and_then(Result::ok) + { + markdown::highlight_code(&mut text, &mut highlights, &block.text, &language); + } else { + text.push_str(&block.text); + } + } + } + } + + ParsedMarkdown { + text: text.trim().to_string(), + highlights, + region_ranges, + regions, + } +} + +#[derive(Default)] +pub struct HoverState { + pub info_popover: Option, + pub diagnostic_popover: Option, + pub triggered_from: Option, + pub info_task: Option>>, +} + +impl HoverState { + pub fn visible(&self) -> bool { + self.info_popover.is_some() || self.diagnostic_popover.is_some() + } + + pub fn render( + &mut self, + snapshot: &EditorSnapshot, + style: &EditorStyle, + visible_rows: Range, + workspace: Option>, + cx: &mut ViewContext, + ) -> Option<(DisplayPoint, Vec>)> { + todo!("old version below") + } + // // If there is a diagnostic, position the popovers based on that. + // // Otherwise use the start of the hover range + // let anchor = self + // .diagnostic_popover + // .as_ref() + // .map(|diagnostic_popover| &diagnostic_popover.local_diagnostic.range.start) + // .or_else(|| { + // self.info_popover + // .as_ref() + // .map(|info_popover| match &info_popover.symbol_range { + // RangeInEditor::Text(range) => &range.start, + // RangeInEditor::Inlay(range) => &range.inlay_position, + // }) + // })?; + // let point = anchor.to_display_point(&snapshot.display_snapshot); + + // // Don't render if the relevant point isn't on screen + // if !self.visible() || !visible_rows.contains(&point.row()) { + // return None; + // } + + // let mut elements = Vec::new(); + + // if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() { + // elements.push(diagnostic_popover.render(style, cx)); + // } + // if let Some(info_popover) = self.info_popover.as_mut() { + // elements.push(info_popover.render(style, workspace, cx)); + // } + + // Some((point, elements)) + // } +} + +#[derive(Debug, Clone)] +pub struct InfoPopover { + pub project: Model, + symbol_range: RangeInEditor, + pub blocks: Vec, + parsed_content: ParsedMarkdown, +} + +// impl InfoPopover { +// pub fn render( +// &mut self, +// style: &EditorStyle, +// workspace: Option>, +// cx: &mut ViewContext, +// ) -> AnyElement { +// MouseEventHandler::new::(0, cx, |_, cx| { +// Flex::column() +// .scrollable::(0, None, cx) +// .with_child(crate::render_parsed_markdown::( +// &self.parsed_content, +// style, +// workspace, +// cx, +// )) +// .contained() +// .with_style(style.hover_popover.container) +// }) +// .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath. +// .with_cursor_style(CursorStyle::Arrow) +// .with_padding(Padding { +// bottom: HOVER_POPOVER_GAP, +// top: HOVER_POPOVER_GAP, +// ..Default::default() +// }) +// .into_any() +// } +// } + +#[derive(Debug, Clone)] +pub struct DiagnosticPopover { + local_diagnostic: DiagnosticEntry, + primary_diagnostic: Option>, +} + +impl DiagnosticPopover { + pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext) -> AnyElement { + todo!() + // enum PrimaryDiagnostic {} + + // let mut text_style = style.hover_popover.prose.clone(); + // text_style.font_size = style.text.font_size; + // let diagnostic_source_style = style.hover_popover.diagnostic_source_highlight.clone(); + + // let text = match &self.local_diagnostic.diagnostic.source { + // Some(source) => Text::new( + // format!("{source}: {}", self.local_diagnostic.diagnostic.message), + // text_style, + // ) + // .with_highlights(vec![(0..source.len(), diagnostic_source_style)]), + + // None => Text::new(self.local_diagnostic.diagnostic.message.clone(), text_style), + // }; + + // let container_style = match self.local_diagnostic.diagnostic.severity { + // DiagnosticSeverity::HINT => style.hover_popover.info_container, + // DiagnosticSeverity::INFORMATION => style.hover_popover.info_container, + // DiagnosticSeverity::WARNING => style.hover_popover.warning_container, + // DiagnosticSeverity::ERROR => style.hover_popover.error_container, + // _ => style.hover_popover.container, + // }; + + // let tooltip_style = theme::current(cx).tooltip.clone(); + + // MouseEventHandler::new::(0, cx, |_, _| { + // text.with_soft_wrap(true) + // .contained() + // .with_style(container_style) + // }) + // .with_padding(Padding { + // top: HOVER_POPOVER_GAP, + // bottom: HOVER_POPOVER_GAP, + // ..Default::default() + // }) + // .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath. + // .on_click(MouseButton::Left, |_, this, cx| { + // this.go_to_diagnostic(&Default::default(), cx) + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .with_tooltip::( + // 0, + // "Go To Diagnostic".to_string(), + // Some(Box::new(crate::GoToDiagnostic)), + // tooltip_style, + // cx, + // ) + // .into_any() + } + + pub fn activation_info(&self) -> (usize, Anchor) { + let entry = self + .primary_diagnostic + .as_ref() + .unwrap_or(&self.local_diagnostic); + + (entry.diagnostic.group_id, entry.range.start.clone()) + } +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::{ +// editor_tests::init_test, +// element::PointForPosition, +// inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, +// link_go_to_definition::update_inlay_link_and_hover_points, +// test::editor_lsp_test_context::EditorLspTestContext, +// InlayId, +// }; +// use collections::BTreeSet; +// use gpui::fonts::{HighlightStyle, Underline, Weight}; +// use indoc::indoc; +// use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; +// use lsp::LanguageServerId; +// use project::{HoverBlock, HoverBlockKind}; +// use smol::stream::StreamExt; +// use unindent::Unindent; +// use util::test::marked_text_ranges; + +// #[gpui::test] +// async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorLspTestContext::new_rust( +// lsp::ServerCapabilities { +// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), +// ..Default::default() +// }, +// cx, +// ) +// .await; + +// // Basic hover delays and then pops without moving the mouse +// cx.set_state(indoc! {" +// fn ˇtest() { println!(); } +// "}); +// let hover_point = cx.display_point(indoc! {" +// fn test() { printˇln!(); } +// "}); + +// cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx)); +// assert!(!cx.editor(|editor, _| editor.hover_state.visible())); + +// // After delay, hover should be visible. +// let symbol_range = cx.lsp_range(indoc! {" +// fn test() { «println!»(); } +// "}); +// let mut requests = +// cx.handle_request::(move |_, _, _| async move { +// Ok(Some(lsp::Hover { +// contents: lsp::HoverContents::Markup(lsp::MarkupContent { +// kind: lsp::MarkupKind::Markdown, +// value: "some basic docs".to_string(), +// }), +// range: Some(symbol_range), +// })) +// }); +// cx.foreground() +// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); +// requests.next().await; + +// cx.editor(|editor, _| { +// assert!(editor.hover_state.visible()); +// assert_eq!( +// editor.hover_state.info_popover.clone().unwrap().blocks, +// vec![HoverBlock { +// text: "some basic docs".to_string(), +// kind: HoverBlockKind::Markdown, +// },] +// ) +// }); + +// // Mouse moved with no hover response dismisses +// let hover_point = cx.display_point(indoc! {" +// fn teˇst() { println!(); } +// "}); +// let mut request = cx +// .lsp +// .handle_request::(|_, _| async move { Ok(None) }); +// cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx)); +// cx.foreground() +// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); +// request.next().await; +// cx.editor(|editor, _| { +// assert!(!editor.hover_state.visible()); +// }); +// } + +// #[gpui::test] +// async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorLspTestContext::new_rust( +// lsp::ServerCapabilities { +// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), +// ..Default::default() +// }, +// cx, +// ) +// .await; + +// // Hover with keyboard has no delay +// cx.set_state(indoc! {" +// fˇn test() { println!(); } +// "}); +// cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); +// let symbol_range = cx.lsp_range(indoc! {" +// «fn» test() { println!(); } +// "}); +// cx.handle_request::(move |_, _, _| async move { +// Ok(Some(lsp::Hover { +// contents: lsp::HoverContents::Markup(lsp::MarkupContent { +// kind: lsp::MarkupKind::Markdown, +// value: "some other basic docs".to_string(), +// }), +// range: Some(symbol_range), +// })) +// }) +// .next() +// .await; + +// cx.condition(|editor, _| editor.hover_state.visible()).await; +// cx.editor(|editor, _| { +// assert_eq!( +// editor.hover_state.info_popover.clone().unwrap().blocks, +// vec![HoverBlock { +// text: "some other basic docs".to_string(), +// kind: HoverBlockKind::Markdown, +// }] +// ) +// }); +// } + +// #[gpui::test] +// async fn test_empty_hovers_filtered(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorLspTestContext::new_rust( +// lsp::ServerCapabilities { +// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), +// ..Default::default() +// }, +// cx, +// ) +// .await; + +// // Hover with keyboard has no delay +// cx.set_state(indoc! {" +// fˇn test() { println!(); } +// "}); +// cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); +// let symbol_range = cx.lsp_range(indoc! {" +// «fn» test() { println!(); } +// "}); +// cx.handle_request::(move |_, _, _| async move { +// Ok(Some(lsp::Hover { +// contents: lsp::HoverContents::Array(vec![ +// lsp::MarkedString::String("regular text for hover to show".to_string()), +// lsp::MarkedString::String("".to_string()), +// lsp::MarkedString::LanguageString(lsp::LanguageString { +// language: "Rust".to_string(), +// value: "".to_string(), +// }), +// ]), +// range: Some(symbol_range), +// })) +// }) +// .next() +// .await; + +// cx.condition(|editor, _| editor.hover_state.visible()).await; +// cx.editor(|editor, _| { +// assert_eq!( +// editor.hover_state.info_popover.clone().unwrap().blocks, +// vec![HoverBlock { +// text: "regular text for hover to show".to_string(), +// kind: HoverBlockKind::Markdown, +// }], +// "No empty string hovers should be shown" +// ); +// }); +// } + +// #[gpui::test] +// async fn test_line_ends_trimmed(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorLspTestContext::new_rust( +// lsp::ServerCapabilities { +// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), +// ..Default::default() +// }, +// cx, +// ) +// .await; + +// // Hover with keyboard has no delay +// cx.set_state(indoc! {" +// fˇn test() { println!(); } +// "}); +// cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); +// let symbol_range = cx.lsp_range(indoc! {" +// «fn» test() { println!(); } +// "}); + +// let code_str = "\nlet hovered_point: Vector2F // size = 8, align = 0x4\n"; +// let markdown_string = format!("\n```rust\n{code_str}```"); + +// let closure_markdown_string = markdown_string.clone(); +// cx.handle_request::(move |_, _, _| { +// let future_markdown_string = closure_markdown_string.clone(); +// async move { +// Ok(Some(lsp::Hover { +// contents: lsp::HoverContents::Markup(lsp::MarkupContent { +// kind: lsp::MarkupKind::Markdown, +// value: future_markdown_string, +// }), +// range: Some(symbol_range), +// })) +// } +// }) +// .next() +// .await; + +// cx.condition(|editor, _| editor.hover_state.visible()).await; +// cx.editor(|editor, _| { +// let blocks = editor.hover_state.info_popover.clone().unwrap().blocks; +// assert_eq!( +// blocks, +// vec![HoverBlock { +// text: markdown_string, +// kind: HoverBlockKind::Markdown, +// }], +// ); + +// let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); +// assert_eq!( +// rendered.text, +// code_str.trim(), +// "Should not have extra line breaks at end of rendered hover" +// ); +// }); +// } + +// #[gpui::test] +// async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorLspTestContext::new_rust( +// lsp::ServerCapabilities { +// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), +// ..Default::default() +// }, +// cx, +// ) +// .await; + +// // Hover with just diagnostic, pops DiagnosticPopover immediately and then +// // info popover once request completes +// cx.set_state(indoc! {" +// fn teˇst() { println!(); } +// "}); + +// // Send diagnostic to client +// let range = cx.text_anchor_range(indoc! {" +// fn «test»() { println!(); } +// "}); +// cx.update_buffer(|buffer, cx| { +// let snapshot = buffer.text_snapshot(); +// let set = DiagnosticSet::from_sorted_entries( +// vec![DiagnosticEntry { +// range, +// diagnostic: Diagnostic { +// message: "A test diagnostic message.".to_string(), +// ..Default::default() +// }, +// }], +// &snapshot, +// ); +// buffer.update_diagnostics(LanguageServerId(0), set, cx); +// }); + +// // Hover pops diagnostic immediately +// cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); +// cx.foreground().run_until_parked(); + +// cx.editor(|Editor { hover_state, .. }, _| { +// assert!(hover_state.diagnostic_popover.is_some() && hover_state.info_popover.is_none()) +// }); + +// // Info Popover shows after request responded to +// let range = cx.lsp_range(indoc! {" +// fn «test»() { println!(); } +// "}); +// cx.handle_request::(move |_, _, _| async move { +// Ok(Some(lsp::Hover { +// contents: lsp::HoverContents::Markup(lsp::MarkupContent { +// kind: lsp::MarkupKind::Markdown, +// value: "some new docs".to_string(), +// }), +// range: Some(range), +// })) +// }); +// cx.foreground() +// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); + +// cx.foreground().run_until_parked(); +// cx.editor(|Editor { hover_state, .. }, _| { +// hover_state.diagnostic_popover.is_some() && hover_state.info_task.is_some() +// }); +// } + +// #[gpui::test] +// fn test_render_blocks(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// cx.add_window(|cx| { +// let editor = Editor::single_line(None, cx); +// let style = editor.style(cx); + +// struct Row { +// blocks: Vec, +// expected_marked_text: String, +// expected_styles: Vec, +// } + +// let rows = &[ +// // Strong emphasis +// Row { +// blocks: vec![HoverBlock { +// text: "one **two** three".to_string(), +// kind: HoverBlockKind::Markdown, +// }], +// expected_marked_text: "one «two» three".to_string(), +// expected_styles: vec![HighlightStyle { +// weight: Some(Weight::BOLD), +// ..Default::default() +// }], +// }, +// // Links +// Row { +// blocks: vec![HoverBlock { +// text: "one [two](https://the-url) three".to_string(), +// kind: HoverBlockKind::Markdown, +// }], +// expected_marked_text: "one «two» three".to_string(), +// expected_styles: vec![HighlightStyle { +// underline: Some(Underline { +// thickness: 1.0.into(), +// ..Default::default() +// }), +// ..Default::default() +// }], +// }, +// // Lists +// Row { +// blocks: vec![HoverBlock { +// text: " +// lists: +// * one +// - a +// - b +// * two +// - [c](https://the-url) +// - d" +// .unindent(), +// kind: HoverBlockKind::Markdown, +// }], +// expected_marked_text: " +// lists: +// - one +// - a +// - b +// - two +// - «c» +// - d" +// .unindent(), +// expected_styles: vec![HighlightStyle { +// underline: Some(Underline { +// thickness: 1.0.into(), +// ..Default::default() +// }), +// ..Default::default() +// }], +// }, +// // Multi-paragraph list items +// Row { +// blocks: vec![HoverBlock { +// text: " +// * one two +// three + +// * four five +// * six seven +// eight + +// nine +// * ten +// * six" +// .unindent(), +// kind: HoverBlockKind::Markdown, +// }], +// expected_marked_text: " +// - one two three +// - four five +// - six seven eight + +// nine +// - ten +// - six" +// .unindent(), +// expected_styles: vec![HighlightStyle { +// underline: Some(Underline { +// thickness: 1.0.into(), +// ..Default::default() +// }), +// ..Default::default() +// }], +// }, +// ]; + +// for Row { +// blocks, +// expected_marked_text, +// expected_styles, +// } in &rows[0..] +// { +// let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); + +// let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); +// let expected_highlights = ranges +// .into_iter() +// .zip(expected_styles.iter().cloned()) +// .collect::>(); +// assert_eq!( +// rendered.text, expected_text, +// "wrong text for input {blocks:?}" +// ); + +// let rendered_highlights: Vec<_> = rendered +// .highlights +// .iter() +// .filter_map(|(range, highlight)| { +// let highlight = highlight.to_highlight_style(&style.syntax)?; +// Some((range.clone(), highlight)) +// }) +// .collect(); + +// assert_eq!( +// rendered_highlights, expected_highlights, +// "wrong highlights for input {blocks:?}" +// ); +// } + +// editor +// }); +// } + +// #[gpui::test] +// async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: true, +// show_type_hints: true, +// show_parameter_hints: true, +// show_other_hints: true, +// }) +// }); + +// let mut cx = EditorLspTestContext::new_rust( +// lsp::ServerCapabilities { +// inlay_hint_provider: Some(lsp::OneOf::Right( +// lsp::InlayHintServerCapabilities::Options(lsp::InlayHintOptions { +// resolve_provider: Some(true), +// ..Default::default() +// }), +// )), +// ..Default::default() +// }, +// cx, +// ) +// .await; + +// cx.set_state(indoc! {" +// struct TestStruct; + +// // ================== + +// struct TestNewType(T); + +// fn main() { +// let variableˇ = TestNewType(TestStruct); +// } +// "}); + +// let hint_start_offset = cx.ranges(indoc! {" +// struct TestStruct; + +// // ================== + +// struct TestNewType(T); + +// fn main() { +// let variableˇ = TestNewType(TestStruct); +// } +// "})[0] +// .start; +// let hint_position = cx.to_lsp(hint_start_offset); +// let new_type_target_range = cx.lsp_range(indoc! {" +// struct TestStruct; + +// // ================== + +// struct «TestNewType»(T); + +// fn main() { +// let variable = TestNewType(TestStruct); +// } +// "}); +// let struct_target_range = cx.lsp_range(indoc! {" +// struct «TestStruct»; + +// // ================== + +// struct TestNewType(T); + +// fn main() { +// let variable = TestNewType(TestStruct); +// } +// "}); + +// let uri = cx.buffer_lsp_url.clone(); +// let new_type_label = "TestNewType"; +// let struct_label = "TestStruct"; +// let entire_hint_label = ": TestNewType"; +// let closure_uri = uri.clone(); +// cx.lsp +// .handle_request::(move |params, _| { +// let task_uri = closure_uri.clone(); +// async move { +// assert_eq!(params.text_document.uri, task_uri); +// Ok(Some(vec![lsp::InlayHint { +// position: hint_position, +// label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart { +// value: entire_hint_label.to_string(), +// ..Default::default() +// }]), +// kind: Some(lsp::InlayHintKind::TYPE), +// text_edits: None, +// tooltip: None, +// padding_left: Some(false), +// padding_right: Some(false), +// data: None, +// }])) +// } +// }) +// .next() +// .await; +// cx.foreground().run_until_parked(); +// cx.update_editor(|editor, cx| { +// let expected_layers = vec![entire_hint_label.to_string()]; +// assert_eq!(expected_layers, cached_hint_labels(editor)); +// assert_eq!(expected_layers, visible_hint_labels(editor, cx)); +// }); + +// let inlay_range = cx +// .ranges(indoc! {" +// struct TestStruct; + +// // ================== + +// struct TestNewType(T); + +// fn main() { +// let variable« »= TestNewType(TestStruct); +// } +// "}) +// .get(0) +// .cloned() +// .unwrap(); +// let new_type_hint_part_hover_position = cx.update_editor(|editor, cx| { +// let snapshot = editor.snapshot(cx); +// let previous_valid = inlay_range.start.to_display_point(&snapshot); +// let next_valid = inlay_range.end.to_display_point(&snapshot); +// assert_eq!(previous_valid.row(), next_valid.row()); +// assert!(previous_valid.column() < next_valid.column()); +// let exact_unclipped = DisplayPoint::new( +// previous_valid.row(), +// previous_valid.column() +// + (entire_hint_label.find(new_type_label).unwrap() + new_type_label.len() / 2) +// as u32, +// ); +// PointForPosition { +// previous_valid, +// next_valid, +// exact_unclipped, +// column_overshoot_after_line_end: 0, +// } +// }); +// cx.update_editor(|editor, cx| { +// update_inlay_link_and_hover_points( +// &editor.snapshot(cx), +// new_type_hint_part_hover_position, +// editor, +// true, +// false, +// cx, +// ); +// }); + +// let resolve_closure_uri = uri.clone(); +// cx.lsp +// .handle_request::( +// move |mut hint_to_resolve, _| { +// let mut resolved_hint_positions = BTreeSet::new(); +// let task_uri = resolve_closure_uri.clone(); +// async move { +// let inserted = resolved_hint_positions.insert(hint_to_resolve.position); +// assert!(inserted, "Hint {hint_to_resolve:?} was resolved twice"); + +// // `: TestNewType` +// hint_to_resolve.label = lsp::InlayHintLabel::LabelParts(vec![ +// lsp::InlayHintLabelPart { +// value: ": ".to_string(), +// ..Default::default() +// }, +// lsp::InlayHintLabelPart { +// value: new_type_label.to_string(), +// location: Some(lsp::Location { +// uri: task_uri.clone(), +// range: new_type_target_range, +// }), +// tooltip: Some(lsp::InlayHintLabelPartTooltip::String(format!( +// "A tooltip for `{new_type_label}`" +// ))), +// ..Default::default() +// }, +// lsp::InlayHintLabelPart { +// value: "<".to_string(), +// ..Default::default() +// }, +// lsp::InlayHintLabelPart { +// value: struct_label.to_string(), +// location: Some(lsp::Location { +// uri: task_uri, +// range: struct_target_range, +// }), +// tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent( +// lsp::MarkupContent { +// kind: lsp::MarkupKind::Markdown, +// value: format!("A tooltip for `{struct_label}`"), +// }, +// )), +// ..Default::default() +// }, +// lsp::InlayHintLabelPart { +// value: ">".to_string(), +// ..Default::default() +// }, +// ]); + +// Ok(hint_to_resolve) +// } +// }, +// ) +// .next() +// .await; +// cx.foreground().run_until_parked(); + +// cx.update_editor(|editor, cx| { +// update_inlay_link_and_hover_points( +// &editor.snapshot(cx), +// new_type_hint_part_hover_position, +// editor, +// true, +// false, +// cx, +// ); +// }); +// cx.foreground() +// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); +// cx.foreground().run_until_parked(); +// cx.update_editor(|editor, cx| { +// let hover_state = &editor.hover_state; +// assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); +// let popover = hover_state.info_popover.as_ref().unwrap(); +// let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); +// assert_eq!( +// popover.symbol_range, +// RangeInEditor::Inlay(InlayHighlight { +// inlay: InlayId::Hint(0), +// inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), +// range: ": ".len()..": ".len() + new_type_label.len(), +// }), +// "Popover range should match the new type label part" +// ); +// assert_eq!( +// popover.parsed_content.text, +// format!("A tooltip for `{new_type_label}`"), +// "Rendered text should not anyhow alter backticks" +// ); +// }); + +// let struct_hint_part_hover_position = cx.update_editor(|editor, cx| { +// let snapshot = editor.snapshot(cx); +// let previous_valid = inlay_range.start.to_display_point(&snapshot); +// let next_valid = inlay_range.end.to_display_point(&snapshot); +// assert_eq!(previous_valid.row(), next_valid.row()); +// assert!(previous_valid.column() < next_valid.column()); +// let exact_unclipped = DisplayPoint::new( +// previous_valid.row(), +// previous_valid.column() +// + (entire_hint_label.find(struct_label).unwrap() + struct_label.len() / 2) +// as u32, +// ); +// PointForPosition { +// previous_valid, +// next_valid, +// exact_unclipped, +// column_overshoot_after_line_end: 0, +// } +// }); +// cx.update_editor(|editor, cx| { +// update_inlay_link_and_hover_points( +// &editor.snapshot(cx), +// struct_hint_part_hover_position, +// editor, +// true, +// false, +// cx, +// ); +// }); +// cx.foreground() +// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); +// cx.foreground().run_until_parked(); +// cx.update_editor(|editor, cx| { +// let hover_state = &editor.hover_state; +// assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); +// let popover = hover_state.info_popover.as_ref().unwrap(); +// let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); +// assert_eq!( +// popover.symbol_range, +// RangeInEditor::Inlay(InlayHighlight { +// inlay: InlayId::Hint(0), +// inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), +// range: ": ".len() + new_type_label.len() + "<".len() +// ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(), +// }), +// "Popover range should match the struct label part" +// ); +// assert_eq!( +// popover.parsed_content.text, +// format!("A tooltip for {struct_label}"), +// "Rendered markdown element should remove backticks from text" +// ); +// }); +// } +// } diff --git a/crates/editor2/src/inlay_hint_cache.rs b/crates/editor2/src/inlay_hint_cache.rs new file mode 100644 index 0000000000..addd3bf3ac --- /dev/null +++ b/crates/editor2/src/inlay_hint_cache.rs @@ -0,0 +1,3355 @@ +use std::{ + cmp, + ops::{ControlFlow, Range}, + sync::Arc, + time::Duration, +}; + +use crate::{ + display_map::Inlay, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, MultiBufferSnapshot, +}; +use anyhow::Context; +use clock::Global; +use futures::future; +use gpui::{Model, ModelContext, Task, ViewContext}; +use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot}; +use parking_lot::RwLock; +use project::{InlayHint, ResolveState}; + +use collections::{hash_map, HashMap, HashSet}; +use language::language_settings::InlayHintSettings; +use smol::lock::Semaphore; +use sum_tree::Bias; +use text::{ToOffset, ToPoint}; +use util::post_inc; + +pub struct InlayHintCache { + hints: HashMap>>, + allowed_hint_kinds: HashSet>, + version: usize, + pub(super) enabled: bool, + update_tasks: HashMap, + lsp_request_limiter: Arc, +} + +#[derive(Debug)] +struct TasksForRanges { + tasks: Vec>, + sorted_ranges: Vec>, +} + +#[derive(Debug)] +pub struct CachedExcerptHints { + version: usize, + buffer_version: Global, + buffer_id: u64, + ordered_hints: Vec, + hints_by_id: HashMap, +} + +#[derive(Debug, Clone, Copy)] +pub enum InvalidationStrategy { + RefreshRequested, + BufferEdited, + None, +} + +#[derive(Debug, Default)] +pub struct InlaySplice { + pub to_remove: Vec, + pub to_insert: Vec, +} + +#[derive(Debug)] +struct ExcerptHintsUpdate { + excerpt_id: ExcerptId, + remove_from_visible: Vec, + remove_from_cache: HashSet, + add_to_cache: Vec, +} + +#[derive(Debug, Clone, Copy)] +struct ExcerptQuery { + buffer_id: u64, + excerpt_id: ExcerptId, + cache_version: usize, + invalidate: InvalidationStrategy, + reason: &'static str, +} + +impl InvalidationStrategy { + fn should_invalidate(&self) -> bool { + matches!( + self, + InvalidationStrategy::RefreshRequested | InvalidationStrategy::BufferEdited + ) + } +} + +impl TasksForRanges { + fn new(query_ranges: QueryRanges, task: Task<()>) -> Self { + let mut sorted_ranges = Vec::new(); + sorted_ranges.extend(query_ranges.before_visible); + sorted_ranges.extend(query_ranges.visible); + sorted_ranges.extend(query_ranges.after_visible); + Self { + tasks: vec![task], + sorted_ranges, + } + } + + fn update_cached_tasks( + &mut self, + buffer_snapshot: &BufferSnapshot, + query_ranges: QueryRanges, + invalidate: InvalidationStrategy, + spawn_task: impl FnOnce(QueryRanges) -> Task<()>, + ) { + let query_ranges = if invalidate.should_invalidate() { + self.tasks.clear(); + self.sorted_ranges.clear(); + query_ranges + } else { + let mut non_cached_query_ranges = query_ranges; + non_cached_query_ranges.before_visible = non_cached_query_ranges + .before_visible + .into_iter() + .flat_map(|query_range| { + self.remove_cached_ranges_from_query(buffer_snapshot, query_range) + }) + .collect(); + non_cached_query_ranges.visible = non_cached_query_ranges + .visible + .into_iter() + .flat_map(|query_range| { + self.remove_cached_ranges_from_query(buffer_snapshot, query_range) + }) + .collect(); + non_cached_query_ranges.after_visible = non_cached_query_ranges + .after_visible + .into_iter() + .flat_map(|query_range| { + self.remove_cached_ranges_from_query(buffer_snapshot, query_range) + }) + .collect(); + non_cached_query_ranges + }; + + if !query_ranges.is_empty() { + self.tasks.push(spawn_task(query_ranges)); + } + } + + fn remove_cached_ranges_from_query( + &mut self, + buffer_snapshot: &BufferSnapshot, + query_range: Range, + ) -> Vec> { + let mut ranges_to_query = Vec::new(); + let mut latest_cached_range = None::<&mut Range>; + for cached_range in self + .sorted_ranges + .iter_mut() + .skip_while(|cached_range| { + cached_range + .end + .cmp(&query_range.start, buffer_snapshot) + .is_lt() + }) + .take_while(|cached_range| { + cached_range + .start + .cmp(&query_range.end, buffer_snapshot) + .is_le() + }) + { + match latest_cached_range { + Some(latest_cached_range) => { + if latest_cached_range.end.offset.saturating_add(1) < cached_range.start.offset + { + ranges_to_query.push(latest_cached_range.end..cached_range.start); + cached_range.start = latest_cached_range.end; + } + } + None => { + if query_range + .start + .cmp(&cached_range.start, buffer_snapshot) + .is_lt() + { + ranges_to_query.push(query_range.start..cached_range.start); + cached_range.start = query_range.start; + } + } + } + latest_cached_range = Some(cached_range); + } + + match latest_cached_range { + Some(latest_cached_range) => { + if latest_cached_range.end.offset.saturating_add(1) < query_range.end.offset { + ranges_to_query.push(latest_cached_range.end..query_range.end); + latest_cached_range.end = query_range.end; + } + } + None => { + ranges_to_query.push(query_range.clone()); + self.sorted_ranges.push(query_range); + self.sorted_ranges + .sort_by(|range_a, range_b| range_a.start.cmp(&range_b.start, buffer_snapshot)); + } + } + + ranges_to_query + } + + fn invalidate_range(&mut self, buffer: &BufferSnapshot, range: &Range) { + self.sorted_ranges = self + .sorted_ranges + .drain(..) + .filter_map(|mut cached_range| { + if cached_range.start.cmp(&range.end, buffer).is_gt() + || cached_range.end.cmp(&range.start, buffer).is_lt() + { + Some(vec![cached_range]) + } else if cached_range.start.cmp(&range.start, buffer).is_ge() + && cached_range.end.cmp(&range.end, buffer).is_le() + { + None + } else if range.start.cmp(&cached_range.start, buffer).is_ge() + && range.end.cmp(&cached_range.end, buffer).is_le() + { + Some(vec![ + cached_range.start..range.start, + range.end..cached_range.end, + ]) + } else if cached_range.start.cmp(&range.start, buffer).is_ge() { + cached_range.start = range.end; + Some(vec![cached_range]) + } else { + cached_range.end = range.start; + Some(vec![cached_range]) + } + }) + .flatten() + .collect(); + } +} + +impl InlayHintCache { + pub fn new(inlay_hint_settings: InlayHintSettings) -> Self { + Self { + allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(), + enabled: inlay_hint_settings.enabled, + hints: HashMap::default(), + update_tasks: HashMap::default(), + version: 0, + lsp_request_limiter: Arc::new(Semaphore::new(MAX_CONCURRENT_LSP_REQUESTS)), + } + } + + pub fn update_settings( + &mut self, + multi_buffer: &Model, + new_hint_settings: InlayHintSettings, + visible_hints: Vec, + cx: &mut ViewContext, + ) -> ControlFlow> { + let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds(); + match (self.enabled, new_hint_settings.enabled) { + (false, false) => { + self.allowed_hint_kinds = new_allowed_hint_kinds; + ControlFlow::Break(None) + } + (true, true) => { + if new_allowed_hint_kinds == self.allowed_hint_kinds { + ControlFlow::Break(None) + } else { + let new_splice = self.new_allowed_hint_kinds_splice( + multi_buffer, + &visible_hints, + &new_allowed_hint_kinds, + cx, + ); + if new_splice.is_some() { + self.version += 1; + self.allowed_hint_kinds = new_allowed_hint_kinds; + } + ControlFlow::Break(new_splice) + } + } + (true, false) => { + self.enabled = new_hint_settings.enabled; + self.allowed_hint_kinds = new_allowed_hint_kinds; + if self.hints.is_empty() { + ControlFlow::Break(None) + } else { + self.clear(); + ControlFlow::Break(Some(InlaySplice { + to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(), + to_insert: Vec::new(), + })) + } + } + (false, true) => { + self.enabled = new_hint_settings.enabled; + self.allowed_hint_kinds = new_allowed_hint_kinds; + ControlFlow::Continue(()) + } + } + } + + pub fn spawn_hint_refresh( + &mut self, + reason: &'static str, + excerpts_to_query: HashMap, Global, Range)>, + invalidate: InvalidationStrategy, + cx: &mut ViewContext, + ) -> Option { + if !self.enabled { + return None; + } + + let mut invalidated_hints = Vec::new(); + if invalidate.should_invalidate() { + self.update_tasks + .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id)); + self.hints.retain(|cached_excerpt, cached_hints| { + let retain = excerpts_to_query.contains_key(cached_excerpt); + if !retain { + invalidated_hints.extend(cached_hints.read().ordered_hints.iter().copied()); + } + retain + }); + } + if excerpts_to_query.is_empty() && invalidated_hints.is_empty() { + return None; + } + + let cache_version = self.version + 1; + cx.spawn(|editor, mut cx| async move { + editor + .update(&mut cx, |editor, cx| { + spawn_new_update_tasks( + editor, + reason, + excerpts_to_query, + invalidate, + cache_version, + cx, + ) + }) + .ok(); + }) + .detach(); + + if invalidated_hints.is_empty() { + None + } else { + Some(InlaySplice { + to_remove: invalidated_hints, + to_insert: Vec::new(), + }) + } + } + + fn new_allowed_hint_kinds_splice( + &self, + multi_buffer: &Model, + visible_hints: &[Inlay], + new_kinds: &HashSet>, + cx: &mut ViewContext, + ) -> Option { + let old_kinds = &self.allowed_hint_kinds; + if new_kinds == old_kinds { + return None; + } + + let mut to_remove = Vec::new(); + let mut to_insert = Vec::new(); + let mut shown_hints_to_remove = visible_hints.iter().fold( + HashMap::>::default(), + |mut current_hints, inlay| { + current_hints + .entry(inlay.position.excerpt_id) + .or_default() + .push((inlay.position, inlay.id)); + current_hints + }, + ); + + let multi_buffer = multi_buffer.read(cx); + let multi_buffer_snapshot = multi_buffer.snapshot(cx); + + for (excerpt_id, excerpt_cached_hints) in &self.hints { + let shown_excerpt_hints_to_remove = + shown_hints_to_remove.entry(*excerpt_id).or_default(); + let excerpt_cached_hints = excerpt_cached_hints.read(); + let mut excerpt_cache = excerpt_cached_hints.ordered_hints.iter().fuse().peekable(); + shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| { + let Some(buffer) = shown_anchor + .buffer_id + .and_then(|buffer_id| multi_buffer.buffer(buffer_id)) + else { + return false; + }; + let buffer_snapshot = buffer.read(cx).snapshot(); + loop { + match excerpt_cache.peek() { + Some(&cached_hint_id) => { + let cached_hint = &excerpt_cached_hints.hints_by_id[cached_hint_id]; + if cached_hint_id == shown_hint_id { + excerpt_cache.next(); + return !new_kinds.contains(&cached_hint.kind); + } + + match cached_hint + .position + .cmp(&shown_anchor.text_anchor, &buffer_snapshot) + { + cmp::Ordering::Less | cmp::Ordering::Equal => { + if !old_kinds.contains(&cached_hint.kind) + && new_kinds.contains(&cached_hint.kind) + { + to_insert.push(Inlay::hint( + cached_hint_id.id(), + multi_buffer_snapshot.anchor_in_excerpt( + *excerpt_id, + cached_hint.position, + ), + &cached_hint, + )); + } + excerpt_cache.next(); + } + cmp::Ordering::Greater => return true, + } + } + None => return true, + } + } + }); + + for cached_hint_id in excerpt_cache { + let maybe_missed_cached_hint = &excerpt_cached_hints.hints_by_id[cached_hint_id]; + let cached_hint_kind = maybe_missed_cached_hint.kind; + if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) { + to_insert.push(Inlay::hint( + cached_hint_id.id(), + multi_buffer_snapshot + .anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position), + &maybe_missed_cached_hint, + )); + } + } + } + + to_remove.extend( + shown_hints_to_remove + .into_values() + .flatten() + .map(|(_, hint_id)| hint_id), + ); + if to_remove.is_empty() && to_insert.is_empty() { + None + } else { + Some(InlaySplice { + to_remove, + to_insert, + }) + } + } + + pub fn remove_excerpts(&mut self, excerpts_removed: Vec) -> Option { + let mut to_remove = Vec::new(); + for excerpt_to_remove in excerpts_removed { + self.update_tasks.remove(&excerpt_to_remove); + if let Some(cached_hints) = self.hints.remove(&excerpt_to_remove) { + let cached_hints = cached_hints.read(); + to_remove.extend(cached_hints.ordered_hints.iter().copied()); + } + } + if to_remove.is_empty() { + None + } else { + self.version += 1; + Some(InlaySplice { + to_remove, + to_insert: Vec::new(), + }) + } + } + + pub fn clear(&mut self) { + if !self.update_tasks.is_empty() || !self.hints.is_empty() { + self.version += 1; + } + self.update_tasks.clear(); + self.hints.clear(); + } + + pub fn hint_by_id(&self, excerpt_id: ExcerptId, hint_id: InlayId) -> Option { + self.hints + .get(&excerpt_id)? + .read() + .hints_by_id + .get(&hint_id) + .cloned() + } + + pub fn hints(&self) -> Vec { + let mut hints = Vec::new(); + for excerpt_hints in self.hints.values() { + let excerpt_hints = excerpt_hints.read(); + hints.extend( + excerpt_hints + .ordered_hints + .iter() + .map(|id| &excerpt_hints.hints_by_id[id]) + .cloned(), + ); + } + hints + } + + pub fn version(&self) -> usize { + self.version + } + + pub fn spawn_hint_resolve( + &self, + buffer_id: u64, + excerpt_id: ExcerptId, + id: InlayId, + cx: &mut ViewContext<'_, Editor>, + ) { + if let Some(excerpt_hints) = self.hints.get(&excerpt_id) { + let mut guard = excerpt_hints.write(); + if let Some(cached_hint) = guard.hints_by_id.get_mut(&id) { + if let ResolveState::CanResolve(server_id, _) = &cached_hint.resolve_state { + let hint_to_resolve = cached_hint.clone(); + let server_id = *server_id; + cached_hint.resolve_state = ResolveState::Resolving; + drop(guard); + cx.spawn(|editor, mut cx| async move { + let resolved_hint_task = editor.update(&mut cx, |editor, cx| { + editor + .buffer() + .read(cx) + .buffer(buffer_id) + .and_then(|buffer| { + let project = editor.project.as_ref()?; + Some(project.update(cx, |project, cx| { + project.resolve_inlay_hint( + hint_to_resolve, + buffer, + server_id, + cx, + ) + })) + }) + })?; + if let Some(resolved_hint_task) = resolved_hint_task { + let mut resolved_hint = + resolved_hint_task.await.context("hint resolve task")?; + editor.update(&mut cx, |editor, _| { + todo!() + // if let Some(excerpt_hints) = + // editor.inlay_hint_cache.hints.get(&excerpt_id) + // { + // let mut guard = excerpt_hints.write(); + // if let Some(cached_hint) = guard.hints_by_id.get_mut(&id) { + // if cached_hint.resolve_state == ResolveState::Resolving { + // resolved_hint.resolve_state = ResolveState::Resolved; + // *cached_hint = resolved_hint; + // } + // } + // } + })?; + } + + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + } + } + } + } +} + +fn spawn_new_update_tasks( + editor: &mut Editor, + reason: &'static str, + excerpts_to_query: HashMap, Global, Range)>, + invalidate: InvalidationStrategy, + update_cache_version: usize, + cx: &mut ViewContext<'_, Editor>, +) { + todo!("old version below"); +} +// let visible_hints = Arc::new(editor.visible_inlay_hints(cx)); +// for (excerpt_id, (excerpt_buffer, new_task_buffer_version, excerpt_visible_range)) in +// excerpts_to_query +// { +// if excerpt_visible_range.is_empty() { +// continue; +// } +// let buffer = excerpt_buffer.read(cx); +// let buffer_id = buffer.remote_id(); +// let buffer_snapshot = buffer.snapshot(); +// if buffer_snapshot +// .version() +// .changed_since(&new_task_buffer_version) +// { +// continue; +// } + +// let cached_excerpt_hints = editor.inlay_hint_cache.hints.get(&excerpt_id).cloned(); +// if let Some(cached_excerpt_hints) = &cached_excerpt_hints { +// let cached_excerpt_hints = cached_excerpt_hints.read(); +// let cached_buffer_version = &cached_excerpt_hints.buffer_version; +// if cached_excerpt_hints.version > update_cache_version +// || cached_buffer_version.changed_since(&new_task_buffer_version) +// { +// continue; +// } +// }; + +// let (multi_buffer_snapshot, Some(query_ranges)) = +// editor.buffer.update(cx, |multi_buffer, cx| { +// ( +// multi_buffer.snapshot(cx), +// determine_query_ranges( +// multi_buffer, +// excerpt_id, +// &excerpt_buffer, +// excerpt_visible_range, +// cx, +// ), +// ) +// }) +// else { +// return; +// }; +// let query = ExcerptQuery { +// buffer_id, +// excerpt_id, +// cache_version: update_cache_version, +// invalidate, +// reason, +// }; + +// let new_update_task = |query_ranges| { +// new_update_task( +// query, +// query_ranges, +// multi_buffer_snapshot, +// buffer_snapshot.clone(), +// Arc::clone(&visible_hints), +// cached_excerpt_hints, +// Arc::clone(&editor.inlay_hint_cache.lsp_request_limiter), +// cx, +// ) +// }; + +// match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) { +// hash_map::Entry::Occupied(mut o) => { +// o.get_mut().update_cached_tasks( +// &buffer_snapshot, +// query_ranges, +// invalidate, +// new_update_task, +// ); +// } +// hash_map::Entry::Vacant(v) => { +// v.insert(TasksForRanges::new( +// query_ranges.clone(), +// new_update_task(query_ranges), +// )); +// } +// } +// } +// } + +#[derive(Debug, Clone)] +struct QueryRanges { + before_visible: Vec>, + visible: Vec>, + after_visible: Vec>, +} + +impl QueryRanges { + fn is_empty(&self) -> bool { + self.before_visible.is_empty() && self.visible.is_empty() && self.after_visible.is_empty() + } +} + +fn determine_query_ranges( + multi_buffer: &mut MultiBuffer, + excerpt_id: ExcerptId, + excerpt_buffer: &Model, + excerpt_visible_range: Range, + cx: &mut ModelContext<'_, MultiBuffer>, +) -> Option { + let full_excerpt_range = multi_buffer + .excerpts_for_buffer(excerpt_buffer, cx) + .into_iter() + .find(|(id, _)| id == &excerpt_id) + .map(|(_, range)| range.context)?; + let buffer = excerpt_buffer.read(cx); + let snapshot = buffer.snapshot(); + let excerpt_visible_len = excerpt_visible_range.end - excerpt_visible_range.start; + + let visible_range = if excerpt_visible_range.start == excerpt_visible_range.end { + return None; + } else { + vec![ + buffer.anchor_before(snapshot.clip_offset(excerpt_visible_range.start, Bias::Left)) + ..buffer.anchor_after(snapshot.clip_offset(excerpt_visible_range.end, Bias::Right)), + ] + }; + + let full_excerpt_range_end_offset = full_excerpt_range.end.to_offset(&snapshot); + let after_visible_range_start = excerpt_visible_range + .end + .saturating_add(1) + .min(full_excerpt_range_end_offset) + .min(buffer.len()); + let after_visible_range = if after_visible_range_start == full_excerpt_range_end_offset { + Vec::new() + } else { + let after_range_end_offset = after_visible_range_start + .saturating_add(excerpt_visible_len) + .min(full_excerpt_range_end_offset) + .min(buffer.len()); + vec![ + buffer.anchor_before(snapshot.clip_offset(after_visible_range_start, Bias::Left)) + ..buffer.anchor_after(snapshot.clip_offset(after_range_end_offset, Bias::Right)), + ] + }; + + let full_excerpt_range_start_offset = full_excerpt_range.start.to_offset(&snapshot); + let before_visible_range_end = excerpt_visible_range + .start + .saturating_sub(1) + .max(full_excerpt_range_start_offset); + let before_visible_range = if before_visible_range_end == full_excerpt_range_start_offset { + Vec::new() + } else { + let before_range_start_offset = before_visible_range_end + .saturating_sub(excerpt_visible_len) + .max(full_excerpt_range_start_offset); + vec![ + buffer.anchor_before(snapshot.clip_offset(before_range_start_offset, Bias::Left)) + ..buffer.anchor_after(snapshot.clip_offset(before_visible_range_end, Bias::Right)), + ] + }; + + Some(QueryRanges { + before_visible: before_visible_range, + visible: visible_range, + after_visible: after_visible_range, + }) +} + +const MAX_CONCURRENT_LSP_REQUESTS: usize = 5; +const INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS: u64 = 400; + +fn new_update_task( + query: ExcerptQuery, + query_ranges: QueryRanges, + multi_buffer_snapshot: MultiBufferSnapshot, + buffer_snapshot: BufferSnapshot, + visible_hints: Arc>, + cached_excerpt_hints: Option>>, + lsp_request_limiter: Arc, + cx: &mut ViewContext<'_, Editor>, +) -> Task<()> { + todo!() + // cx.spawn(|editor, mut cx| async move { + // let closure_cx = cx.clone(); + // let fetch_and_update_hints = |invalidate, range| { + // fetch_and_update_hints( + // editor.clone(), + // multi_buffer_snapshot.clone(), + // buffer_snapshot.clone(), + // Arc::clone(&visible_hints), + // cached_excerpt_hints.as_ref().map(Arc::clone), + // query, + // invalidate, + // range, + // Arc::clone(&lsp_request_limiter), + // closure_cx.clone(), + // ) + // }; + // let visible_range_update_results = future::join_all(query_ranges.visible.into_iter().map( + // |visible_range| async move { + // ( + // visible_range.clone(), + // fetch_and_update_hints(query.invalidate.should_invalidate(), visible_range) + // .await, + // ) + // }, + // )) + // .await; + + // let hint_delay = cx.background().timer(Duration::from_millis( + // INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS, + // )); + + // let mut query_range_failed = |range: &Range, e: anyhow::Error| { + // log::error!("inlay hint update task for range {range:?} failed: {e:#}"); + // editor + // .update(&mut cx, |editor, _| { + // if let Some(task_ranges) = editor + // .inlay_hint_cache + // .update_tasks + // .get_mut(&query.excerpt_id) + // { + // task_ranges.invalidate_range(&buffer_snapshot, &range); + // } + // }) + // .ok() + // }; + + // for (range, result) in visible_range_update_results { + // if let Err(e) = result { + // query_range_failed(&range, e); + // } + // } + + // hint_delay.await; + // let invisible_range_update_results = future::join_all( + // query_ranges + // .before_visible + // .into_iter() + // .chain(query_ranges.after_visible.into_iter()) + // .map(|invisible_range| async move { + // ( + // invisible_range.clone(), + // fetch_and_update_hints(false, invisible_range).await, + // ) + // }), + // ) + // .await; + // for (range, result) in invisible_range_update_results { + // if let Err(e) = result { + // query_range_failed(&range, e); + // } + // } + // }) +} + +// async fn fetch_and_update_hints( +// editor: gpui::WeakView, +// multi_buffer_snapshot: MultiBufferSnapshot, +// buffer_snapshot: BufferSnapshot, +// visible_hints: Arc>, +// cached_excerpt_hints: Option>>, +// query: ExcerptQuery, +// invalidate: bool, +// fetch_range: Range, +// lsp_request_limiter: Arc, +// mut cx: gpui::AsyncAppContext, +// ) -> anyhow::Result<()> { +// let (lsp_request_guard, got_throttled) = if query.invalidate.should_invalidate() { +// (None, false) +// } else { +// match lsp_request_limiter.try_acquire() { +// Some(guard) => (Some(guard), false), +// None => (Some(lsp_request_limiter.acquire().await), true), +// } +// }; +// let fetch_range_to_log = +// fetch_range.start.to_point(&buffer_snapshot)..fetch_range.end.to_point(&buffer_snapshot); +// let inlay_hints_fetch_task = editor +// .update(&mut cx, |editor, cx| { +// if got_throttled { +// let query_not_around_visible_range = match editor.excerpt_visible_offsets(None, cx).remove(&query.excerpt_id) { +// Some((_, _, current_visible_range)) => { +// let visible_offset_length = current_visible_range.len(); +// let double_visible_range = current_visible_range +// .start +// .saturating_sub(visible_offset_length) +// ..current_visible_range +// .end +// .saturating_add(visible_offset_length) +// .min(buffer_snapshot.len()); +// !double_visible_range +// .contains(&fetch_range.start.to_offset(&buffer_snapshot)) +// && !double_visible_range +// .contains(&fetch_range.end.to_offset(&buffer_snapshot)) +// }, +// None => true, +// }; +// if query_not_around_visible_range { +// log::trace!("Fetching inlay hints for range {fetch_range_to_log:?} got throttled and fell off the current visible range, skipping."); +// if let Some(task_ranges) = editor +// .inlay_hint_cache +// .update_tasks +// .get_mut(&query.excerpt_id) +// { +// task_ranges.invalidate_range(&buffer_snapshot, &fetch_range); +// } +// return None; +// } +// } +// editor +// .buffer() +// .read(cx) +// .buffer(query.buffer_id) +// .and_then(|buffer| { +// let project = editor.project.as_ref()?; +// Some(project.update(cx, |project, cx| { +// project.inlay_hints(buffer, fetch_range.clone(), cx) +// })) +// }) +// }) +// .ok() +// .flatten(); +// let new_hints = match inlay_hints_fetch_task { +// Some(fetch_task) => { +// log::debug!( +// "Fetching inlay hints for range {fetch_range_to_log:?}, reason: {query_reason}, invalidate: {invalidate}", +// query_reason = query.reason, +// ); +// log::trace!( +// "Currently visible hints: {visible_hints:?}, cached hints present: {}", +// cached_excerpt_hints.is_some(), +// ); +// fetch_task.await.context("inlay hint fetch task")? +// } +// None => return Ok(()), +// }; +// drop(lsp_request_guard); +// log::debug!( +// "Fetched {} hints for range {fetch_range_to_log:?}", +// new_hints.len() +// ); +// log::trace!("Fetched hints: {new_hints:?}"); + +// let background_task_buffer_snapshot = buffer_snapshot.clone(); +// let backround_fetch_range = fetch_range.clone(); +// let new_update = cx +// .background() +// .spawn(async move { +// calculate_hint_updates( +// query.excerpt_id, +// invalidate, +// backround_fetch_range, +// new_hints, +// &background_task_buffer_snapshot, +// cached_excerpt_hints, +// &visible_hints, +// ) +// }) +// .await; +// if let Some(new_update) = new_update { +// log::debug!( +// "Applying update for range {fetch_range_to_log:?}: remove from editor: {}, remove from cache: {}, add to cache: {}", +// new_update.remove_from_visible.len(), +// new_update.remove_from_cache.len(), +// new_update.add_to_cache.len() +// ); +// log::trace!("New update: {new_update:?}"); +// editor +// .update(&mut cx, |editor, cx| { +// apply_hint_update( +// editor, +// new_update, +// query, +// invalidate, +// buffer_snapshot, +// multi_buffer_snapshot, +// cx, +// ); +// }) +// .ok(); +// } +// Ok(()) +// } + +fn calculate_hint_updates( + excerpt_id: ExcerptId, + invalidate: bool, + fetch_range: Range, + new_excerpt_hints: Vec, + buffer_snapshot: &BufferSnapshot, + cached_excerpt_hints: Option>>, + visible_hints: &[Inlay], +) -> Option { + let mut add_to_cache = Vec::::new(); + let mut excerpt_hints_to_persist = HashMap::default(); + for new_hint in new_excerpt_hints { + if !contains_position(&fetch_range, new_hint.position, buffer_snapshot) { + continue; + } + let missing_from_cache = match &cached_excerpt_hints { + Some(cached_excerpt_hints) => { + let cached_excerpt_hints = cached_excerpt_hints.read(); + match cached_excerpt_hints + .ordered_hints + .binary_search_by(|probe| { + cached_excerpt_hints.hints_by_id[probe] + .position + .cmp(&new_hint.position, buffer_snapshot) + }) { + Ok(ix) => { + let mut missing_from_cache = true; + for id in &cached_excerpt_hints.ordered_hints[ix..] { + let cached_hint = &cached_excerpt_hints.hints_by_id[id]; + if new_hint + .position + .cmp(&cached_hint.position, buffer_snapshot) + .is_gt() + { + break; + } + if cached_hint == &new_hint { + excerpt_hints_to_persist.insert(*id, cached_hint.kind); + missing_from_cache = false; + } + } + missing_from_cache + } + Err(_) => true, + } + } + None => true, + }; + if missing_from_cache { + add_to_cache.push(new_hint); + } + } + + let mut remove_from_visible = Vec::new(); + let mut remove_from_cache = HashSet::default(); + if invalidate { + remove_from_visible.extend( + visible_hints + .iter() + .filter(|hint| hint.position.excerpt_id == excerpt_id) + .map(|inlay_hint| inlay_hint.id) + .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)), + ); + + if let Some(cached_excerpt_hints) = &cached_excerpt_hints { + let cached_excerpt_hints = cached_excerpt_hints.read(); + remove_from_cache.extend( + cached_excerpt_hints + .ordered_hints + .iter() + .filter(|cached_inlay_id| { + !excerpt_hints_to_persist.contains_key(cached_inlay_id) + }) + .copied(), + ); + } + } + + if remove_from_visible.is_empty() && remove_from_cache.is_empty() && add_to_cache.is_empty() { + None + } else { + Some(ExcerptHintsUpdate { + excerpt_id, + remove_from_visible, + remove_from_cache, + add_to_cache, + }) + } +} + +fn contains_position( + range: &Range, + position: language::Anchor, + buffer_snapshot: &BufferSnapshot, +) -> bool { + range.start.cmp(&position, buffer_snapshot).is_le() + && range.end.cmp(&position, buffer_snapshot).is_ge() +} + +fn apply_hint_update( + editor: &mut Editor, + new_update: ExcerptHintsUpdate, + query: ExcerptQuery, + invalidate: bool, + buffer_snapshot: BufferSnapshot, + multi_buffer_snapshot: MultiBufferSnapshot, + cx: &mut ViewContext<'_, Editor>, +) { + todo!("old implementation commented below") +} +// let cached_excerpt_hints = editor +// .inlay_hint_cache +// .hints +// .entry(new_update.excerpt_id) +// .or_insert_with(|| { +// Arc::new(RwLock::new(CachedExcerptHints { +// version: query.cache_version, +// buffer_version: buffer_snapshot.version().clone(), +// buffer_id: query.buffer_id, +// ordered_hints: Vec::new(), +// hints_by_id: HashMap::default(), +// })) +// }); +// let mut cached_excerpt_hints = cached_excerpt_hints.write(); +// match query.cache_version.cmp(&cached_excerpt_hints.version) { +// cmp::Ordering::Less => return, +// cmp::Ordering::Greater | cmp::Ordering::Equal => { +// cached_excerpt_hints.version = query.cache_version; +// } +// } + +// let mut cached_inlays_changed = !new_update.remove_from_cache.is_empty(); +// cached_excerpt_hints +// .ordered_hints +// .retain(|hint_id| !new_update.remove_from_cache.contains(hint_id)); +// cached_excerpt_hints +// .hints_by_id +// .retain(|hint_id, _| !new_update.remove_from_cache.contains(hint_id)); +// let mut splice = InlaySplice { +// to_remove: new_update.remove_from_visible, +// to_insert: Vec::new(), +// }; +// for new_hint in new_update.add_to_cache { +// let insert_position = match cached_excerpt_hints +// .ordered_hints +// .binary_search_by(|probe| { +// cached_excerpt_hints.hints_by_id[probe] +// .position +// .cmp(&new_hint.position, &buffer_snapshot) +// }) { +// Ok(i) => { +// let mut insert_position = Some(i); +// for id in &cached_excerpt_hints.ordered_hints[i..] { +// let cached_hint = &cached_excerpt_hints.hints_by_id[id]; +// if new_hint +// .position +// .cmp(&cached_hint.position, &buffer_snapshot) +// .is_gt() +// { +// break; +// } +// if cached_hint.text() == new_hint.text() { +// insert_position = None; +// break; +// } +// } +// insert_position +// } +// Err(i) => Some(i), +// }; + +// if let Some(insert_position) = insert_position { +// let new_inlay_id = post_inc(&mut editor.next_inlay_id); +// if editor +// .inlay_hint_cache +// .allowed_hint_kinds +// .contains(&new_hint.kind) +// { +// let new_hint_position = +// multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position); +// splice +// .to_insert +// .push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint)); +// } +// let new_id = InlayId::Hint(new_inlay_id); +// cached_excerpt_hints.hints_by_id.insert(new_id, new_hint); +// cached_excerpt_hints +// .ordered_hints +// .insert(insert_position, new_id); +// cached_inlays_changed = true; +// } +// } +// cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone(); +// drop(cached_excerpt_hints); + +// if invalidate { +// let mut outdated_excerpt_caches = HashSet::default(); +// for (excerpt_id, excerpt_hints) in &editor.inlay_hint_cache().hints { +// let excerpt_hints = excerpt_hints.read(); +// if excerpt_hints.buffer_id == query.buffer_id +// && excerpt_id != &query.excerpt_id +// && buffer_snapshot +// .version() +// .changed_since(&excerpt_hints.buffer_version) +// { +// outdated_excerpt_caches.insert(*excerpt_id); +// splice +// .to_remove +// .extend(excerpt_hints.ordered_hints.iter().copied()); +// } +// } +// cached_inlays_changed |= !outdated_excerpt_caches.is_empty(); +// editor +// .inlay_hint_cache +// .hints +// .retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id)); +// } + +// let InlaySplice { +// to_remove, +// to_insert, +// } = splice; +// let displayed_inlays_changed = !to_remove.is_empty() || !to_insert.is_empty(); +// if cached_inlays_changed || displayed_inlays_changed { +// editor.inlay_hint_cache.version += 1; +// } +// if displayed_inlays_changed { +// editor.splice_inlay_hints(to_remove, to_insert, cx) +// } +// } + +// #[cfg(test)] +// pub mod tests { +// use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; + +// use crate::{ +// scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount}, +// serde_json::json, +// ExcerptRange, +// }; +// use futures::StreamExt; +// use gpui::{executor::Deterministic, TestAppContext, View}; +// use itertools::Itertools; +// use language::{ +// language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig, +// }; +// use lsp::FakeLanguageServer; +// use parking_lot::Mutex; +// use project::{FakeFs, Project}; +// use settings::SettingsStore; +// use text::{Point, ToPoint}; +// use workspace::Workspace; + +// use crate::editor_tests::update_test_language_settings; + +// use super::*; + +// #[gpui::test] +// async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) { +// let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); +// init_test(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: true, +// show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), +// show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), +// show_other_hints: allowed_hint_kinds.contains(&None), +// }) +// }); + +// let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; +// let lsp_request_count = Arc::new(AtomicU32::new(0)); +// fake_server +// .handle_request::(move |params, _| { +// let task_lsp_request_count = Arc::clone(&lsp_request_count); +// async move { +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path(file_with_hints).unwrap(), +// ); +// let current_call_id = +// Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst); +// let mut new_hints = Vec::with_capacity(2 * current_call_id as usize); +// for _ in 0..2 { +// let mut i = current_call_id; +// loop { +// new_hints.push(lsp::InlayHint { +// position: lsp::Position::new(0, i), +// label: lsp::InlayHintLabel::String(i.to_string()), +// kind: None, +// text_edits: None, +// tooltip: None, +// padding_left: None, +// padding_right: None, +// data: None, +// }); +// if i == 0 { +// break; +// } +// i -= 1; +// } +// } + +// Ok(Some(new_hints)) +// } +// }) +// .next() +// .await; +// cx.foreground().run_until_parked(); + +// let mut edits_made = 1; +// editor.update(cx, |editor, cx| { +// let expected_hints = vec!["0".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Should get its first hints when opening the editor" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// let inlay_cache = editor.inlay_hint_cache(); +// assert_eq!( +// inlay_cache.allowed_hint_kinds, allowed_hint_kinds, +// "Cache should use editor settings to get the allowed hint kinds" +// ); +// assert_eq!( +// inlay_cache.version, edits_made, +// "The editor update the cache version after every cache/view change" +// ); +// }); + +// editor.update(cx, |editor, cx| { +// editor.change_selections(None, cx, |s| s.select_ranges([13..13])); +// editor.handle_input("some change", cx); +// edits_made += 1; +// }); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// let expected_hints = vec!["0".to_string(), "1".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Should get new hints after an edit" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// let inlay_cache = editor.inlay_hint_cache(); +// assert_eq!( +// inlay_cache.allowed_hint_kinds, allowed_hint_kinds, +// "Cache should use editor settings to get the allowed hint kinds" +// ); +// assert_eq!( +// inlay_cache.version, edits_made, +// "The editor update the cache version after every cache/view change" +// ); +// }); + +// fake_server +// .request::(()) +// .await +// .expect("inlay refresh request failed"); +// edits_made += 1; +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// let expected_hints = vec!["0".to_string(), "1".to_string(), "2".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Should get new hints after hint refresh/ request" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// let inlay_cache = editor.inlay_hint_cache(); +// assert_eq!( +// inlay_cache.allowed_hint_kinds, allowed_hint_kinds, +// "Cache should use editor settings to get the allowed hint kinds" +// ); +// assert_eq!( +// inlay_cache.version, edits_made, +// "The editor update the cache version after every cache/view change" +// ); +// }); +// } + +// #[gpui::test] +// async fn test_cache_update_on_lsp_completion_tasks(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: true, +// show_type_hints: true, +// show_parameter_hints: true, +// show_other_hints: true, +// }) +// }); + +// let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; +// let lsp_request_count = Arc::new(AtomicU32::new(0)); +// fake_server +// .handle_request::(move |params, _| { +// let task_lsp_request_count = Arc::clone(&lsp_request_count); +// async move { +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path(file_with_hints).unwrap(), +// ); +// let current_call_id = +// Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst); +// Ok(Some(vec![lsp::InlayHint { +// position: lsp::Position::new(0, current_call_id), +// label: lsp::InlayHintLabel::String(current_call_id.to_string()), +// kind: None, +// text_edits: None, +// tooltip: None, +// padding_left: None, +// padding_right: None, +// data: None, +// }])) +// } +// }) +// .next() +// .await; +// cx.foreground().run_until_parked(); + +// let mut edits_made = 1; +// editor.update(cx, |editor, cx| { +// let expected_hints = vec!["0".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Should get its first hints when opening the editor" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!( +// editor.inlay_hint_cache().version, +// edits_made, +// "The editor update the cache version after every cache/view change" +// ); +// }); + +// let progress_token = "test_progress_token"; +// fake_server +// .request::(lsp::WorkDoneProgressCreateParams { +// token: lsp::ProgressToken::String(progress_token.to_string()), +// }) +// .await +// .expect("work done progress create request failed"); +// cx.foreground().run_until_parked(); +// fake_server.notify::(lsp::ProgressParams { +// token: lsp::ProgressToken::String(progress_token.to_string()), +// value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin( +// lsp::WorkDoneProgressBegin::default(), +// )), +// }); +// cx.foreground().run_until_parked(); + +// editor.update(cx, |editor, cx| { +// let expected_hints = vec!["0".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Should not update hints while the work task is running" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!( +// editor.inlay_hint_cache().version, +// edits_made, +// "Should not update the cache while the work task is running" +// ); +// }); + +// fake_server.notify::(lsp::ProgressParams { +// token: lsp::ProgressToken::String(progress_token.to_string()), +// value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End( +// lsp::WorkDoneProgressEnd::default(), +// )), +// }); +// cx.foreground().run_until_parked(); + +// edits_made += 1; +// editor.update(cx, |editor, cx| { +// let expected_hints = vec!["1".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "New hints should be queried after the work task is done" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!( +// editor.inlay_hint_cache().version, +// edits_made, +// "Cache version should udpate once after the work task is done" +// ); +// }); +// } + +// #[gpui::test] +// async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: true, +// show_type_hints: true, +// show_parameter_hints: true, +// show_other_hints: true, +// }) +// }); + +// let fs = FakeFs::new(cx.background()); +// fs.insert_tree( +// "/a", +// json!({ +// "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out", +// "other.md": "Test md file with some text", +// }), +// ) +// .await; +// let project = Project::test(fs, ["/a".as_ref()], cx).await; +// let workspace = cx +// .add_window(|cx| Workspace::test_new(project.clone(), cx)) +// .root(cx); +// let worktree_id = workspace.update(cx, |workspace, cx| { +// workspace.project().read_with(cx, |project, cx| { +// project.worktrees(cx).next().unwrap().read(cx).id() +// }) +// }); + +// let mut rs_fake_servers = None; +// let mut md_fake_servers = None; +// for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] { +// let mut language = Language::new( +// LanguageConfig { +// name: name.into(), +// path_suffixes: vec![path_suffix.to_string()], +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ); +// let fake_servers = language +// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { +// name, +// capabilities: lsp::ServerCapabilities { +// inlay_hint_provider: Some(lsp::OneOf::Left(true)), +// ..Default::default() +// }, +// ..Default::default() +// })) +// .await; +// match name { +// "Rust" => rs_fake_servers = Some(fake_servers), +// "Markdown" => md_fake_servers = Some(fake_servers), +// _ => unreachable!(), +// } +// project.update(cx, |project, _| { +// project.languages().add(Arc::new(language)); +// }); +// } + +// let _rs_buffer = project +// .update(cx, |project, cx| { +// project.open_local_buffer("/a/main.rs", cx) +// }) +// .await +// .unwrap(); +// cx.foreground().run_until_parked(); +// cx.foreground().start_waiting(); +// let rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap(); +// let rs_editor = workspace +// .update(cx, |workspace, cx| { +// workspace.open_path((worktree_id, "main.rs"), None, true, cx) +// }) +// .await +// .unwrap() +// .downcast::() +// .unwrap(); +// let rs_lsp_request_count = Arc::new(AtomicU32::new(0)); +// rs_fake_server +// .handle_request::(move |params, _| { +// let task_lsp_request_count = Arc::clone(&rs_lsp_request_count); +// async move { +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path("/a/main.rs").unwrap(), +// ); +// let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst); +// Ok(Some(vec![lsp::InlayHint { +// position: lsp::Position::new(0, i), +// label: lsp::InlayHintLabel::String(i.to_string()), +// kind: None, +// text_edits: None, +// tooltip: None, +// padding_left: None, +// padding_right: None, +// data: None, +// }])) +// } +// }) +// .next() +// .await; +// cx.foreground().run_until_parked(); +// rs_editor.update(cx, |editor, cx| { +// let expected_hints = vec!["0".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Should get its first hints when opening the editor" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!( +// editor.inlay_hint_cache().version, +// 1, +// "Rust editor update the cache version after every cache/view change" +// ); +// }); + +// cx.foreground().run_until_parked(); +// let _md_buffer = project +// .update(cx, |project, cx| { +// project.open_local_buffer("/a/other.md", cx) +// }) +// .await +// .unwrap(); +// cx.foreground().run_until_parked(); +// cx.foreground().start_waiting(); +// let md_fake_server = md_fake_servers.unwrap().next().await.unwrap(); +// let md_editor = workspace +// .update(cx, |workspace, cx| { +// workspace.open_path((worktree_id, "other.md"), None, true, cx) +// }) +// .await +// .unwrap() +// .downcast::() +// .unwrap(); +// let md_lsp_request_count = Arc::new(AtomicU32::new(0)); +// md_fake_server +// .handle_request::(move |params, _| { +// let task_lsp_request_count = Arc::clone(&md_lsp_request_count); +// async move { +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path("/a/other.md").unwrap(), +// ); +// let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst); +// Ok(Some(vec![lsp::InlayHint { +// position: lsp::Position::new(0, i), +// label: lsp::InlayHintLabel::String(i.to_string()), +// kind: None, +// text_edits: None, +// tooltip: None, +// padding_left: None, +// padding_right: None, +// data: None, +// }])) +// } +// }) +// .next() +// .await; +// cx.foreground().run_until_parked(); +// md_editor.update(cx, |editor, cx| { +// let expected_hints = vec!["0".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Markdown editor should have a separate verison, repeating Rust editor rules" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!(editor.inlay_hint_cache().version, 1); +// }); + +// rs_editor.update(cx, |editor, cx| { +// editor.change_selections(None, cx, |s| s.select_ranges([13..13])); +// editor.handle_input("some rs change", cx); +// }); +// cx.foreground().run_until_parked(); +// rs_editor.update(cx, |editor, cx| { +// let expected_hints = vec!["1".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Rust inlay cache should change after the edit" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!( +// editor.inlay_hint_cache().version, +// 2, +// "Every time hint cache changes, cache version should be incremented" +// ); +// }); +// md_editor.update(cx, |editor, cx| { +// let expected_hints = vec!["0".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Markdown editor should not be affected by Rust editor changes" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!(editor.inlay_hint_cache().version, 1); +// }); + +// md_editor.update(cx, |editor, cx| { +// editor.change_selections(None, cx, |s| s.select_ranges([13..13])); +// editor.handle_input("some md change", cx); +// }); +// cx.foreground().run_until_parked(); +// md_editor.update(cx, |editor, cx| { +// let expected_hints = vec!["1".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Rust editor should not be affected by Markdown editor changes" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!(editor.inlay_hint_cache().version, 2); +// }); +// rs_editor.update(cx, |editor, cx| { +// let expected_hints = vec!["1".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Markdown editor should also change independently" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!(editor.inlay_hint_cache().version, 2); +// }); +// } + +// #[gpui::test] +// async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) { +// let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); +// init_test(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: true, +// show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), +// show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), +// show_other_hints: allowed_hint_kinds.contains(&None), +// }) +// }); + +// let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; +// let lsp_request_count = Arc::new(AtomicU32::new(0)); +// let another_lsp_request_count = Arc::clone(&lsp_request_count); +// fake_server +// .handle_request::(move |params, _| { +// let task_lsp_request_count = Arc::clone(&another_lsp_request_count); +// async move { +// Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst); +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path(file_with_hints).unwrap(), +// ); +// Ok(Some(vec![ +// lsp::InlayHint { +// position: lsp::Position::new(0, 1), +// label: lsp::InlayHintLabel::String("type hint".to_string()), +// kind: Some(lsp::InlayHintKind::TYPE), +// text_edits: None, +// tooltip: None, +// padding_left: None, +// padding_right: None, +// data: None, +// }, +// lsp::InlayHint { +// position: lsp::Position::new(0, 2), +// label: lsp::InlayHintLabel::String("parameter hint".to_string()), +// kind: Some(lsp::InlayHintKind::PARAMETER), +// text_edits: None, +// tooltip: None, +// padding_left: None, +// padding_right: None, +// data: None, +// }, +// lsp::InlayHint { +// position: lsp::Position::new(0, 3), +// label: lsp::InlayHintLabel::String("other hint".to_string()), +// kind: None, +// text_edits: None, +// tooltip: None, +// padding_left: None, +// padding_right: None, +// data: None, +// }, +// ])) +// } +// }) +// .next() +// .await; +// cx.foreground().run_until_parked(); + +// let mut edits_made = 1; +// editor.update(cx, |editor, cx| { +// assert_eq!( +// lsp_request_count.load(Ordering::Relaxed), +// 1, +// "Should query new hints once" +// ); +// assert_eq!( +// vec![ +// "other hint".to_string(), +// "parameter hint".to_string(), +// "type hint".to_string(), +// ], +// cached_hint_labels(editor), +// "Should get its first hints when opening the editor" +// ); +// assert_eq!( +// vec!["other hint".to_string(), "type hint".to_string()], +// visible_hint_labels(editor, cx) +// ); +// let inlay_cache = editor.inlay_hint_cache(); +// assert_eq!( +// inlay_cache.allowed_hint_kinds, allowed_hint_kinds, +// "Cache should use editor settings to get the allowed hint kinds" +// ); +// assert_eq!( +// inlay_cache.version, edits_made, +// "The editor update the cache version after every cache/view change" +// ); +// }); + +// fake_server +// .request::(()) +// .await +// .expect("inlay refresh request failed"); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// assert_eq!( +// lsp_request_count.load(Ordering::Relaxed), +// 2, +// "Should load new hints twice" +// ); +// assert_eq!( +// vec![ +// "other hint".to_string(), +// "parameter hint".to_string(), +// "type hint".to_string(), +// ], +// cached_hint_labels(editor), +// "Cached hints should not change due to allowed hint kinds settings update" +// ); +// assert_eq!( +// vec!["other hint".to_string(), "type hint".to_string()], +// visible_hint_labels(editor, cx) +// ); +// assert_eq!( +// editor.inlay_hint_cache().version, +// edits_made, +// "Should not update cache version due to new loaded hints being the same" +// ); +// }); + +// for (new_allowed_hint_kinds, expected_visible_hints) in [ +// (HashSet::from_iter([None]), vec!["other hint".to_string()]), +// ( +// HashSet::from_iter([Some(InlayHintKind::Type)]), +// vec!["type hint".to_string()], +// ), +// ( +// HashSet::from_iter([Some(InlayHintKind::Parameter)]), +// vec!["parameter hint".to_string()], +// ), +// ( +// HashSet::from_iter([None, Some(InlayHintKind::Type)]), +// vec!["other hint".to_string(), "type hint".to_string()], +// ), +// ( +// HashSet::from_iter([None, Some(InlayHintKind::Parameter)]), +// vec!["other hint".to_string(), "parameter hint".to_string()], +// ), +// ( +// HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]), +// vec!["parameter hint".to_string(), "type hint".to_string()], +// ), +// ( +// HashSet::from_iter([ +// None, +// Some(InlayHintKind::Type), +// Some(InlayHintKind::Parameter), +// ]), +// vec![ +// "other hint".to_string(), +// "parameter hint".to_string(), +// "type hint".to_string(), +// ], +// ), +// ] { +// edits_made += 1; +// update_test_language_settings(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: true, +// show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), +// show_parameter_hints: new_allowed_hint_kinds +// .contains(&Some(InlayHintKind::Parameter)), +// show_other_hints: new_allowed_hint_kinds.contains(&None), +// }) +// }); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// assert_eq!( +// lsp_request_count.load(Ordering::Relaxed), +// 2, +// "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}" +// ); +// assert_eq!( +// vec![ +// "other hint".to_string(), +// "parameter hint".to_string(), +// "type hint".to_string(), +// ], +// cached_hint_labels(editor), +// "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}" +// ); +// assert_eq!( +// expected_visible_hints, +// visible_hint_labels(editor, cx), +// "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}" +// ); +// let inlay_cache = editor.inlay_hint_cache(); +// assert_eq!( +// inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds, +// "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}" +// ); +// assert_eq!( +// inlay_cache.version, edits_made, +// "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change" +// ); +// }); +// } + +// edits_made += 1; +// let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]); +// update_test_language_settings(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: false, +// show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), +// show_parameter_hints: another_allowed_hint_kinds +// .contains(&Some(InlayHintKind::Parameter)), +// show_other_hints: another_allowed_hint_kinds.contains(&None), +// }) +// }); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// assert_eq!( +// lsp_request_count.load(Ordering::Relaxed), +// 2, +// "Should not load new hints when hints got disabled" +// ); +// assert!( +// cached_hint_labels(editor).is_empty(), +// "Should clear the cache when hints got disabled" +// ); +// assert!( +// visible_hint_labels(editor, cx).is_empty(), +// "Should clear visible hints when hints got disabled" +// ); +// let inlay_cache = editor.inlay_hint_cache(); +// assert_eq!( +// inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds, +// "Should update its allowed hint kinds even when hints got disabled" +// ); +// assert_eq!( +// inlay_cache.version, edits_made, +// "The editor should update the cache version after hints got disabled" +// ); +// }); + +// fake_server +// .request::(()) +// .await +// .expect("inlay refresh request failed"); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// assert_eq!( +// lsp_request_count.load(Ordering::Relaxed), +// 2, +// "Should not load new hints when they got disabled" +// ); +// assert!(cached_hint_labels(editor).is_empty()); +// assert!(visible_hint_labels(editor, cx).is_empty()); +// assert_eq!( +// editor.inlay_hint_cache().version, edits_made, +// "The editor should not update the cache version after /refresh query without updates" +// ); +// }); + +// let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]); +// edits_made += 1; +// update_test_language_settings(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: true, +// show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), +// show_parameter_hints: final_allowed_hint_kinds +// .contains(&Some(InlayHintKind::Parameter)), +// show_other_hints: final_allowed_hint_kinds.contains(&None), +// }) +// }); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// assert_eq!( +// lsp_request_count.load(Ordering::Relaxed), +// 3, +// "Should query for new hints when they got reenabled" +// ); +// assert_eq!( +// vec![ +// "other hint".to_string(), +// "parameter hint".to_string(), +// "type hint".to_string(), +// ], +// cached_hint_labels(editor), +// "Should get its cached hints fully repopulated after the hints got reenabled" +// ); +// assert_eq!( +// vec!["parameter hint".to_string()], +// visible_hint_labels(editor, cx), +// "Should get its visible hints repopulated and filtered after the h" +// ); +// let inlay_cache = editor.inlay_hint_cache(); +// assert_eq!( +// inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds, +// "Cache should update editor settings when hints got reenabled" +// ); +// assert_eq!( +// inlay_cache.version, edits_made, +// "Cache should update its version after hints got reenabled" +// ); +// }); + +// fake_server +// .request::(()) +// .await +// .expect("inlay refresh request failed"); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// assert_eq!( +// lsp_request_count.load(Ordering::Relaxed), +// 4, +// "Should query for new hints again" +// ); +// assert_eq!( +// vec![ +// "other hint".to_string(), +// "parameter hint".to_string(), +// "type hint".to_string(), +// ], +// cached_hint_labels(editor), +// ); +// assert_eq!( +// vec!["parameter hint".to_string()], +// visible_hint_labels(editor, cx), +// ); +// assert_eq!(editor.inlay_hint_cache().version, edits_made); +// }); +// } + +// #[gpui::test] +// async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: true, +// show_type_hints: true, +// show_parameter_hints: true, +// show_other_hints: true, +// }) +// }); + +// let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; +// let fake_server = Arc::new(fake_server); +// let lsp_request_count = Arc::new(AtomicU32::new(0)); +// let another_lsp_request_count = Arc::clone(&lsp_request_count); +// fake_server +// .handle_request::(move |params, _| { +// let task_lsp_request_count = Arc::clone(&another_lsp_request_count); +// async move { +// let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1; +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path(file_with_hints).unwrap(), +// ); +// Ok(Some(vec![lsp::InlayHint { +// position: lsp::Position::new(0, i), +// label: lsp::InlayHintLabel::String(i.to_string()), +// kind: None, +// text_edits: None, +// tooltip: None, +// padding_left: None, +// padding_right: None, +// data: None, +// }])) +// } +// }) +// .next() +// .await; + +// let mut expected_changes = Vec::new(); +// for change_after_opening in [ +// "initial change #1", +// "initial change #2", +// "initial change #3", +// ] { +// editor.update(cx, |editor, cx| { +// editor.change_selections(None, cx, |s| s.select_ranges([13..13])); +// editor.handle_input(change_after_opening, cx); +// }); +// expected_changes.push(change_after_opening); +// } + +// cx.foreground().run_until_parked(); + +// editor.update(cx, |editor, cx| { +// let current_text = editor.text(cx); +// for change in &expected_changes { +// assert!( +// current_text.contains(change), +// "Should apply all changes made" +// ); +// } +// assert_eq!( +// lsp_request_count.load(Ordering::Relaxed), +// 2, +// "Should query new hints twice: for editor init and for the last edit that interrupted all others" +// ); +// let expected_hints = vec!["2".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Should get hints from the last edit landed only" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!( +// editor.inlay_hint_cache().version, 1, +// "Only one update should be registered in the cache after all cancellations" +// ); +// }); + +// let mut edits = Vec::new(); +// for async_later_change in [ +// "another change #1", +// "another change #2", +// "another change #3", +// ] { +// expected_changes.push(async_later_change); +// let task_editor = editor.clone(); +// let mut task_cx = cx.clone(); +// edits.push(cx.foreground().spawn(async move { +// task_editor.update(&mut task_cx, |editor, cx| { +// editor.change_selections(None, cx, |s| s.select_ranges([13..13])); +// editor.handle_input(async_later_change, cx); +// }); +// })); +// } +// let _ = future::join_all(edits).await; +// cx.foreground().run_until_parked(); + +// editor.update(cx, |editor, cx| { +// let current_text = editor.text(cx); +// for change in &expected_changes { +// assert!( +// current_text.contains(change), +// "Should apply all changes made" +// ); +// } +// assert_eq!( +// lsp_request_count.load(Ordering::SeqCst), +// 3, +// "Should query new hints one more time, for the last edit only" +// ); +// let expected_hints = vec!["3".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Should get hints from the last edit landed only" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!( +// editor.inlay_hint_cache().version, +// 2, +// "Should update the cache version once more, for the new change" +// ); +// }); +// } + +// #[gpui::test(iterations = 10)] +// async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: true, +// show_type_hints: true, +// show_parameter_hints: true, +// show_other_hints: true, +// }) +// }); + +// let mut language = Language::new( +// LanguageConfig { +// name: "Rust".into(), +// path_suffixes: vec!["rs".to_string()], +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ); +// let mut fake_servers = language +// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { +// capabilities: lsp::ServerCapabilities { +// inlay_hint_provider: Some(lsp::OneOf::Left(true)), +// ..Default::default() +// }, +// ..Default::default() +// })) +// .await; +// let fs = FakeFs::new(cx.background()); +// fs.insert_tree( +// "/a", +// json!({ +// "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)), +// "other.rs": "// Test file", +// }), +// ) +// .await; +// let project = Project::test(fs, ["/a".as_ref()], cx).await; +// project.update(cx, |project, _| project.languages().add(Arc::new(language))); +// let workspace = cx +// .add_window(|cx| Workspace::test_new(project.clone(), cx)) +// .root(cx); +// let worktree_id = workspace.update(cx, |workspace, cx| { +// workspace.project().read_with(cx, |project, cx| { +// project.worktrees(cx).next().unwrap().read(cx).id() +// }) +// }); + +// let _buffer = project +// .update(cx, |project, cx| { +// project.open_local_buffer("/a/main.rs", cx) +// }) +// .await +// .unwrap(); +// cx.foreground().run_until_parked(); +// cx.foreground().start_waiting(); +// let fake_server = fake_servers.next().await.unwrap(); +// let editor = workspace +// .update(cx, |workspace, cx| { +// workspace.open_path((worktree_id, "main.rs"), None, true, cx) +// }) +// .await +// .unwrap() +// .downcast::() +// .unwrap(); +// let lsp_request_ranges = Arc::new(Mutex::new(Vec::new())); +// let lsp_request_count = Arc::new(AtomicUsize::new(0)); +// let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges); +// let closure_lsp_request_count = Arc::clone(&lsp_request_count); +// fake_server +// .handle_request::(move |params, _| { +// let task_lsp_request_ranges = Arc::clone(&closure_lsp_request_ranges); +// let task_lsp_request_count = Arc::clone(&closure_lsp_request_count); +// async move { +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path("/a/main.rs").unwrap(), +// ); + +// task_lsp_request_ranges.lock().push(params.range); +// let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1; +// Ok(Some(vec![lsp::InlayHint { +// position: params.range.end, +// label: lsp::InlayHintLabel::String(i.to_string()), +// kind: None, +// text_edits: None, +// tooltip: None, +// padding_left: None, +// padding_right: None, +// data: None, +// }])) +// } +// }) +// .next() +// .await; +// fn editor_visible_range( +// editor: &ViewHandle, +// cx: &mut gpui::TestAppContext, +// ) -> Range { +// let ranges = editor.update(cx, |editor, cx| editor.excerpt_visible_offsets(None, cx)); +// assert_eq!( +// ranges.len(), +// 1, +// "Single buffer should produce a single excerpt with visible range" +// ); +// let (_, (excerpt_buffer, _, excerpt_visible_range)) = +// ranges.into_iter().next().unwrap(); +// excerpt_buffer.update(cx, |buffer, _| { +// let snapshot = buffer.snapshot(); +// let start = buffer +// .anchor_before(excerpt_visible_range.start) +// .to_point(&snapshot); +// let end = buffer +// .anchor_after(excerpt_visible_range.end) +// .to_point(&snapshot); +// start..end +// }) +// } + +// // in large buffers, requests are made for more than visible range of a buffer. +// // invisible parts are queried later, to avoid excessive requests on quick typing. +// // wait the timeout needed to get all requests. +// cx.foreground().advance_clock(Duration::from_millis( +// INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100, +// )); +// cx.foreground().run_until_parked(); +// let initial_visible_range = editor_visible_range(&editor, cx); +// let lsp_initial_visible_range = lsp::Range::new( +// lsp::Position::new( +// initial_visible_range.start.row, +// initial_visible_range.start.column, +// ), +// lsp::Position::new( +// initial_visible_range.end.row, +// initial_visible_range.end.column, +// ), +// ); +// let expected_initial_query_range_end = +// lsp::Position::new(initial_visible_range.end.row * 2, 2); +// let mut expected_invisible_query_start = lsp_initial_visible_range.end; +// expected_invisible_query_start.character += 1; +// editor.update(cx, |editor, cx| { +// let ranges = lsp_request_ranges.lock().drain(..).collect::>(); +// assert_eq!(ranges.len(), 2, +// "When scroll is at the edge of a big document, its visible part and the same range further should be queried in order, but got: {ranges:?}"); +// let visible_query_range = &ranges[0]; +// assert_eq!(visible_query_range.start, lsp_initial_visible_range.start); +// assert_eq!(visible_query_range.end, lsp_initial_visible_range.end); +// let invisible_query_range = &ranges[1]; + +// assert_eq!(invisible_query_range.start, expected_invisible_query_start, "Should initially query visible edge of the document"); +// assert_eq!(invisible_query_range.end, expected_initial_query_range_end, "Should initially query visible edge of the document"); + +// let requests_count = lsp_request_count.load(Ordering::Acquire); +// assert_eq!(requests_count, 2, "Visible + invisible request"); +// let expected_hints = vec!["1".to_string(), "2".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Should have hints from both LSP requests made for a big file" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx), "Should display only hints from the visible range"); +// assert_eq!( +// editor.inlay_hint_cache().version, requests_count, +// "LSP queries should've bumped the cache version" +// ); +// }); + +// editor.update(cx, |editor, cx| { +// editor.scroll_screen(&ScrollAmount::Page(1.0), cx); +// editor.scroll_screen(&ScrollAmount::Page(1.0), cx); +// }); +// cx.foreground().advance_clock(Duration::from_millis( +// INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100, +// )); +// cx.foreground().run_until_parked(); +// let visible_range_after_scrolls = editor_visible_range(&editor, cx); +// let visible_line_count = +// editor.update(cx, |editor, _| editor.visible_line_count().unwrap()); +// let selection_in_cached_range = editor.update(cx, |editor, cx| { +// let ranges = lsp_request_ranges +// .lock() +// .drain(..) +// .sorted_by_key(|r| r.start) +// .collect::>(); +// assert_eq!( +// ranges.len(), +// 2, +// "Should query 2 ranges after both scrolls, but got: {ranges:?}" +// ); +// let first_scroll = &ranges[0]; +// let second_scroll = &ranges[1]; +// assert_eq!( +// first_scroll.end, second_scroll.start, +// "Should query 2 adjacent ranges after the scrolls, but got: {ranges:?}" +// ); +// assert_eq!( +// first_scroll.start, expected_initial_query_range_end, +// "First scroll should start the query right after the end of the original scroll", +// ); +// assert_eq!( +// second_scroll.end, +// lsp::Position::new( +// visible_range_after_scrolls.end.row +// + visible_line_count.ceil() as u32, +// 1, +// ), +// "Second scroll should query one more screen down after the end of the visible range" +// ); + +// let lsp_requests = lsp_request_count.load(Ordering::Acquire); +// assert_eq!(lsp_requests, 4, "Should query for hints after every scroll"); +// let expected_hints = vec![ +// "1".to_string(), +// "2".to_string(), +// "3".to_string(), +// "4".to_string(), +// ]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Should have hints from the new LSP response after the edit" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!( +// editor.inlay_hint_cache().version, +// lsp_requests, +// "Should update the cache for every LSP response with hints added" +// ); + +// let mut selection_in_cached_range = visible_range_after_scrolls.end; +// selection_in_cached_range.row -= visible_line_count.ceil() as u32; +// selection_in_cached_range +// }); + +// editor.update(cx, |editor, cx| { +// editor.change_selections(Some(Autoscroll::center()), cx, |s| { +// s.select_ranges([selection_in_cached_range..selection_in_cached_range]) +// }); +// }); +// cx.foreground().advance_clock(Duration::from_millis( +// INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100, +// )); +// cx.foreground().run_until_parked(); +// editor.update(cx, |_, _| { +// let ranges = lsp_request_ranges +// .lock() +// .drain(..) +// .sorted_by_key(|r| r.start) +// .collect::>(); +// assert!(ranges.is_empty(), "No new ranges or LSP queries should be made after returning to the selection with cached hints"); +// assert_eq!(lsp_request_count.load(Ordering::Acquire), 4); +// }); + +// editor.update(cx, |editor, cx| { +// editor.handle_input("++++more text++++", cx); +// }); +// cx.foreground().advance_clock(Duration::from_millis( +// INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100, +// )); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// let mut ranges = lsp_request_ranges.lock().drain(..).collect::>(); +// ranges.sort_by_key(|r| r.start); + +// assert_eq!(ranges.len(), 3, +// "On edit, should scroll to selection and query a range around it: visible + same range above and below. Instead, got query ranges {ranges:?}"); +// let above_query_range = &ranges[0]; +// let visible_query_range = &ranges[1]; +// let below_query_range = &ranges[2]; +// assert!(above_query_range.end.character < visible_query_range.start.character || above_query_range.end.line + 1 == visible_query_range.start.line, +// "Above range {above_query_range:?} should be before visible range {visible_query_range:?}"); +// assert!(visible_query_range.end.character < below_query_range.start.character || visible_query_range.end.line + 1 == below_query_range.start.line, +// "Visible range {visible_query_range:?} should be before below range {below_query_range:?}"); +// assert!(above_query_range.start.line < selection_in_cached_range.row, +// "Hints should be queried with the selected range after the query range start"); +// assert!(below_query_range.end.line > selection_in_cached_range.row, +// "Hints should be queried with the selected range before the query range end"); +// assert!(above_query_range.start.line <= selection_in_cached_range.row - (visible_line_count * 3.0 / 2.0) as u32, +// "Hints query range should contain one more screen before"); +// assert!(below_query_range.end.line >= selection_in_cached_range.row + (visible_line_count * 3.0 / 2.0) as u32, +// "Hints query range should contain one more screen after"); + +// let lsp_requests = lsp_request_count.load(Ordering::Acquire); +// assert_eq!(lsp_requests, 7, "There should be a visible range and two ranges above and below it queried"); +// let expected_hints = vec!["5".to_string(), "6".to_string(), "7".to_string()]; +// assert_eq!(expected_hints, cached_hint_labels(editor), +// "Should have hints from the new LSP response after the edit"); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!(editor.inlay_hint_cache().version, lsp_requests, "Should update the cache for every LSP response with hints added"); +// }); +// } + +// #[gpui::test(iterations = 10)] +// async fn test_multiple_excerpts_large_multibuffer( +// deterministic: Arc, +// cx: &mut gpui::TestAppContext, +// ) { +// init_test(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: true, +// show_type_hints: true, +// show_parameter_hints: true, +// show_other_hints: true, +// }) +// }); + +// let mut language = Language::new( +// LanguageConfig { +// name: "Rust".into(), +// path_suffixes: vec!["rs".to_string()], +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ); +// let mut fake_servers = language +// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { +// capabilities: lsp::ServerCapabilities { +// inlay_hint_provider: Some(lsp::OneOf::Left(true)), +// ..Default::default() +// }, +// ..Default::default() +// })) +// .await; +// let language = Arc::new(language); +// let fs = FakeFs::new(cx.background()); +// fs.insert_tree( +// "/a", +// json!({ +// "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::>().join("")), +// "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::>().join("")), +// }), +// ) +// .await; +// let project = Project::test(fs, ["/a".as_ref()], cx).await; +// project.update(cx, |project, _| { +// project.languages().add(Arc::clone(&language)) +// }); +// let workspace = cx +// .add_window(|cx| Workspace::test_new(project.clone(), cx)) +// .root(cx); +// let worktree_id = workspace.update(cx, |workspace, cx| { +// workspace.project().read_with(cx, |project, cx| { +// project.worktrees(cx).next().unwrap().read(cx).id() +// }) +// }); + +// let buffer_1 = project +// .update(cx, |project, cx| { +// project.open_buffer((worktree_id, "main.rs"), cx) +// }) +// .await +// .unwrap(); +// let buffer_2 = project +// .update(cx, |project, cx| { +// project.open_buffer((worktree_id, "other.rs"), cx) +// }) +// .await +// .unwrap(); +// let multibuffer = cx.add_model(|cx| { +// let mut multibuffer = MultiBuffer::new(0); +// multibuffer.push_excerpts( +// buffer_1.clone(), +// [ +// ExcerptRange { +// context: Point::new(0, 0)..Point::new(2, 0), +// primary: None, +// }, +// ExcerptRange { +// context: Point::new(4, 0)..Point::new(11, 0), +// primary: None, +// }, +// ExcerptRange { +// context: Point::new(22, 0)..Point::new(33, 0), +// primary: None, +// }, +// ExcerptRange { +// context: Point::new(44, 0)..Point::new(55, 0), +// primary: None, +// }, +// ExcerptRange { +// context: Point::new(56, 0)..Point::new(66, 0), +// primary: None, +// }, +// ExcerptRange { +// context: Point::new(67, 0)..Point::new(77, 0), +// primary: None, +// }, +// ], +// cx, +// ); +// multibuffer.push_excerpts( +// buffer_2.clone(), +// [ +// ExcerptRange { +// context: Point::new(0, 1)..Point::new(2, 1), +// primary: None, +// }, +// ExcerptRange { +// context: Point::new(4, 1)..Point::new(11, 1), +// primary: None, +// }, +// ExcerptRange { +// context: Point::new(22, 1)..Point::new(33, 1), +// primary: None, +// }, +// ExcerptRange { +// context: Point::new(44, 1)..Point::new(55, 1), +// primary: None, +// }, +// ExcerptRange { +// context: Point::new(56, 1)..Point::new(66, 1), +// primary: None, +// }, +// ExcerptRange { +// context: Point::new(67, 1)..Point::new(77, 1), +// primary: None, +// }, +// ], +// cx, +// ); +// multibuffer +// }); + +// deterministic.run_until_parked(); +// cx.foreground().run_until_parked(); +// let editor = cx +// .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx)) +// .root(cx); +// let editor_edited = Arc::new(AtomicBool::new(false)); +// let fake_server = fake_servers.next().await.unwrap(); +// let closure_editor_edited = Arc::clone(&editor_edited); +// fake_server +// .handle_request::(move |params, _| { +// let task_editor_edited = Arc::clone(&closure_editor_edited); +// async move { +// let hint_text = if params.text_document.uri +// == lsp::Url::from_file_path("/a/main.rs").unwrap() +// { +// "main hint" +// } else if params.text_document.uri +// == lsp::Url::from_file_path("/a/other.rs").unwrap() +// { +// "other hint" +// } else { +// panic!("unexpected uri: {:?}", params.text_document.uri); +// }; + +// // one hint per excerpt +// let positions = [ +// lsp::Position::new(0, 2), +// lsp::Position::new(4, 2), +// lsp::Position::new(22, 2), +// lsp::Position::new(44, 2), +// lsp::Position::new(56, 2), +// lsp::Position::new(67, 2), +// ]; +// let out_of_range_hint = lsp::InlayHint { +// position: lsp::Position::new( +// params.range.start.line + 99, +// params.range.start.character + 99, +// ), +// label: lsp::InlayHintLabel::String( +// "out of excerpt range, should be ignored".to_string(), +// ), +// kind: None, +// text_edits: None, +// tooltip: None, +// padding_left: None, +// padding_right: None, +// data: None, +// }; + +// let edited = task_editor_edited.load(Ordering::Acquire); +// Ok(Some( +// std::iter::once(out_of_range_hint) +// .chain(positions.into_iter().enumerate().map(|(i, position)| { +// lsp::InlayHint { +// position, +// label: lsp::InlayHintLabel::String(format!( +// "{hint_text}{} #{i}", +// if edited { "(edited)" } else { "" }, +// )), +// kind: None, +// text_edits: None, +// tooltip: None, +// padding_left: None, +// padding_right: None, +// data: None, +// } +// })) +// .collect(), +// )) +// } +// }) +// .next() +// .await; +// cx.foreground().run_until_parked(); + +// editor.update(cx, |editor, cx| { +// let expected_hints = vec![ +// "main hint #0".to_string(), +// "main hint #1".to_string(), +// "main hint #2".to_string(), +// "main hint #3".to_string(), +// ]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the verison"); +// }); + +// editor.update(cx, |editor, cx| { +// editor.change_selections(Some(Autoscroll::Next), cx, |s| { +// s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) +// }); +// editor.change_selections(Some(Autoscroll::Next), cx, |s| { +// s.select_ranges([Point::new(22, 0)..Point::new(22, 0)]) +// }); +// editor.change_selections(Some(Autoscroll::Next), cx, |s| { +// s.select_ranges([Point::new(50, 0)..Point::new(50, 0)]) +// }); +// }); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// let expected_hints = vec![ +// "main hint #0".to_string(), +// "main hint #1".to_string(), +// "main hint #2".to_string(), +// "main hint #3".to_string(), +// "main hint #4".to_string(), +// "main hint #5".to_string(), +// "other hint #0".to_string(), +// "other hint #1".to_string(), +// "other hint #2".to_string(), +// ]; +// assert_eq!(expected_hints, cached_hint_labels(editor), +// "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits"); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), +// "Due to every excerpt having one hint, we update cache per new excerpt scrolled"); +// }); + +// editor.update(cx, |editor, cx| { +// editor.change_selections(Some(Autoscroll::Next), cx, |s| { +// s.select_ranges([Point::new(100, 0)..Point::new(100, 0)]) +// }); +// }); +// cx.foreground().advance_clock(Duration::from_millis( +// INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100, +// )); +// cx.foreground().run_until_parked(); +// let last_scroll_update_version = editor.update(cx, |editor, cx| { +// let expected_hints = vec![ +// "main hint #0".to_string(), +// "main hint #1".to_string(), +// "main hint #2".to_string(), +// "main hint #3".to_string(), +// "main hint #4".to_string(), +// "main hint #5".to_string(), +// "other hint #0".to_string(), +// "other hint #1".to_string(), +// "other hint #2".to_string(), +// "other hint #3".to_string(), +// "other hint #4".to_string(), +// "other hint #5".to_string(), +// ]; +// assert_eq!(expected_hints, cached_hint_labels(editor), +// "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched"); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!(editor.inlay_hint_cache().version, expected_hints.len()); +// expected_hints.len() +// }); + +// editor.update(cx, |editor, cx| { +// editor.change_selections(Some(Autoscroll::Next), cx, |s| { +// s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) +// }); +// }); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// let expected_hints = vec![ +// "main hint #0".to_string(), +// "main hint #1".to_string(), +// "main hint #2".to_string(), +// "main hint #3".to_string(), +// "main hint #4".to_string(), +// "main hint #5".to_string(), +// "other hint #0".to_string(), +// "other hint #1".to_string(), +// "other hint #2".to_string(), +// "other hint #3".to_string(), +// "other hint #4".to_string(), +// "other hint #5".to_string(), +// ]; +// assert_eq!(expected_hints, cached_hint_labels(editor), +// "After multibuffer was scrolled to the end, further scrolls up should not bring more hints"); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer"); +// }); + +// editor_edited.store(true, Ordering::Release); +// editor.update(cx, |editor, cx| { +// editor.change_selections(None, cx, |s| { +// s.select_ranges([Point::new(56, 0)..Point::new(56, 0)]) +// }); +// editor.handle_input("++++more text++++", cx); +// }); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// let expected_hints = vec![ +// "main hint(edited) #0".to_string(), +// "main hint(edited) #1".to_string(), +// "main hint(edited) #2".to_string(), +// "main hint(edited) #3".to_string(), +// "main hint(edited) #4".to_string(), +// "main hint(edited) #5".to_string(), +// "other hint(edited) #0".to_string(), +// "other hint(edited) #1".to_string(), +// ]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "After multibuffer edit, editor gets scolled back to the last selection; \ +// all hints should be invalidated and requeried for all of its visible excerpts" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + +// let current_cache_version = editor.inlay_hint_cache().version; +// let minimum_expected_version = last_scroll_update_version + expected_hints.len(); +// assert!( +// current_cache_version == minimum_expected_version || current_cache_version == minimum_expected_version + 1, +// "Due to every excerpt having one hint, cache should update per new excerpt received + 1 potential sporadic update" +// ); +// }); +// } + +// #[gpui::test] +// async fn test_excerpts_removed( +// deterministic: Arc, +// cx: &mut gpui::TestAppContext, +// ) { +// init_test(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: true, +// show_type_hints: false, +// show_parameter_hints: false, +// show_other_hints: false, +// }) +// }); + +// let mut language = Language::new( +// LanguageConfig { +// name: "Rust".into(), +// path_suffixes: vec!["rs".to_string()], +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ); +// let mut fake_servers = language +// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { +// capabilities: lsp::ServerCapabilities { +// inlay_hint_provider: Some(lsp::OneOf::Left(true)), +// ..Default::default() +// }, +// ..Default::default() +// })) +// .await; +// let language = Arc::new(language); +// let fs = FakeFs::new(cx.background()); +// fs.insert_tree( +// "/a", +// json!({ +// "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::>().join("")), +// "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::>().join("")), +// }), +// ) +// .await; +// let project = Project::test(fs, ["/a".as_ref()], cx).await; +// project.update(cx, |project, _| { +// project.languages().add(Arc::clone(&language)) +// }); +// let workspace = cx +// .add_window(|cx| Workspace::test_new(project.clone(), cx)) +// .root(cx); +// let worktree_id = workspace.update(cx, |workspace, cx| { +// workspace.project().read_with(cx, |project, cx| { +// project.worktrees(cx).next().unwrap().read(cx).id() +// }) +// }); + +// let buffer_1 = project +// .update(cx, |project, cx| { +// project.open_buffer((worktree_id, "main.rs"), cx) +// }) +// .await +// .unwrap(); +// let buffer_2 = project +// .update(cx, |project, cx| { +// project.open_buffer((worktree_id, "other.rs"), cx) +// }) +// .await +// .unwrap(); +// let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); +// let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| { +// let buffer_1_excerpts = multibuffer.push_excerpts( +// buffer_1.clone(), +// [ExcerptRange { +// context: Point::new(0, 0)..Point::new(2, 0), +// primary: None, +// }], +// cx, +// ); +// let buffer_2_excerpts = multibuffer.push_excerpts( +// buffer_2.clone(), +// [ExcerptRange { +// context: Point::new(0, 1)..Point::new(2, 1), +// primary: None, +// }], +// cx, +// ); +// (buffer_1_excerpts, buffer_2_excerpts) +// }); + +// assert!(!buffer_1_excerpts.is_empty()); +// assert!(!buffer_2_excerpts.is_empty()); + +// deterministic.run_until_parked(); +// cx.foreground().run_until_parked(); +// let editor = cx +// .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx)) +// .root(cx); +// let editor_edited = Arc::new(AtomicBool::new(false)); +// let fake_server = fake_servers.next().await.unwrap(); +// let closure_editor_edited = Arc::clone(&editor_edited); +// fake_server +// .handle_request::(move |params, _| { +// let task_editor_edited = Arc::clone(&closure_editor_edited); +// async move { +// let hint_text = if params.text_document.uri +// == lsp::Url::from_file_path("/a/main.rs").unwrap() +// { +// "main hint" +// } else if params.text_document.uri +// == lsp::Url::from_file_path("/a/other.rs").unwrap() +// { +// "other hint" +// } else { +// panic!("unexpected uri: {:?}", params.text_document.uri); +// }; + +// let positions = [ +// lsp::Position::new(0, 2), +// lsp::Position::new(4, 2), +// lsp::Position::new(22, 2), +// lsp::Position::new(44, 2), +// lsp::Position::new(56, 2), +// lsp::Position::new(67, 2), +// ]; +// let out_of_range_hint = lsp::InlayHint { +// position: lsp::Position::new( +// params.range.start.line + 99, +// params.range.start.character + 99, +// ), +// label: lsp::InlayHintLabel::String( +// "out of excerpt range, should be ignored".to_string(), +// ), +// kind: None, +// text_edits: None, +// tooltip: None, +// padding_left: None, +// padding_right: None, +// data: None, +// }; + +// let edited = task_editor_edited.load(Ordering::Acquire); +// Ok(Some( +// std::iter::once(out_of_range_hint) +// .chain(positions.into_iter().enumerate().map(|(i, position)| { +// lsp::InlayHint { +// position, +// label: lsp::InlayHintLabel::String(format!( +// "{hint_text}{} #{i}", +// if edited { "(edited)" } else { "" }, +// )), +// kind: None, +// text_edits: None, +// tooltip: None, +// padding_left: None, +// padding_right: None, +// data: None, +// } +// })) +// .collect(), +// )) +// } +// }) +// .next() +// .await; +// cx.foreground().run_until_parked(); + +// editor.update(cx, |editor, cx| { +// assert_eq!( +// vec!["main hint #0".to_string(), "other hint #0".to_string()], +// cached_hint_labels(editor), +// "Cache should update for both excerpts despite hints display was disabled" +// ); +// assert!( +// visible_hint_labels(editor, cx).is_empty(), +// "All hints are disabled and should not be shown despite being present in the cache" +// ); +// assert_eq!( +// editor.inlay_hint_cache().version, +// 2, +// "Cache should update once per excerpt query" +// ); +// }); + +// editor.update(cx, |editor, cx| { +// editor.buffer().update(cx, |multibuffer, cx| { +// multibuffer.remove_excerpts(buffer_2_excerpts, cx) +// }) +// }); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// assert_eq!( +// vec!["main hint #0".to_string()], +// cached_hint_labels(editor), +// "For the removed excerpt, should clean corresponding cached hints" +// ); +// assert!( +// visible_hint_labels(editor, cx).is_empty(), +// "All hints are disabled and should not be shown despite being present in the cache" +// ); +// assert_eq!( +// editor.inlay_hint_cache().version, +// 3, +// "Excerpt removal should trigger a cache update" +// ); +// }); + +// update_test_language_settings(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: true, +// show_type_hints: true, +// show_parameter_hints: true, +// show_other_hints: true, +// }) +// }); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// let expected_hints = vec!["main hint #0".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Hint display settings change should not change the cache" +// ); +// assert_eq!( +// expected_hints, +// visible_hint_labels(editor, cx), +// "Settings change should make cached hints visible" +// ); +// assert_eq!( +// editor.inlay_hint_cache().version, +// 4, +// "Settings change should trigger a cache update" +// ); +// }); +// } + +// #[gpui::test] +// async fn test_inside_char_boundary_range_hints(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: true, +// show_type_hints: true, +// show_parameter_hints: true, +// show_other_hints: true, +// }) +// }); + +// let mut language = Language::new( +// LanguageConfig { +// name: "Rust".into(), +// path_suffixes: vec!["rs".to_string()], +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ); +// let mut fake_servers = language +// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { +// capabilities: lsp::ServerCapabilities { +// inlay_hint_provider: Some(lsp::OneOf::Left(true)), +// ..Default::default() +// }, +// ..Default::default() +// })) +// .await; +// let fs = FakeFs::new(cx.background()); +// fs.insert_tree( +// "/a", +// json!({ +// "main.rs": format!(r#"fn main() {{\n{}\n}}"#, format!("let i = {};\n", "√".repeat(10)).repeat(500)), +// "other.rs": "// Test file", +// }), +// ) +// .await; +// let project = Project::test(fs, ["/a".as_ref()], cx).await; +// project.update(cx, |project, _| project.languages().add(Arc::new(language))); +// let workspace = cx +// .add_window(|cx| Workspace::test_new(project.clone(), cx)) +// .root(cx); +// let worktree_id = workspace.update(cx, |workspace, cx| { +// workspace.project().read_with(cx, |project, cx| { +// project.worktrees(cx).next().unwrap().read(cx).id() +// }) +// }); + +// let _buffer = project +// .update(cx, |project, cx| { +// project.open_local_buffer("/a/main.rs", cx) +// }) +// .await +// .unwrap(); +// cx.foreground().run_until_parked(); +// cx.foreground().start_waiting(); +// let fake_server = fake_servers.next().await.unwrap(); +// let editor = workspace +// .update(cx, |workspace, cx| { +// workspace.open_path((worktree_id, "main.rs"), None, true, cx) +// }) +// .await +// .unwrap() +// .downcast::() +// .unwrap(); +// let lsp_request_count = Arc::new(AtomicU32::new(0)); +// let closure_lsp_request_count = Arc::clone(&lsp_request_count); +// fake_server +// .handle_request::(move |params, _| { +// let task_lsp_request_count = Arc::clone(&closure_lsp_request_count); +// async move { +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path("/a/main.rs").unwrap(), +// ); +// let query_start = params.range.start; +// let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1; +// Ok(Some(vec![lsp::InlayHint { +// position: query_start, +// label: lsp::InlayHintLabel::String(i.to_string()), +// kind: None, +// text_edits: None, +// tooltip: None, +// padding_left: None, +// padding_right: None, +// data: None, +// }])) +// } +// }) +// .next() +// .await; + +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// editor.change_selections(None, cx, |s| { +// s.select_ranges([Point::new(10, 0)..Point::new(10, 0)]) +// }) +// }); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// let expected_hints = vec!["1".to_string()]; +// assert_eq!(expected_hints, cached_hint_labels(editor)); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!(editor.inlay_hint_cache().version, 1); +// }); +// } + +// #[gpui::test] +// async fn test_toggle_inlay_hints(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: false, +// show_type_hints: true, +// show_parameter_hints: true, +// show_other_hints: true, +// }) +// }); + +// let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; + +// editor.update(cx, |editor, cx| { +// editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx) +// }); +// cx.foreground().start_waiting(); +// let lsp_request_count = Arc::new(AtomicU32::new(0)); +// let closure_lsp_request_count = Arc::clone(&lsp_request_count); +// fake_server +// .handle_request::(move |params, _| { +// let task_lsp_request_count = Arc::clone(&closure_lsp_request_count); +// async move { +// assert_eq!( +// params.text_document.uri, +// lsp::Url::from_file_path(file_with_hints).unwrap(), +// ); + +// let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1; +// Ok(Some(vec![lsp::InlayHint { +// position: lsp::Position::new(0, i), +// label: lsp::InlayHintLabel::String(i.to_string()), +// kind: None, +// text_edits: None, +// tooltip: None, +// padding_left: None, +// padding_right: None, +// data: None, +// }])) +// } +// }) +// .next() +// .await; +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// let expected_hints = vec!["1".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Should display inlays after toggle despite them disabled in settings" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!( +// editor.inlay_hint_cache().version, +// 1, +// "First toggle should be cache's first update" +// ); +// }); + +// editor.update(cx, |editor, cx| { +// editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx) +// }); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// assert!( +// cached_hint_labels(editor).is_empty(), +// "Should clear hints after 2nd toggle" +// ); +// assert!(visible_hint_labels(editor, cx).is_empty()); +// assert_eq!(editor.inlay_hint_cache().version, 2); +// }); + +// update_test_language_settings(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: true, +// show_type_hints: true, +// show_parameter_hints: true, +// show_other_hints: true, +// }) +// }); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// let expected_hints = vec!["2".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Should query LSP hints for the 2nd time after enabling hints in settings" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!(editor.inlay_hint_cache().version, 3); +// }); + +// editor.update(cx, |editor, cx| { +// editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx) +// }); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// assert!( +// cached_hint_labels(editor).is_empty(), +// "Should clear hints after enabling in settings and a 3rd toggle" +// ); +// assert!(visible_hint_labels(editor, cx).is_empty()); +// assert_eq!(editor.inlay_hint_cache().version, 4); +// }); + +// editor.update(cx, |editor, cx| { +// editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx) +// }); +// cx.foreground().run_until_parked(); +// editor.update(cx, |editor, cx| { +// let expected_hints = vec!["3".to_string()]; +// assert_eq!( +// expected_hints, +// cached_hint_labels(editor), +// "Should query LSP hints for the 3rd time after enabling hints in settings and toggling them back on" +// ); +// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); +// assert_eq!(editor.inlay_hint_cache().version, 5); +// }); +// } + +// pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) { +// cx.foreground().forbid_parking(); + +// cx.update(|cx| { +// cx.set_global(SettingsStore::test(cx)); +// theme::init(cx); +// client::init_settings(cx); +// language::init(cx); +// Project::init_settings(cx); +// workspace::init_settings(cx); +// crate::init(cx); +// }); + +// update_test_language_settings(cx, f); +// } + +// async fn prepare_test_objects( +// cx: &mut TestAppContext, +// ) -> (&'static str, ViewHandle, FakeLanguageServer) { +// let mut language = Language::new( +// LanguageConfig { +// name: "Rust".into(), +// path_suffixes: vec!["rs".to_string()], +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ); +// let mut fake_servers = language +// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { +// capabilities: lsp::ServerCapabilities { +// inlay_hint_provider: Some(lsp::OneOf::Left(true)), +// ..Default::default() +// }, +// ..Default::default() +// })) +// .await; + +// let fs = FakeFs::new(cx.background()); +// fs.insert_tree( +// "/a", +// json!({ +// "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out", +// "other.rs": "// Test file", +// }), +// ) +// .await; + +// let project = Project::test(fs, ["/a".as_ref()], cx).await; +// project.update(cx, |project, _| project.languages().add(Arc::new(language))); +// let workspace = cx +// .add_window(|cx| Workspace::test_new(project.clone(), cx)) +// .root(cx); +// let worktree_id = workspace.update(cx, |workspace, cx| { +// workspace.project().read_with(cx, |project, cx| { +// project.worktrees(cx).next().unwrap().read(cx).id() +// }) +// }); + +// let _buffer = project +// .update(cx, |project, cx| { +// project.open_local_buffer("/a/main.rs", cx) +// }) +// .await +// .unwrap(); +// cx.foreground().run_until_parked(); +// cx.foreground().start_waiting(); +// let fake_server = fake_servers.next().await.unwrap(); +// let editor = workspace +// .update(cx, |workspace, cx| { +// workspace.open_path((worktree_id, "main.rs"), None, true, cx) +// }) +// .await +// .unwrap() +// .downcast::() +// .unwrap(); + +// editor.update(cx, |editor, cx| { +// assert!(cached_hint_labels(editor).is_empty()); +// assert!(visible_hint_labels(editor, cx).is_empty()); +// assert_eq!(editor.inlay_hint_cache().version, 0); +// }); + +// ("/a/main.rs", editor, fake_server) +// } + +// pub fn cached_hint_labels(editor: &Editor) -> Vec { +// let mut labels = Vec::new(); +// for (_, excerpt_hints) in &editor.inlay_hint_cache().hints { +// let excerpt_hints = excerpt_hints.read(); +// for id in &excerpt_hints.ordered_hints { +// labels.push(excerpt_hints.hints_by_id[id].text()); +// } +// } + +// labels.sort(); +// labels +// } + +// pub fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec { +// let mut hints = editor +// .visible_inlay_hints(cx) +// .into_iter() +// .map(|hint| hint.text.to_string()) +// .collect::>(); +// hints.sort(); +// hints +// } +// } diff --git a/crates/editor2/src/items.rs b/crates/editor2/src/items.rs new file mode 100644 index 0000000000..c439adcc44 --- /dev/null +++ b/crates/editor2/src/items.rs @@ -0,0 +1,1339 @@ +use crate::{ + display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition, + movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, + Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _, +}; +use anyhow::{anyhow, Context, Result}; +use collections::HashSet; +use futures::future::try_join_all; +use gpui::{ + div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, FocusHandle, Model, + ParentElement, Pixels, SharedString, Styled, Subscription, Task, View, ViewContext, + VisualContext, WeakView, +}; +use language::{ + proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point, + SelectionGoal, +}; +use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath}; +use rpc::proto::{self, update_view, PeerId}; +use smallvec::SmallVec; +use std::{ + borrow::Cow, + cmp::{self, Ordering}, + iter, + ops::Range, + path::{Path, PathBuf}, + sync::Arc, +}; +use text::Selection; +use theme::{ActiveTheme, ThemeVariant}; +use util::{paths::PathExt, ResultExt, TryFutureExt}; +use workspace::item::{BreadcrumbText, FollowableItemHandle}; +use workspace::{ + item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem}, + searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}, + ItemId, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId, +}; + +pub const MAX_TAB_TITLE_LEN: usize = 24; + +impl FollowableItem for Editor { + fn remote_id(&self) -> Option { + self.remote_id + } + + fn from_state_proto( + pane: View, + workspace: View, + remote_id: ViewId, + state: &mut Option, + cx: &mut AppContext, + ) -> Option>>> { + todo!() + } + // let project = workspace.read(cx).project().to_owned(); + // let Some(proto::view::Variant::Editor(_)) = state else { + // return None; + // }; + // let Some(proto::view::Variant::Editor(state)) = state.take() else { + // unreachable!() + // }; + + // let client = project.read(cx).client(); + // let replica_id = project.read(cx).replica_id(); + // let buffer_ids = state + // .excerpts + // .iter() + // .map(|excerpt| excerpt.buffer_id) + // .collect::>(); + // let buffers = project.update(cx, |project, cx| { + // buffer_ids + // .iter() + // .map(|id| project.open_buffer_by_id(*id, cx)) + // .collect::>() + // }); + + // let pane = pane.downgrade(); + // Some(cx.spawn(|mut cx| async move { + // let mut buffers = futures::future::try_join_all(buffers).await?; + // let editor = pane.read_with(&cx, |pane, cx| { + // let mut editors = pane.items_of_type::(); + // editors.find(|editor| { + // let ids_match = editor.remote_id(&client, cx) == Some(remote_id); + // let singleton_buffer_matches = state.singleton + // && buffers.first() + // == editor.read(cx).buffer.read(cx).as_singleton().as_ref(); + // ids_match || singleton_buffer_matches + // }) + // })?; + + // let editor = if let Some(editor) = editor { + // editor + // } else { + // pane.update(&mut cx, |_, cx| { + // let multibuffer = cx.add_model(|cx| { + // let mut multibuffer; + // if state.singleton && buffers.len() == 1 { + // multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx) + // } else { + // multibuffer = MultiBuffer::new(replica_id); + // let mut excerpts = state.excerpts.into_iter().peekable(); + // while let Some(excerpt) = excerpts.peek() { + // let buffer_id = excerpt.buffer_id; + // let buffer_excerpts = iter::from_fn(|| { + // let excerpt = excerpts.peek()?; + // (excerpt.buffer_id == buffer_id) + // .then(|| excerpts.next().unwrap()) + // }); + // let buffer = + // buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id); + // if let Some(buffer) = buffer { + // multibuffer.push_excerpts( + // buffer.clone(), + // buffer_excerpts.filter_map(deserialize_excerpt_range), + // cx, + // ); + // } + // } + // }; + + // if let Some(title) = &state.title { + // multibuffer = multibuffer.with_title(title.clone()) + // } + + // multibuffer + // }); + + // cx.add_view(|cx| { + // let mut editor = + // Editor::for_multibuffer(multibuffer, Some(project.clone()), cx); + // editor.remote_id = Some(remote_id); + // editor + // }) + // })? + // }; + + // update_editor_from_message( + // editor.downgrade(), + // project, + // proto::update_view::Editor { + // selections: state.selections, + // pending_selection: state.pending_selection, + // scroll_top_anchor: state.scroll_top_anchor, + // scroll_x: state.scroll_x, + // scroll_y: state.scroll_y, + // ..Default::default() + // }, + // &mut cx, + // ) + // .await?; + + // Ok(editor) + // })) + // } + + fn set_leader_peer_id(&mut self, leader_peer_id: Option, cx: &mut ViewContext) { + self.leader_peer_id = leader_peer_id; + if self.leader_peer_id.is_some() { + self.buffer.update(cx, |buffer, cx| { + buffer.remove_active_selections(cx); + }); + } else { + self.buffer.update(cx, |buffer, cx| { + if self.focused { + buffer.set_active_selections( + &self.selections.disjoint_anchors(), + self.selections.line_mode, + self.cursor_shape, + cx, + ); + } + }); + } + cx.notify(); + } + + fn to_state_proto(&self, cx: &AppContext) -> Option { + let buffer = self.buffer.read(cx); + let scroll_anchor = self.scroll_manager.anchor(); + let excerpts = buffer + .read(cx) + .excerpts() + .map(|(id, buffer, range)| proto::Excerpt { + id: id.to_proto(), + buffer_id: buffer.remote_id(), + context_start: Some(serialize_text_anchor(&range.context.start)), + context_end: Some(serialize_text_anchor(&range.context.end)), + primary_start: range + .primary + .as_ref() + .map(|range| serialize_text_anchor(&range.start)), + primary_end: range + .primary + .as_ref() + .map(|range| serialize_text_anchor(&range.end)), + }) + .collect(); + + Some(proto::view::Variant::Editor(proto::view::Editor { + singleton: buffer.is_singleton(), + title: (!buffer.is_singleton()).then(|| buffer.title(cx).into()), + excerpts, + scroll_top_anchor: Some(serialize_anchor(&scroll_anchor.anchor)), + scroll_x: scroll_anchor.offset.x, + scroll_y: scroll_anchor.offset.y, + selections: self + .selections + .disjoint_anchors() + .iter() + .map(serialize_selection) + .collect(), + pending_selection: self + .selections + .pending_anchor() + .as_ref() + .map(serialize_selection), + })) + } + + fn add_event_to_update_proto( + &self, + event: &Self::Event, + update: &mut Option, + cx: &AppContext, + ) -> bool { + let update = + update.get_or_insert_with(|| proto::update_view::Variant::Editor(Default::default())); + + match update { + proto::update_view::Variant::Editor(update) => match event { + Event::ExcerptsAdded { + buffer, + predecessor, + excerpts, + } => { + let buffer_id = buffer.read(cx).remote_id(); + let mut excerpts = excerpts.iter(); + if let Some((id, range)) = excerpts.next() { + update.inserted_excerpts.push(proto::ExcerptInsertion { + previous_excerpt_id: Some(predecessor.to_proto()), + excerpt: serialize_excerpt(buffer_id, id, range), + }); + update.inserted_excerpts.extend(excerpts.map(|(id, range)| { + proto::ExcerptInsertion { + previous_excerpt_id: None, + excerpt: serialize_excerpt(buffer_id, id, range), + } + })) + } + true + } + Event::ExcerptsRemoved { ids } => { + update + .deleted_excerpts + .extend(ids.iter().map(ExcerptId::to_proto)); + true + } + Event::ScrollPositionChanged { .. } => { + let scroll_anchor = self.scroll_manager.anchor(); + update.scroll_top_anchor = Some(serialize_anchor(&scroll_anchor.anchor)); + update.scroll_x = scroll_anchor.offset.x; + update.scroll_y = scroll_anchor.offset.y; + true + } + Event::SelectionsChanged { .. } => { + update.selections = self + .selections + .disjoint_anchors() + .iter() + .map(serialize_selection) + .collect(); + update.pending_selection = self + .selections + .pending_anchor() + .as_ref() + .map(serialize_selection); + true + } + _ => false, + }, + } + } + + fn apply_update_proto( + &mut self, + project: &Model, + message: update_view::Variant, + cx: &mut ViewContext, + ) -> Task> { + let update_view::Variant::Editor(message) = message; + let project = project.clone(); + cx.spawn(|this, mut cx| async move { + update_editor_from_message(this, project, message, &mut cx).await + }) + } + + fn should_unfollow_on_event(event: &Self::Event, _: &AppContext) -> bool { + match event { + Event::Edited => true, + Event::SelectionsChanged { local } => *local, + Event::ScrollPositionChanged { local, .. } => *local, + _ => false, + } + } + + fn is_project_item(&self, _cx: &AppContext) -> bool { + true + } +} + +async fn update_editor_from_message( + this: WeakView, + project: Model, + message: proto::update_view::Editor, + cx: &mut AsyncAppContext, +) -> Result<()> { + todo!() +} +// Previous implementation of the above +// // Open all of the buffers of which excerpts were added to the editor. +// let inserted_excerpt_buffer_ids = message +// .inserted_excerpts +// .iter() +// .filter_map(|insertion| Some(insertion.excerpt.as_ref()?.buffer_id)) +// .collect::>(); +// let inserted_excerpt_buffers = project.update(cx, |project, cx| { +// inserted_excerpt_buffer_ids +// .into_iter() +// .map(|id| project.open_buffer_by_id(id, cx)) +// .collect::>() +// })?; +// let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?; + +// // Update the editor's excerpts. +// this.update(cx, |editor, cx| { +// editor.buffer.update(cx, |multibuffer, cx| { +// let mut removed_excerpt_ids = message +// .deleted_excerpts +// .into_iter() +// .map(ExcerptId::from_proto) +// .collect::>(); +// removed_excerpt_ids.sort_by({ +// let multibuffer = multibuffer.read(cx); +// move |a, b| a.cmp(&b, &multibuffer) +// }); + +// let mut insertions = message.inserted_excerpts.into_iter().peekable(); +// while let Some(insertion) = insertions.next() { +// let Some(excerpt) = insertion.excerpt else { +// continue; +// }; +// let Some(previous_excerpt_id) = insertion.previous_excerpt_id else { +// continue; +// }; +// let buffer_id = excerpt.buffer_id; +// let Some(buffer) = project.read(cx).buffer_for_id(buffer_id) else { +// continue; +// }; + +// let adjacent_excerpts = iter::from_fn(|| { +// let insertion = insertions.peek()?; +// if insertion.previous_excerpt_id.is_none() +// && insertion.excerpt.as_ref()?.buffer_id == buffer_id +// { +// insertions.next()?.excerpt +// } else { +// None +// } +// }); + +// multibuffer.insert_excerpts_with_ids_after( +// ExcerptId::from_proto(previous_excerpt_id), +// buffer, +// [excerpt] +// .into_iter() +// .chain(adjacent_excerpts) +// .filter_map(|excerpt| { +// Some(( +// ExcerptId::from_proto(excerpt.id), +// deserialize_excerpt_range(excerpt)?, +// )) +// }), +// cx, +// ); +// } + +// multibuffer.remove_excerpts(removed_excerpt_ids, cx); +// }); +// })?; + +// // Deserialize the editor state. +// let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| { +// let buffer = editor.buffer.read(cx).read(cx); +// let selections = message +// .selections +// .into_iter() +// .filter_map(|selection| deserialize_selection(&buffer, selection)) +// .collect::>(); +// let pending_selection = message +// .pending_selection +// .and_then(|selection| deserialize_selection(&buffer, selection)); +// let scroll_top_anchor = message +// .scroll_top_anchor +// .and_then(|anchor| deserialize_anchor(&buffer, anchor)); +// anyhow::Ok((selections, pending_selection, scroll_top_anchor)) +// })??; + +// // Wait until the buffer has received all of the operations referenced by +// // the editor's new state. +// this.update(cx, |editor, cx| { +// editor.buffer.update(cx, |buffer, cx| { +// buffer.wait_for_anchors( +// selections +// .iter() +// .chain(pending_selection.as_ref()) +// .flat_map(|selection| [selection.start, selection.end]) +// .chain(scroll_top_anchor), +// cx, +// ) +// }) +// })? +// .await?; + +// // Update the editor's state. +// this.update(cx, |editor, cx| { +// if !selections.is_empty() || pending_selection.is_some() { +// editor.set_selections_from_remote(selections, pending_selection, cx); +// editor.request_autoscroll_remotely(Autoscroll::newest(), cx); +// } else if let Some(scroll_top_anchor) = scroll_top_anchor { +// editor.set_scroll_anchor_remote( +// ScrollAnchor { +// anchor: scroll_top_anchor, +// offset: point(message.scroll_x, message.scroll_y), +// }, +// cx, +// ); +// } +// })?; +// Ok(()) +// } + +fn serialize_excerpt( + buffer_id: u64, + id: &ExcerptId, + range: &ExcerptRange, +) -> Option { + Some(proto::Excerpt { + id: id.to_proto(), + buffer_id, + context_start: Some(serialize_text_anchor(&range.context.start)), + context_end: Some(serialize_text_anchor(&range.context.end)), + primary_start: range + .primary + .as_ref() + .map(|r| serialize_text_anchor(&r.start)), + primary_end: range + .primary + .as_ref() + .map(|r| serialize_text_anchor(&r.end)), + }) +} + +fn serialize_selection(selection: &Selection) -> proto::Selection { + proto::Selection { + id: selection.id as u64, + start: Some(serialize_anchor(&selection.start)), + end: Some(serialize_anchor(&selection.end)), + reversed: selection.reversed, + } +} + +fn serialize_anchor(anchor: &Anchor) -> proto::EditorAnchor { + proto::EditorAnchor { + excerpt_id: anchor.excerpt_id.to_proto(), + anchor: Some(serialize_text_anchor(&anchor.text_anchor)), + } +} + +fn deserialize_excerpt_range(excerpt: proto::Excerpt) -> Option> { + let context = { + let start = language::proto::deserialize_anchor(excerpt.context_start?)?; + let end = language::proto::deserialize_anchor(excerpt.context_end?)?; + start..end + }; + let primary = excerpt + .primary_start + .zip(excerpt.primary_end) + .and_then(|(start, end)| { + let start = language::proto::deserialize_anchor(start)?; + let end = language::proto::deserialize_anchor(end)?; + Some(start..end) + }); + Some(ExcerptRange { context, primary }) +} + +fn deserialize_selection( + buffer: &MultiBufferSnapshot, + selection: proto::Selection, +) -> Option> { + Some(Selection { + id: selection.id as usize, + start: deserialize_anchor(buffer, selection.start?)?, + end: deserialize_anchor(buffer, selection.end?)?, + reversed: selection.reversed, + goal: SelectionGoal::None, + }) +} + +fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor) -> Option { + let excerpt_id = ExcerptId::from_proto(anchor.excerpt_id); + Some(Anchor { + excerpt_id, + text_anchor: language::proto::deserialize_anchor(anchor.anchor?)?, + buffer_id: buffer.buffer_id_for_excerpt(excerpt_id), + }) +} + +impl Item for Editor { + fn focus_handle(&self) -> FocusHandle { + self.focus_handle.clone() + } + + fn navigate(&mut self, data: Box, cx: &mut ViewContext) -> bool { + todo!(); + // if let Ok(data) = data.downcast::() { + // let newest_selection = self.selections.newest::(cx); + // let buffer = self.buffer.read(cx).read(cx); + // let offset = if buffer.can_resolve(&data.cursor_anchor) { + // data.cursor_anchor.to_point(&buffer) + // } else { + // buffer.clip_point(data.cursor_position, Bias::Left) + // }; + + // let mut scroll_anchor = data.scroll_anchor; + // if !buffer.can_resolve(&scroll_anchor.anchor) { + // scroll_anchor.anchor = buffer.anchor_before( + // buffer.clip_point(Point::new(data.scroll_top_row, 0), Bias::Left), + // ); + // } + + // drop(buffer); + + // if newest_selection.head() == offset { + // false + // } else { + // let nav_history = self.nav_history.take(); + // self.set_scroll_anchor(scroll_anchor, cx); + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select_ranges([offset..offset]) + // }); + // self.nav_history = nav_history; + // true + // } + // } else { + // false + // } + } + + fn tab_tooltip_text(&self, cx: &AppContext) -> Option { + let file_path = self + .buffer() + .read(cx) + .as_singleton()? + .read(cx) + .file() + .and_then(|f| f.as_local())? + .abs_path(cx); + + let file_path = file_path.compact().to_string_lossy().to_string(); + + Some(file_path.into()) + } + + fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option { + let path = path_for_buffer(&self.buffer, detail, true, cx)?; + Some(path.to_string_lossy().to_string().into()) + } + + fn tab_content(&self, detail: Option, cx: &AppContext) -> AnyElement { + let theme = cx.theme(); + AnyElement::new( + div() + .flex() + .flex_row() + .items_center() + .bg(gpui::white()) + .text_color(gpui::white()) + .child(self.title(cx).to_string()) + .children(detail.and_then(|detail| { + let path = path_for_buffer(&self.buffer, detail, false, cx)?; + let description = path.to_string_lossy(); + Some(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN)) + })), + ) + } + + fn for_each_project_item( + &self, + cx: &AppContext, + f: &mut dyn FnMut(EntityId, &dyn project::Item), + ) { + self.buffer + .read(cx) + .for_each_buffer(|buffer| f(buffer.entity_id(), buffer.read(cx))); + } + + fn is_singleton(&self, cx: &AppContext) -> bool { + self.buffer.read(cx).is_singleton() + } + + fn clone_on_split( + &self, + _workspace_id: WorkspaceId, + cx: &mut ViewContext, + ) -> Option> + where + Self: Sized, + { + Some(cx.build_view(|cx| self.clone(cx))) + } + + fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext) { + self.nav_history = Some(history); + } + + fn deactivated(&mut self, cx: &mut ViewContext) { + let selection = self.selections.newest_anchor(); + todo!() + // self.push_to_nav_history(selection.head(), None, cx); + } + + fn workspace_deactivated(&mut self, cx: &mut ViewContext) { + hide_link_definition(self, cx); + self.link_go_to_definition_state.last_trigger_point = None; + } + + fn is_dirty(&self, cx: &AppContext) -> bool { + self.buffer().read(cx).read(cx).is_dirty() + } + + fn has_conflict(&self, cx: &AppContext) -> bool { + self.buffer().read(cx).read(cx).has_conflict() + } + + fn can_save(&self, cx: &AppContext) -> bool { + let buffer = &self.buffer().read(cx); + if let Some(buffer) = buffer.as_singleton() { + buffer.read(cx).project_path(cx).is_some() + } else { + true + } + } + + fn save(&mut self, project: Model, cx: &mut ViewContext) -> Task> { + self.report_editor_event("save", None, cx); + let format = self.perform_format(project.clone(), FormatTrigger::Save, cx); + let buffers = self.buffer().clone().read(cx).all_buffers(); + cx.spawn(|_, mut cx| async move { + format.await?; + + if buffers.len() == 1 { + project + .update(&mut cx, |project, cx| project.save_buffers(buffers, cx))? + .await?; + } else { + // For multi-buffers, only save those ones that contain changes. For clean buffers + // we simulate saving by calling `Buffer::did_save`, so that language servers or + // other downstream listeners of save events get notified. + let (dirty_buffers, clean_buffers) = buffers.into_iter().partition(|buffer| { + buffer + .update(&mut cx, |buffer, _| { + buffer.is_dirty() || buffer.has_conflict() + }) + .unwrap_or(false) + }); + + project + .update(&mut cx, |project, cx| { + project.save_buffers(dirty_buffers, cx) + })? + .await?; + for buffer in clean_buffers { + buffer.update(&mut cx, |buffer, cx| { + let version = buffer.saved_version().clone(); + let fingerprint = buffer.saved_version_fingerprint(); + let mtime = buffer.saved_mtime(); + buffer.did_save(version, fingerprint, mtime, cx); + }); + } + } + + Ok(()) + }) + } + + fn save_as( + &mut self, + project: Model, + abs_path: PathBuf, + cx: &mut ViewContext, + ) -> Task> { + let buffer = self + .buffer() + .read(cx) + .as_singleton() + .expect("cannot call save_as on an excerpt list"); + + let file_extension = abs_path + .extension() + .map(|a| a.to_string_lossy().to_string()); + self.report_editor_event("save", file_extension, cx); + + project.update(cx, |project, cx| { + project.save_buffer_as(buffer, abs_path, cx) + }) + } + + fn reload(&mut self, project: Model, cx: &mut ViewContext) -> Task> { + let buffer = self.buffer().clone(); + let buffers = self.buffer.read(cx).all_buffers(); + let reload_buffers = + project.update(cx, |project, cx| project.reload_buffers(buffers, true, cx)); + cx.spawn(|this, mut cx| async move { + let transaction = reload_buffers.log_err().await; + this.update(&mut cx, |editor, cx| { + editor.request_autoscroll(Autoscroll::fit(), cx) + })?; + buffer.update(&mut cx, |buffer, cx| { + if let Some(transaction) = transaction { + if !buffer.is_singleton() { + buffer.push_transaction(&transaction.0, cx); + } + } + }); + Ok(()) + }) + } + + fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { + let mut result = SmallVec::new(); + match event { + Event::Closed => result.push(ItemEvent::CloseItem), + Event::Saved | Event::TitleChanged => { + result.push(ItemEvent::UpdateTab); + result.push(ItemEvent::UpdateBreadcrumbs); + } + Event::Reparsed => { + result.push(ItemEvent::UpdateBreadcrumbs); + } + Event::SelectionsChanged { local } if *local => { + result.push(ItemEvent::UpdateBreadcrumbs); + } + Event::DirtyChanged => { + result.push(ItemEvent::UpdateTab); + } + Event::BufferEdited => { + result.push(ItemEvent::Edit); + result.push(ItemEvent::UpdateBreadcrumbs); + } + _ => {} + } + result + } + + fn as_searchable(&self, handle: &View) -> Option> { + Some(Box::new(handle.clone())) + } + + fn pixel_position_of_cursor(&self, _: &AppContext) -> Option> { + self.pixel_position_of_newest_cursor + } + + fn breadcrumb_location(&self) -> ToolbarItemLocation { + ToolbarItemLocation::PrimaryLeft { flex: None } + } + + fn breadcrumbs(&self, variant: &ThemeVariant, cx: &AppContext) -> Option> { + todo!(); + // let cursor = self.selections.newest_anchor().head(); + // let multibuffer = &self.buffer().read(cx); + // let (buffer_id, symbols) = + // multibuffer.symbols_containing(cursor, Some(&theme.editor.syntax), cx)?; + // let buffer = multibuffer.buffer(buffer_id)?; + + // let buffer = buffer.read(cx); + // let filename = buffer + // .snapshot() + // .resolve_file_path( + // cx, + // self.project + // .as_ref() + // .map(|project| project.read(cx).visible_worktrees(cx).count() > 1) + // .unwrap_or_default(), + // ) + // .map(|path| path.to_string_lossy().to_string()) + // .unwrap_or_else(|| "untitled".to_string()); + + // let mut breadcrumbs = vec![BreadcrumbText { + // text: filename, + // highlights: None, + // }]; + // breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText { + // text: symbol.text, + // highlights: Some(symbol.highlight_ranges), + // })); + // Some(breadcrumbs) + } + + fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext) { + let workspace_id = workspace.database_id(); + let item_id = cx.view().entity_id().as_u64() as ItemId; + self.workspace = Some((workspace.weak_handle(), workspace.database_id())); + + fn serialize( + buffer: Model, + workspace_id: WorkspaceId, + item_id: ItemId, + cx: &mut AppContext, + ) { + if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) { + let path = file.abs_path(cx); + + cx.background_executor() + .spawn(async move { + DB.save_path(item_id, workspace_id, path.clone()) + .await + .log_err() + }) + .detach(); + } + } + + if let Some(buffer) = self.buffer().read(cx).as_singleton() { + serialize(buffer.clone(), workspace_id, item_id, cx); + + cx.subscribe(&buffer, |this, buffer, event, cx| { + if let Some((_, workspace_id)) = this.workspace.as_ref() { + if let language::Event::FileHandleChanged = event { + serialize( + buffer, + *workspace_id, + cx.view().entity_id().as_u64() as ItemId, + cx, + ); + } + } + }) + .detach(); + } + } + + fn serialized_item_kind() -> Option<&'static str> { + Some("Editor") + } + + fn deserialize( + project: Model, + _workspace: WeakView, + workspace_id: workspace::WorkspaceId, + item_id: ItemId, + cx: &mut ViewContext, + ) -> Task>> { + let project_item: Result<_> = project.update(cx, |project, cx| { + // Look up the path with this key associated, create a self with that path + let path = DB + .get_path(item_id, workspace_id)? + .context("No path stored for this editor")?; + + let (worktree, path) = project + .find_local_worktree(&path, cx) + .with_context(|| format!("No worktree for path: {path:?}"))?; + let project_path = ProjectPath { + worktree_id: worktree.read(cx).id(), + path: path.into(), + }; + + Ok(project.open_path(project_path, cx)) + }); + + project_item + .map(|project_item| { + cx.spawn(|pane, mut cx| async move { + let (_, project_item) = project_item.await?; + let buffer = project_item + .downcast::() + .map_err(|_| anyhow!("Project item at stored path was not a buffer"))?; + Ok(pane.update(&mut cx, |_, cx| { + cx.build_view(|cx| { + let mut editor = Editor::for_buffer(buffer, Some(project), cx); + + editor.read_scroll_position_from_db(item_id, workspace_id, cx); + editor + }) + })?) + }) + }) + .unwrap_or_else(|error| Task::ready(Err(error))) + } +} + +impl ProjectItem for Editor { + type Item = Buffer; + + fn for_project_item( + project: Model, + buffer: Model, + cx: &mut ViewContext, + ) -> Self { + Self::for_buffer(buffer, Some(project), cx) + } +} + +pub(crate) enum BufferSearchHighlights {} +impl SearchableItem for Editor { + type Match = Range; + + fn to_search_event( + &mut self, + event: &Self::Event, + _: &mut ViewContext, + ) -> Option { + match event { + Event::BufferEdited => Some(SearchEvent::MatchesInvalidated), + Event::SelectionsChanged { .. } => { + if self.selections.disjoint_anchors().len() == 1 { + Some(SearchEvent::ActiveMatchChanged) + } else { + None + } + } + _ => None, + } + } + + fn clear_matches(&mut self, cx: &mut ViewContext) { + todo!() + // self.clear_background_highlights::(cx); + } + + fn update_matches(&mut self, matches: Vec>, cx: &mut ViewContext) { + todo!() + // self.highlight_background::( + // matches, + // |theme| theme.search.match_background, + // cx, + // ); + } + + fn query_suggestion(&mut self, cx: &mut ViewContext) -> String { + let display_map = self.snapshot(cx).display_snapshot; + let selection = self.selections.newest::(cx); + if selection.start == selection.end { + let point = selection.start.to_display_point(&display_map); + let range = surrounding_word(&display_map, point); + let range = range.start.to_offset(&display_map, Bias::Left) + ..range.end.to_offset(&display_map, Bias::Right); + let text: String = display_map.buffer_snapshot.text_for_range(range).collect(); + if text.trim().is_empty() { + String::new() + } else { + text + } + } else { + display_map + .buffer_snapshot + .text_for_range(selection.start..selection.end) + .collect() + } + } + + fn activate_match( + &mut self, + index: usize, + matches: Vec>, + cx: &mut ViewContext, + ) { + todo!() + // self.unfold_ranges([matches[index].clone()], false, true, cx); + // let range = self.range_for_match(&matches[index]); + // self.change_selections(Some(Autoscroll::fit()), cx, |s| { + // s.select_ranges([range]); + // }) + } + + fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext) { + todo!() + // self.unfold_ranges(matches.clone(), false, false, cx); + // let mut ranges = Vec::new(); + // for m in &matches { + // ranges.push(self.range_for_match(&m)) + // } + // self.change_selections(None, cx, |s| s.select_ranges(ranges)); + } + fn replace( + &mut self, + identifier: &Self::Match, + query: &SearchQuery, + cx: &mut ViewContext, + ) { + let text = self.buffer.read(cx); + let text = text.snapshot(cx); + let text = text.text_for_range(identifier.clone()).collect::>(); + let text: Cow<_> = if text.len() == 1 { + text.first().cloned().unwrap().into() + } else { + let joined_chunks = text.join(""); + joined_chunks.into() + }; + + if let Some(replacement) = query.replacement_for(&text) { + self.transact(cx, |this, cx| { + this.edit([(identifier.clone(), Arc::from(&*replacement))], cx); + }); + } + } + fn match_index_for_direction( + &mut self, + matches: &Vec>, + current_index: usize, + direction: Direction, + count: usize, + cx: &mut ViewContext, + ) -> usize { + let buffer = self.buffer().read(cx).snapshot(cx); + let current_index_position = if self.selections.disjoint_anchors().len() == 1 { + self.selections.newest_anchor().head() + } else { + matches[current_index].start + }; + + let mut count = count % matches.len(); + if count == 0 { + return current_index; + } + match direction { + Direction::Next => { + if matches[current_index] + .start + .cmp(¤t_index_position, &buffer) + .is_gt() + { + count = count - 1 + } + + (current_index + count) % matches.len() + } + Direction::Prev => { + if matches[current_index] + .end + .cmp(¤t_index_position, &buffer) + .is_lt() + { + count = count - 1; + } + + if current_index >= count { + current_index - count + } else { + matches.len() - (count - current_index) + } + } + } + } + + fn find_matches( + &mut self, + query: Arc, + cx: &mut ViewContext, + ) -> Task>> { + let buffer = self.buffer().read(cx).snapshot(cx); + cx.background_executor().spawn(async move { + let mut ranges = Vec::new(); + if let Some((_, _, excerpt_buffer)) = buffer.as_singleton() { + ranges.extend( + query + .search(excerpt_buffer, None) + .await + .into_iter() + .map(|range| { + buffer.anchor_after(range.start)..buffer.anchor_before(range.end) + }), + ); + } else { + for excerpt in buffer.excerpt_boundaries_in_range(0..buffer.len()) { + let excerpt_range = excerpt.range.context.to_offset(&excerpt.buffer); + ranges.extend( + query + .search(&excerpt.buffer, Some(excerpt_range.clone())) + .await + .into_iter() + .map(|range| { + let start = excerpt + .buffer + .anchor_after(excerpt_range.start + range.start); + let end = excerpt + .buffer + .anchor_before(excerpt_range.start + range.end); + buffer.anchor_in_excerpt(excerpt.id.clone(), start) + ..buffer.anchor_in_excerpt(excerpt.id.clone(), end) + }), + ); + } + } + ranges + }) + } + + fn active_match_index( + &mut self, + matches: Vec>, + cx: &mut ViewContext, + ) -> Option { + active_match_index( + &matches, + &self.selections.newest_anchor().head(), + &self.buffer().read(cx).snapshot(cx), + ) + } +} + +pub fn active_match_index( + ranges: &[Range], + cursor: &Anchor, + buffer: &MultiBufferSnapshot, +) -> Option { + if ranges.is_empty() { + None + } else { + match ranges.binary_search_by(|probe| { + if probe.end.cmp(cursor, &*buffer).is_lt() { + Ordering::Less + } else if probe.start.cmp(cursor, &*buffer).is_gt() { + Ordering::Greater + } else { + Ordering::Equal + } + }) { + Ok(i) | Err(i) => Some(cmp::min(i, ranges.len() - 1)), + } + } +} + +pub struct CursorPosition { + position: Option, + selected_count: usize, + _observe_active_editor: Option, +} + +// impl Default for CursorPosition { +// fn default() -> Self { +// Self::new() +// } +// } + +// impl CursorPosition { +// pub fn new() -> Self { +// Self { +// position: None, +// selected_count: 0, +// _observe_active_editor: None, +// } +// } + +// fn update_position(&mut self, editor: View, cx: &mut ViewContext) { +// let editor = editor.read(cx); +// let buffer = editor.buffer().read(cx).snapshot(cx); + +// self.selected_count = 0; +// let mut last_selection: Option> = None; +// for selection in editor.selections.all::(cx) { +// self.selected_count += selection.end - selection.start; +// if last_selection +// .as_ref() +// .map_or(true, |last_selection| selection.id > last_selection.id) +// { +// last_selection = Some(selection); +// } +// } +// self.position = last_selection.map(|s| s.head().to_point(&buffer)); + +// cx.notify(); +// } +// } + +// impl Entity for CursorPosition { +// type Event = (); +// } + +// impl View for CursorPosition { +// fn ui_name() -> &'static str { +// "CursorPosition" +// } + +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// if let Some(position) = self.position { +// let theme = &theme::current(cx).workspace.status_bar; +// let mut text = format!( +// "{}{FILE_ROW_COLUMN_DELIMITER}{}", +// position.row + 1, +// position.column + 1 +// ); +// if self.selected_count > 0 { +// write!(text, " ({} selected)", self.selected_count).unwrap(); +// } +// Label::new(text, theme.cursor_position.clone()).into_any() +// } else { +// Empty::new().into_any() +// } +// } +// } + +// impl StatusItemView for CursorPosition { +// fn set_active_pane_item( +// &mut self, +// active_pane_item: Option<&dyn ItemHandle>, +// cx: &mut ViewContext, +// ) { +// if let Some(editor) = active_pane_item.and_then(|item| item.act_as::(cx)) { +// self._observe_active_editor = Some(cx.observe(&editor, Self::update_position)); +// self.update_position(editor, cx); +// } else { +// self.position = None; +// self._observe_active_editor = None; +// } + +// cx.notify(); +// } +// } + +fn path_for_buffer<'a>( + buffer: &Model, + height: usize, + include_filename: bool, + cx: &'a AppContext, +) -> Option> { + let file = buffer.read(cx).as_singleton()?.read(cx).file()?; + path_for_file(file.as_ref(), height, include_filename, cx) +} + +fn path_for_file<'a>( + file: &'a dyn language::File, + mut height: usize, + include_filename: bool, + cx: &'a AppContext, +) -> Option> { + // Ensure we always render at least the filename. + height += 1; + + let mut prefix = file.path().as_ref(); + while height > 0 { + if let Some(parent) = prefix.parent() { + prefix = parent; + height -= 1; + } else { + break; + } + } + + // Here we could have just always used `full_path`, but that is very + // allocation-heavy and so we try to use a `Cow` if we haven't + // traversed all the way up to the worktree's root. + if height > 0 { + let full_path = file.full_path(cx); + if include_filename { + Some(full_path.into()) + } else { + Some(full_path.parent()?.to_path_buf().into()) + } + } else { + let mut path = file.path().strip_prefix(prefix).ok()?; + if !include_filename { + path = path.parent()?; + } + Some(path.into()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use gpui::AppContext; + use std::{ + path::{Path, PathBuf}, + sync::Arc, + time::SystemTime, + }; + + #[gpui::test] + fn test_path_for_file(cx: &mut AppContext) { + let file = TestFile { + path: Path::new("").into(), + full_path: PathBuf::from(""), + }; + assert_eq!(path_for_file(&file, 0, false, cx), None); + } + + struct TestFile { + path: Arc, + full_path: PathBuf, + } + + impl language::File for TestFile { + fn path(&self) -> &Arc { + &self.path + } + + fn full_path(&self, _: &gpui::AppContext) -> PathBuf { + self.full_path.clone() + } + + fn as_local(&self) -> Option<&dyn language::LocalFile> { + unimplemented!() + } + + fn mtime(&self) -> SystemTime { + unimplemented!() + } + + fn file_name<'a>(&'a self, _: &'a gpui::AppContext) -> &'a std::ffi::OsStr { + unimplemented!() + } + + fn worktree_id(&self) -> usize { + 0 + } + + fn is_deleted(&self) -> bool { + unimplemented!() + } + + fn as_any(&self) -> &dyn std::any::Any { + unimplemented!() + } + + fn to_proto(&self) -> rpc::proto::File { + unimplemented!() + } + } +} diff --git a/crates/editor2/src/link_go_to_definition.rs b/crates/editor2/src/link_go_to_definition.rs new file mode 100644 index 0000000000..8c285c8214 --- /dev/null +++ b/crates/editor2/src/link_go_to_definition.rs @@ -0,0 +1,1275 @@ +use crate::{ + display_map::DisplaySnapshot, + element::PointForPosition, + hover_popover::{self, InlayHover}, + Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, InlayId, + SelectPhase, +}; +use gpui::{Task, ViewContext}; +use language::{Bias, ToOffset}; +use lsp::LanguageServerId; +use project::{ + HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink, + ResolveState, +}; +use std::ops::Range; +use util::TryFutureExt; + +#[derive(Debug, Default)] +pub struct LinkGoToDefinitionState { + pub last_trigger_point: Option, + pub symbol_range: Option, + pub kind: Option, + pub definitions: Vec, + pub task: Option>>, +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum RangeInEditor { + Text(Range), + Inlay(InlayHighlight), +} + +impl RangeInEditor { + pub fn as_text_range(&self) -> Option> { + match self { + Self::Text(range) => Some(range.clone()), + Self::Inlay(_) => None, + } + } + + fn point_within_range(&self, trigger_point: &TriggerPoint, snapshot: &EditorSnapshot) -> bool { + match (self, trigger_point) { + (Self::Text(range), TriggerPoint::Text(point)) => { + let point_after_start = range.start.cmp(point, &snapshot.buffer_snapshot).is_le(); + point_after_start && range.end.cmp(point, &snapshot.buffer_snapshot).is_ge() + } + (Self::Inlay(highlight), TriggerPoint::InlayHint(point, _, _)) => { + highlight.inlay == point.inlay + && highlight.range.contains(&point.range.start) + && highlight.range.contains(&point.range.end) + } + (Self::Inlay(_), TriggerPoint::Text(_)) + | (Self::Text(_), TriggerPoint::InlayHint(_, _, _)) => false, + } + } +} + +#[derive(Debug)] +pub enum GoToDefinitionTrigger { + Text(DisplayPoint), + InlayHint(InlayHighlight, lsp::Location, LanguageServerId), +} + +#[derive(Debug, Clone)] +pub enum GoToDefinitionLink { + Text(LocationLink), + InlayHint(lsp::Location, LanguageServerId), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InlayHighlight { + pub inlay: InlayId, + pub inlay_position: Anchor, + pub range: Range, +} + +#[derive(Debug, Clone)] +pub enum TriggerPoint { + Text(Anchor), + InlayHint(InlayHighlight, lsp::Location, LanguageServerId), +} + +impl TriggerPoint { + pub fn definition_kind(&self, shift: bool) -> LinkDefinitionKind { + match self { + TriggerPoint::Text(_) => { + if shift { + LinkDefinitionKind::Type + } else { + LinkDefinitionKind::Symbol + } + } + TriggerPoint::InlayHint(_, _, _) => LinkDefinitionKind::Type, + } + } + + fn anchor(&self) -> &Anchor { + match self { + TriggerPoint::Text(anchor) => anchor, + TriggerPoint::InlayHint(inlay_range, _, _) => &inlay_range.inlay_position, + } + } +} + +pub fn update_go_to_definition_link( + editor: &mut Editor, + origin: Option, + cmd_held: bool, + shift_held: bool, + cx: &mut ViewContext, +) { + let pending_nonempty_selection = editor.has_pending_nonempty_selection(); + + // Store new mouse point as an anchor + let snapshot = editor.snapshot(cx); + let trigger_point = match origin { + Some(GoToDefinitionTrigger::Text(p)) => { + Some(TriggerPoint::Text(snapshot.buffer_snapshot.anchor_before( + p.to_offset(&snapshot.display_snapshot, Bias::Left), + ))) + } + Some(GoToDefinitionTrigger::InlayHint(p, lsp_location, language_server_id)) => { + Some(TriggerPoint::InlayHint(p, lsp_location, language_server_id)) + } + None => None, + }; + + // If the new point is the same as the previously stored one, return early + if let (Some(a), Some(b)) = ( + &trigger_point, + &editor.link_go_to_definition_state.last_trigger_point, + ) { + match (a, b) { + (TriggerPoint::Text(anchor_a), TriggerPoint::Text(anchor_b)) => { + if anchor_a.cmp(anchor_b, &snapshot.buffer_snapshot).is_eq() { + return; + } + } + (TriggerPoint::InlayHint(range_a, _, _), TriggerPoint::InlayHint(range_b, _, _)) => { + if range_a == range_b { + return; + } + } + _ => {} + } + } + + editor.link_go_to_definition_state.last_trigger_point = trigger_point.clone(); + + if pending_nonempty_selection { + hide_link_definition(editor, cx); + return; + } + + if cmd_held { + if let Some(trigger_point) = trigger_point { + let kind = trigger_point.definition_kind(shift_held); + show_link_definition(kind, editor, trigger_point, snapshot, cx); + return; + } + } + + hide_link_definition(editor, cx); +} + +pub fn update_inlay_link_and_hover_points( + snapshot: &DisplaySnapshot, + point_for_position: PointForPosition, + editor: &mut Editor, + cmd_held: bool, + shift_held: bool, + cx: &mut ViewContext<'_, Editor>, +) { + todo!("old implementation below") +} +// ) { +// let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 { +// Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left)) +// } else { +// None +// }; +// let mut go_to_definition_updated = false; +// let mut hover_updated = false; +// if let Some(hovered_offset) = hovered_offset { +// let buffer_snapshot = editor.buffer().read(cx).snapshot(cx); +// let previous_valid_anchor = buffer_snapshot.anchor_at( +// point_for_position.previous_valid.to_point(snapshot), +// Bias::Left, +// ); +// let next_valid_anchor = buffer_snapshot.anchor_at( +// point_for_position.next_valid.to_point(snapshot), +// Bias::Right, +// ); +// if let Some(hovered_hint) = editor +// .visible_inlay_hints(cx) +// .into_iter() +// .skip_while(|hint| { +// hint.position +// .cmp(&previous_valid_anchor, &buffer_snapshot) +// .is_lt() +// }) +// .take_while(|hint| { +// hint.position +// .cmp(&next_valid_anchor, &buffer_snapshot) +// .is_le() +// }) +// .max_by_key(|hint| hint.id) +// { +// let inlay_hint_cache = editor.inlay_hint_cache(); +// let excerpt_id = previous_valid_anchor.excerpt_id; +// if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) { +// match cached_hint.resolve_state { +// ResolveState::CanResolve(_, _) => { +// if let Some(buffer_id) = previous_valid_anchor.buffer_id { +// inlay_hint_cache.spawn_hint_resolve( +// buffer_id, +// excerpt_id, +// hovered_hint.id, +// cx, +// ); +// } +// } +// ResolveState::Resolved => { +// let mut extra_shift_left = 0; +// let mut extra_shift_right = 0; +// if cached_hint.padding_left { +// extra_shift_left += 1; +// extra_shift_right += 1; +// } +// if cached_hint.padding_right { +// extra_shift_right += 1; +// } +// match cached_hint.label { +// project::InlayHintLabel::String(_) => { +// if let Some(tooltip) = cached_hint.tooltip { +// hover_popover::hover_at_inlay( +// editor, +// InlayHover { +// excerpt: excerpt_id, +// tooltip: match tooltip { +// InlayHintTooltip::String(text) => HoverBlock { +// text, +// kind: HoverBlockKind::PlainText, +// }, +// InlayHintTooltip::MarkupContent(content) => { +// HoverBlock { +// text: content.value, +// kind: content.kind, +// } +// } +// }, +// range: InlayHighlight { +// inlay: hovered_hint.id, +// inlay_position: hovered_hint.position, +// range: extra_shift_left +// ..hovered_hint.text.len() + extra_shift_right, +// }, +// }, +// cx, +// ); +// hover_updated = true; +// } +// } +// project::InlayHintLabel::LabelParts(label_parts) => { +// let hint_start = +// snapshot.anchor_to_inlay_offset(hovered_hint.position); +// if let Some((hovered_hint_part, part_range)) = +// hover_popover::find_hovered_hint_part( +// label_parts, +// hint_start, +// hovered_offset, +// ) +// { +// let highlight_start = +// (part_range.start - hint_start).0 + extra_shift_left; +// let highlight_end = +// (part_range.end - hint_start).0 + extra_shift_right; +// let highlight = InlayHighlight { +// inlay: hovered_hint.id, +// inlay_position: hovered_hint.position, +// range: highlight_start..highlight_end, +// }; +// if let Some(tooltip) = hovered_hint_part.tooltip { +// hover_popover::hover_at_inlay( +// editor, +// InlayHover { +// excerpt: excerpt_id, +// tooltip: match tooltip { +// InlayHintLabelPartTooltip::String(text) => { +// HoverBlock { +// text, +// kind: HoverBlockKind::PlainText, +// } +// } +// InlayHintLabelPartTooltip::MarkupContent( +// content, +// ) => HoverBlock { +// text: content.value, +// kind: content.kind, +// }, +// }, +// range: highlight.clone(), +// }, +// cx, +// ); +// hover_updated = true; +// } +// if let Some((language_server_id, location)) = +// hovered_hint_part.location +// { +// go_to_definition_updated = true; +// update_go_to_definition_link( +// editor, +// Some(GoToDefinitionTrigger::InlayHint( +// highlight, +// location, +// language_server_id, +// )), +// cmd_held, +// shift_held, +// cx, +// ); +// } +// } +// } +// }; +// } +// ResolveState::Resolving => {} +// } +// } +// } +// } + +// if !go_to_definition_updated { +// update_go_to_definition_link(editor, None, cmd_held, shift_held, cx); +// } +// if !hover_updated { +// hover_popover::hover_at(editor, None, cx); +// } +// } + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum LinkDefinitionKind { + Symbol, + Type, +} + +pub fn show_link_definition( + definition_kind: LinkDefinitionKind, + editor: &mut Editor, + trigger_point: TriggerPoint, + snapshot: EditorSnapshot, + cx: &mut ViewContext, +) { + let same_kind = editor.link_go_to_definition_state.kind == Some(definition_kind); + if !same_kind { + hide_link_definition(editor, cx); + } + + if editor.pending_rename.is_some() { + return; + } + + let trigger_anchor = trigger_point.anchor(); + let (buffer, buffer_position) = if let Some(output) = editor + .buffer + .read(cx) + .text_anchor_for_position(trigger_anchor.clone(), cx) + { + output + } else { + return; + }; + + let excerpt_id = if let Some((excerpt_id, _, _)) = editor + .buffer() + .read(cx) + .excerpt_containing(trigger_anchor.clone(), cx) + { + excerpt_id + } else { + return; + }; + + let project = if let Some(project) = editor.project.clone() { + project + } else { + return; + }; + + // Don't request again if the location is within the symbol region of a previous request with the same kind + if let Some(symbol_range) = &editor.link_go_to_definition_state.symbol_range { + if same_kind && symbol_range.point_within_range(&trigger_point, &snapshot) { + return; + } + } + + let task = cx.spawn(|this, mut cx| { + async move { + let result = match &trigger_point { + TriggerPoint::Text(_) => { + // query the LSP for definition info + project + .update(&mut cx, |project, cx| match definition_kind { + LinkDefinitionKind::Symbol => { + project.definition(&buffer, buffer_position, cx) + } + + LinkDefinitionKind::Type => { + project.type_definition(&buffer, buffer_position, cx) + } + })? + .await + .ok() + .map(|definition_result| { + ( + definition_result.iter().find_map(|link| { + link.origin.as_ref().map(|origin| { + let start = snapshot.buffer_snapshot.anchor_in_excerpt( + excerpt_id.clone(), + origin.range.start, + ); + let end = snapshot.buffer_snapshot.anchor_in_excerpt( + excerpt_id.clone(), + origin.range.end, + ); + RangeInEditor::Text(start..end) + }) + }), + definition_result + .into_iter() + .map(GoToDefinitionLink::Text) + .collect(), + ) + }) + } + TriggerPoint::InlayHint(highlight, lsp_location, server_id) => Some(( + Some(RangeInEditor::Inlay(highlight.clone())), + vec![GoToDefinitionLink::InlayHint( + lsp_location.clone(), + *server_id, + )], + )), + }; + + this.update(&mut cx, |this, cx| { + // Clear any existing highlights + this.clear_highlights::(cx); + this.link_go_to_definition_state.kind = Some(definition_kind); + this.link_go_to_definition_state.symbol_range = result + .as_ref() + .and_then(|(symbol_range, _)| symbol_range.clone()); + + if let Some((symbol_range, definitions)) = result { + this.link_go_to_definition_state.definitions = definitions.clone(); + + let buffer_snapshot = buffer.read(cx).snapshot(); + + // Only show highlight if there exists a definition to jump to that doesn't contain + // the current location. + let any_definition_does_not_contain_current_location = + definitions.iter().any(|definition| { + match &definition { + GoToDefinitionLink::Text(link) => { + if link.target.buffer == buffer { + let range = &link.target.range; + // Expand range by one character as lsp definition ranges include positions adjacent + // but not contained by the symbol range + let start = buffer_snapshot.clip_offset( + range + .start + .to_offset(&buffer_snapshot) + .saturating_sub(1), + Bias::Left, + ); + let end = buffer_snapshot.clip_offset( + range.end.to_offset(&buffer_snapshot) + 1, + Bias::Right, + ); + let offset = buffer_position.to_offset(&buffer_snapshot); + !(start <= offset && end >= offset) + } else { + true + } + } + GoToDefinitionLink::InlayHint(_, _) => true, + } + }); + + if any_definition_does_not_contain_current_location { + // todo!() + // // Highlight symbol using theme link definition highlight style + // let style = theme::current(cx).editor.link_definition; + // let highlight_range = + // symbol_range.unwrap_or_else(|| match &trigger_point { + // TriggerPoint::Text(trigger_anchor) => { + // let snapshot = &snapshot.buffer_snapshot; + // // If no symbol range returned from language server, use the surrounding word. + // let (offset_range, _) = + // snapshot.surrounding_word(*trigger_anchor); + // RangeInEditor::Text( + // snapshot.anchor_before(offset_range.start) + // ..snapshot.anchor_after(offset_range.end), + // ) + // } + // TriggerPoint::InlayHint(highlight, _, _) => { + // RangeInEditor::Inlay(highlight.clone()) + // } + // }); + + // match highlight_range { + // RangeInEditor::Text(text_range) => this + // .highlight_text::( + // vec![text_range], + // style, + // cx, + // ), + // RangeInEditor::Inlay(highlight) => this + // .highlight_inlays::( + // vec![highlight], + // style, + // cx, + // ), + // } + } else { + hide_link_definition(this, cx); + } + } + })?; + + Ok::<_, anyhow::Error>(()) + } + .log_err() + }); + + editor.link_go_to_definition_state.task = Some(task); +} + +pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext) { + if editor.link_go_to_definition_state.symbol_range.is_some() + || !editor.link_go_to_definition_state.definitions.is_empty() + { + editor.link_go_to_definition_state.symbol_range.take(); + editor.link_go_to_definition_state.definitions.clear(); + cx.notify(); + } + + editor.link_go_to_definition_state.task = None; + + editor.clear_highlights::(cx); +} + +pub fn go_to_fetched_definition( + editor: &mut Editor, + point: PointForPosition, + split: bool, + cx: &mut ViewContext, +) { + go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, editor, point, split, cx); +} + +pub fn go_to_fetched_type_definition( + editor: &mut Editor, + point: PointForPosition, + split: bool, + cx: &mut ViewContext, +) { + go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, editor, point, split, cx); +} + +fn go_to_fetched_definition_of_kind( + kind: LinkDefinitionKind, + editor: &mut Editor, + point: PointForPosition, + split: bool, + cx: &mut ViewContext, +) { + let cached_definitions = editor.link_go_to_definition_state.definitions.clone(); + hide_link_definition(editor, cx); + let cached_definitions_kind = editor.link_go_to_definition_state.kind; + + let is_correct_kind = cached_definitions_kind == Some(kind); + if !cached_definitions.is_empty() && is_correct_kind { + if !editor.focused { + cx.focus(&editor.focus_handle); + } + + editor.navigate_to_definitions(cached_definitions, split, cx); + } else { + editor.select( + SelectPhase::Begin { + position: point.next_valid, + add: false, + click_count: 1, + }, + cx, + ); + + if point.as_valid().is_some() { + match kind { + LinkDefinitionKind::Symbol => editor.go_to_definition(&GoToDefinition, cx), + LinkDefinitionKind::Type => editor.go_to_type_definition(&GoToTypeDefinition, cx), + } + } + } +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::{ +// display_map::ToDisplayPoint, +// editor_tests::init_test, +// inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, +// test::editor_lsp_test_context::EditorLspTestContext, +// }; +// use futures::StreamExt; +// use gpui::{ +// platform::{self, Modifiers, ModifiersChangedEvent}, +// View, +// }; +// use indoc::indoc; +// use language::language_settings::InlayHintSettings; +// use lsp::request::{GotoDefinition, GotoTypeDefinition}; +// use util::assert_set_eq; + +// #[gpui::test] +// async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorLspTestContext::new_rust( +// lsp::ServerCapabilities { +// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), +// type_definition_provider: Some(lsp::TypeDefinitionProviderCapability::Simple(true)), +// ..Default::default() +// }, +// cx, +// ) +// .await; + +// cx.set_state(indoc! {" +// struct A; +// let vˇariable = A; +// "}); + +// // Basic hold cmd+shift, expect highlight in region if response contains type definition +// let hover_point = cx.display_point(indoc! {" +// struct A; +// let vˇariable = A; +// "}); +// let symbol_range = cx.lsp_range(indoc! {" +// struct A; +// let «variable» = A; +// "}); +// let target_range = cx.lsp_range(indoc! {" +// struct «A»; +// let variable = A; +// "}); + +// let mut requests = +// cx.handle_request::(move |url, _, _| async move { +// Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![ +// lsp::LocationLink { +// origin_selection_range: Some(symbol_range), +// target_uri: url.clone(), +// target_range, +// target_selection_range: target_range, +// }, +// ]))) +// }); + +// // Press cmd+shift to trigger highlight +// cx.update_editor(|editor, cx| { +// update_go_to_definition_link( +// editor, +// Some(GoToDefinitionTrigger::Text(hover_point)), +// true, +// true, +// cx, +// ); +// }); +// requests.next().await; +// cx.foreground().run_until_parked(); +// cx.assert_editor_text_highlights::(indoc! {" +// struct A; +// let «variable» = A; +// "}); + +// // Unpress shift causes highlight to go away (normal goto-definition is not valid here) +// cx.update_editor(|editor, cx| { +// editor.modifiers_changed( +// &platform::ModifiersChangedEvent { +// modifiers: Modifiers { +// cmd: true, +// ..Default::default() +// }, +// ..Default::default() +// }, +// cx, +// ); +// }); +// // Assert no link highlights +// cx.assert_editor_text_highlights::(indoc! {" +// struct A; +// let variable = A; +// "}); + +// // Cmd+shift click without existing definition requests and jumps +// let hover_point = cx.display_point(indoc! {" +// struct A; +// let vˇariable = A; +// "}); +// let target_range = cx.lsp_range(indoc! {" +// struct «A»; +// let variable = A; +// "}); + +// let mut requests = +// cx.handle_request::(move |url, _, _| async move { +// Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![ +// lsp::LocationLink { +// origin_selection_range: None, +// target_uri: url, +// target_range, +// target_selection_range: target_range, +// }, +// ]))) +// }); + +// cx.update_editor(|editor, cx| { +// go_to_fetched_type_definition(editor, PointForPosition::valid(hover_point), false, cx); +// }); +// requests.next().await; +// cx.foreground().run_until_parked(); + +// cx.assert_editor_state(indoc! {" +// struct «Aˇ»; +// let variable = A; +// "}); +// } + +// #[gpui::test] +// async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorLspTestContext::new_rust( +// lsp::ServerCapabilities { +// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), +// ..Default::default() +// }, +// cx, +// ) +// .await; + +// cx.set_state(indoc! {" +// fn ˇtest() { do_work(); } +// fn do_work() { test(); } +// "}); + +// // Basic hold cmd, expect highlight in region if response contains definition +// let hover_point = cx.display_point(indoc! {" +// fn test() { do_wˇork(); } +// fn do_work() { test(); } +// "}); +// let symbol_range = cx.lsp_range(indoc! {" +// fn test() { «do_work»(); } +// fn do_work() { test(); } +// "}); +// let target_range = cx.lsp_range(indoc! {" +// fn test() { do_work(); } +// fn «do_work»() { test(); } +// "}); + +// let mut requests = cx.handle_request::(move |url, _, _| async move { +// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ +// lsp::LocationLink { +// origin_selection_range: Some(symbol_range), +// target_uri: url.clone(), +// target_range, +// target_selection_range: target_range, +// }, +// ]))) +// }); + +// cx.update_editor(|editor, cx| { +// update_go_to_definition_link( +// editor, +// Some(GoToDefinitionTrigger::Text(hover_point)), +// true, +// false, +// cx, +// ); +// }); +// requests.next().await; +// cx.foreground().run_until_parked(); +// cx.assert_editor_text_highlights::(indoc! {" +// fn test() { «do_work»(); } +// fn do_work() { test(); } +// "}); + +// // Unpress cmd causes highlight to go away +// cx.update_editor(|editor, cx| { +// editor.modifiers_changed(&Default::default(), cx); +// }); + +// // Assert no link highlights +// cx.assert_editor_text_highlights::(indoc! {" +// fn test() { do_work(); } +// fn do_work() { test(); } +// "}); + +// // Response without source range still highlights word +// cx.update_editor(|editor, _| editor.link_go_to_definition_state.last_trigger_point = None); +// let mut requests = cx.handle_request::(move |url, _, _| async move { +// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ +// lsp::LocationLink { +// // No origin range +// origin_selection_range: None, +// target_uri: url.clone(), +// target_range, +// target_selection_range: target_range, +// }, +// ]))) +// }); +// cx.update_editor(|editor, cx| { +// update_go_to_definition_link( +// editor, +// Some(GoToDefinitionTrigger::Text(hover_point)), +// true, +// false, +// cx, +// ); +// }); +// requests.next().await; +// cx.foreground().run_until_parked(); + +// cx.assert_editor_text_highlights::(indoc! {" +// fn test() { «do_work»(); } +// fn do_work() { test(); } +// "}); + +// // Moving mouse to location with no response dismisses highlight +// let hover_point = cx.display_point(indoc! {" +// fˇn test() { do_work(); } +// fn do_work() { test(); } +// "}); +// let mut requests = cx +// .lsp +// .handle_request::(move |_, _| async move { +// // No definitions returned +// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![]))) +// }); +// cx.update_editor(|editor, cx| { +// update_go_to_definition_link( +// editor, +// Some(GoToDefinitionTrigger::Text(hover_point)), +// true, +// false, +// cx, +// ); +// }); +// requests.next().await; +// cx.foreground().run_until_parked(); + +// // Assert no link highlights +// cx.assert_editor_text_highlights::(indoc! {" +// fn test() { do_work(); } +// fn do_work() { test(); } +// "}); + +// // Move mouse without cmd and then pressing cmd triggers highlight +// let hover_point = cx.display_point(indoc! {" +// fn test() { do_work(); } +// fn do_work() { teˇst(); } +// "}); +// cx.update_editor(|editor, cx| { +// update_go_to_definition_link( +// editor, +// Some(GoToDefinitionTrigger::Text(hover_point)), +// false, +// false, +// cx, +// ); +// }); +// cx.foreground().run_until_parked(); + +// // Assert no link highlights +// cx.assert_editor_text_highlights::(indoc! {" +// fn test() { do_work(); } +// fn do_work() { test(); } +// "}); + +// let symbol_range = cx.lsp_range(indoc! {" +// fn test() { do_work(); } +// fn do_work() { «test»(); } +// "}); +// let target_range = cx.lsp_range(indoc! {" +// fn «test»() { do_work(); } +// fn do_work() { test(); } +// "}); + +// let mut requests = cx.handle_request::(move |url, _, _| async move { +// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ +// lsp::LocationLink { +// origin_selection_range: Some(symbol_range), +// target_uri: url, +// target_range, +// target_selection_range: target_range, +// }, +// ]))) +// }); +// cx.update_editor(|editor, cx| { +// editor.modifiers_changed( +// &ModifiersChangedEvent { +// modifiers: Modifiers { +// cmd: true, +// ..Default::default() +// }, +// }, +// cx, +// ); +// }); +// requests.next().await; +// cx.foreground().run_until_parked(); + +// cx.assert_editor_text_highlights::(indoc! {" +// fn test() { do_work(); } +// fn do_work() { «test»(); } +// "}); + +// // Deactivating the window dismisses the highlight +// cx.update_workspace(|workspace, cx| { +// workspace.on_window_activation_changed(false, cx); +// }); +// cx.assert_editor_text_highlights::(indoc! {" +// fn test() { do_work(); } +// fn do_work() { test(); } +// "}); + +// // Moving the mouse restores the highlights. +// cx.update_editor(|editor, cx| { +// update_go_to_definition_link( +// editor, +// Some(GoToDefinitionTrigger::Text(hover_point)), +// true, +// false, +// cx, +// ); +// }); +// cx.foreground().run_until_parked(); +// cx.assert_editor_text_highlights::(indoc! {" +// fn test() { do_work(); } +// fn do_work() { «test»(); } +// "}); + +// // Moving again within the same symbol range doesn't re-request +// let hover_point = cx.display_point(indoc! {" +// fn test() { do_work(); } +// fn do_work() { tesˇt(); } +// "}); +// cx.update_editor(|editor, cx| { +// update_go_to_definition_link( +// editor, +// Some(GoToDefinitionTrigger::Text(hover_point)), +// true, +// false, +// cx, +// ); +// }); +// cx.foreground().run_until_parked(); +// cx.assert_editor_text_highlights::(indoc! {" +// fn test() { do_work(); } +// fn do_work() { «test»(); } +// "}); + +// // Cmd click with existing definition doesn't re-request and dismisses highlight +// cx.update_editor(|editor, cx| { +// go_to_fetched_definition(editor, PointForPosition::valid(hover_point), false, cx); +// }); +// // Assert selection moved to to definition +// cx.lsp +// .handle_request::(move |_, _| async move { +// // Empty definition response to make sure we aren't hitting the lsp and using +// // the cached location instead +// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![]))) +// }); +// cx.foreground().run_until_parked(); +// cx.assert_editor_state(indoc! {" +// fn «testˇ»() { do_work(); } +// fn do_work() { test(); } +// "}); + +// // Assert no link highlights after jump +// cx.assert_editor_text_highlights::(indoc! {" +// fn test() { do_work(); } +// fn do_work() { test(); } +// "}); + +// // Cmd click without existing definition requests and jumps +// let hover_point = cx.display_point(indoc! {" +// fn test() { do_wˇork(); } +// fn do_work() { test(); } +// "}); +// let target_range = cx.lsp_range(indoc! {" +// fn test() { do_work(); } +// fn «do_work»() { test(); } +// "}); + +// let mut requests = cx.handle_request::(move |url, _, _| async move { +// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ +// lsp::LocationLink { +// origin_selection_range: None, +// target_uri: url, +// target_range, +// target_selection_range: target_range, +// }, +// ]))) +// }); +// cx.update_editor(|editor, cx| { +// go_to_fetched_definition(editor, PointForPosition::valid(hover_point), false, cx); +// }); +// requests.next().await; +// cx.foreground().run_until_parked(); +// cx.assert_editor_state(indoc! {" +// fn test() { do_work(); } +// fn «do_workˇ»() { test(); } +// "}); + +// // 1. We have a pending selection, mouse point is over a symbol that we have a response for, hitting cmd and nothing happens +// // 2. Selection is completed, hovering +// let hover_point = cx.display_point(indoc! {" +// fn test() { do_wˇork(); } +// fn do_work() { test(); } +// "}); +// let target_range = cx.lsp_range(indoc! {" +// fn test() { do_work(); } +// fn «do_work»() { test(); } +// "}); +// let mut requests = cx.handle_request::(move |url, _, _| async move { +// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ +// lsp::LocationLink { +// origin_selection_range: None, +// target_uri: url, +// target_range, +// target_selection_range: target_range, +// }, +// ]))) +// }); + +// // create a pending selection +// let selection_range = cx.ranges(indoc! {" +// fn «test() { do_w»ork(); } +// fn do_work() { test(); } +// "})[0] +// .clone(); +// cx.update_editor(|editor, cx| { +// let snapshot = editor.buffer().read(cx).snapshot(cx); +// let anchor_range = snapshot.anchor_before(selection_range.start) +// ..snapshot.anchor_after(selection_range.end); +// editor.change_selections(Some(crate::Autoscroll::fit()), cx, |s| { +// s.set_pending_anchor_range(anchor_range, crate::SelectMode::Character) +// }); +// }); +// cx.update_editor(|editor, cx| { +// update_go_to_definition_link( +// editor, +// Some(GoToDefinitionTrigger::Text(hover_point)), +// true, +// false, +// cx, +// ); +// }); +// cx.foreground().run_until_parked(); +// assert!(requests.try_next().is_err()); +// cx.assert_editor_text_highlights::(indoc! {" +// fn test() { do_work(); } +// fn do_work() { test(); } +// "}); +// cx.foreground().run_until_parked(); +// } + +// #[gpui::test] +// async fn test_link_go_to_inlay(cx: &mut gpui::TestAppContext) { +// init_test(cx, |settings| { +// settings.defaults.inlay_hints = Some(InlayHintSettings { +// enabled: true, +// show_type_hints: true, +// show_parameter_hints: true, +// show_other_hints: true, +// }) +// }); + +// let mut cx = EditorLspTestContext::new_rust( +// lsp::ServerCapabilities { +// inlay_hint_provider: Some(lsp::OneOf::Left(true)), +// ..Default::default() +// }, +// cx, +// ) +// .await; +// cx.set_state(indoc! {" +// struct TestStruct; + +// fn main() { +// let variableˇ = TestStruct; +// } +// "}); +// let hint_start_offset = cx.ranges(indoc! {" +// struct TestStruct; + +// fn main() { +// let variableˇ = TestStruct; +// } +// "})[0] +// .start; +// let hint_position = cx.to_lsp(hint_start_offset); +// let target_range = cx.lsp_range(indoc! {" +// struct «TestStruct»; + +// fn main() { +// let variable = TestStruct; +// } +// "}); + +// let expected_uri = cx.buffer_lsp_url.clone(); +// let hint_label = ": TestStruct"; +// cx.lsp +// .handle_request::(move |params, _| { +// let expected_uri = expected_uri.clone(); +// async move { +// assert_eq!(params.text_document.uri, expected_uri); +// Ok(Some(vec![lsp::InlayHint { +// position: hint_position, +// label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart { +// value: hint_label.to_string(), +// location: Some(lsp::Location { +// uri: params.text_document.uri, +// range: target_range, +// }), +// ..Default::default() +// }]), +// kind: Some(lsp::InlayHintKind::TYPE), +// text_edits: None, +// tooltip: None, +// padding_left: Some(false), +// padding_right: Some(false), +// data: None, +// }])) +// } +// }) +// .next() +// .await; +// cx.foreground().run_until_parked(); +// cx.update_editor(|editor, cx| { +// let expected_layers = vec![hint_label.to_string()]; +// assert_eq!(expected_layers, cached_hint_labels(editor)); +// assert_eq!(expected_layers, visible_hint_labels(editor, cx)); +// }); + +// let inlay_range = cx +// .ranges(indoc! {" +// struct TestStruct; + +// fn main() { +// let variable« »= TestStruct; +// } +// "}) +// .get(0) +// .cloned() +// .unwrap(); +// let hint_hover_position = cx.update_editor(|editor, cx| { +// let snapshot = editor.snapshot(cx); +// let previous_valid = inlay_range.start.to_display_point(&snapshot); +// let next_valid = inlay_range.end.to_display_point(&snapshot); +// assert_eq!(previous_valid.row(), next_valid.row()); +// assert!(previous_valid.column() < next_valid.column()); +// let exact_unclipped = DisplayPoint::new( +// previous_valid.row(), +// previous_valid.column() + (hint_label.len() / 2) as u32, +// ); +// PointForPosition { +// previous_valid, +// next_valid, +// exact_unclipped, +// column_overshoot_after_line_end: 0, +// } +// }); +// // Press cmd to trigger highlight +// cx.update_editor(|editor, cx| { +// update_inlay_link_and_hover_points( +// &editor.snapshot(cx), +// hint_hover_position, +// editor, +// true, +// false, +// cx, +// ); +// }); +// cx.foreground().run_until_parked(); +// cx.update_editor(|editor, cx| { +// let snapshot = editor.snapshot(cx); +// let actual_highlights = snapshot +// .inlay_highlights::() +// .into_iter() +// .flat_map(|highlights| highlights.values().map(|(_, highlight)| highlight)) +// .collect::>(); + +// let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); +// let expected_highlight = InlayHighlight { +// inlay: InlayId::Hint(0), +// inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), +// range: 0..hint_label.len(), +// }; +// assert_set_eq!(actual_highlights, vec![&expected_highlight]); +// }); + +// // Unpress cmd causes highlight to go away +// cx.update_editor(|editor, cx| { +// editor.modifiers_changed( +// &platform::ModifiersChangedEvent { +// modifiers: Modifiers { +// cmd: false, +// ..Default::default() +// }, +// ..Default::default() +// }, +// cx, +// ); +// }); +// // Assert no link highlights +// cx.update_editor(|editor, cx| { +// let snapshot = editor.snapshot(cx); +// let actual_ranges = snapshot +// .text_highlight_ranges::() +// .map(|ranges| ranges.as_ref().clone().1) +// .unwrap_or_default(); + +// assert!(actual_ranges.is_empty(), "When no cmd is pressed, should have no hint label selected, but got: {actual_ranges:?}"); +// }); + +// // Cmd+click without existing definition requests and jumps +// cx.update_editor(|editor, cx| { +// editor.modifiers_changed( +// &platform::ModifiersChangedEvent { +// modifiers: Modifiers { +// cmd: true, +// ..Default::default() +// }, +// ..Default::default() +// }, +// cx, +// ); +// update_inlay_link_and_hover_points( +// &editor.snapshot(cx), +// hint_hover_position, +// editor, +// true, +// false, +// cx, +// ); +// }); +// cx.foreground().run_until_parked(); +// cx.update_editor(|editor, cx| { +// go_to_fetched_type_definition(editor, hint_hover_position, false, cx); +// }); +// cx.foreground().run_until_parked(); +// cx.assert_editor_state(indoc! {" +// struct «TestStructˇ»; + +// fn main() { +// let variable = TestStruct; +// } +// "}); +// } +// } diff --git a/crates/editor2/src/mouse_context_menu.rs b/crates/editor2/src/mouse_context_menu.rs new file mode 100644 index 0000000000..b70a826bf8 --- /dev/null +++ b/crates/editor2/src/mouse_context_menu.rs @@ -0,0 +1,94 @@ +use crate::{DisplayPoint, Editor, EditorMode, SelectMode}; +use gpui::{Pixels, Point, ViewContext}; + +pub fn deploy_context_menu( + editor: &mut Editor, + position: Point, + point: DisplayPoint, + cx: &mut ViewContext, +) { + todo!(); + + // if !editor.focused { + // cx.focus_self(); + // } + + // // Don't show context menu for inline editors + // if editor.mode() != EditorMode::Full { + // return; + // } + + // // Don't show the context menu if there isn't a project associated with this editor + // if editor.project.is_none() { + // return; + // } + + // // Move the cursor to the clicked location so that dispatched actions make sense + // editor.change_selections(None, cx, |s| { + // s.clear_disjoint(); + // s.set_pending_display_range(point..point, SelectMode::Character); + // }); + + // editor.mouse_context_menu.update(cx, |menu, cx| { + // menu.show( + // position, + // AnchorCorner::TopLeft, + // vec![ + // ContextMenuItem::action("Rename Symbol", Rename), + // ContextMenuItem::action("Go to Definition", GoToDefinition), + // ContextMenuItem::action("Go to Type Definition", GoToTypeDefinition), + // ContextMenuItem::action("Find All References", FindAllReferences), + // ContextMenuItem::action( + // "Code Actions", + // ToggleCodeActions { + // deployed_from_indicator: false, + // }, + // ), + // ContextMenuItem::Separator, + // ContextMenuItem::action("Reveal in Finder", RevealInFinder), + // ], + // cx, + // ); + // }); + // cx.notify(); +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext}; +// use indoc::indoc; + +// #[gpui::test] +// async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); + +// let mut cx = EditorLspTestContext::new_rust( +// lsp::ServerCapabilities { +// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), +// ..Default::default() +// }, +// cx, +// ) +// .await; + +// cx.set_state(indoc! {" +// fn teˇst() { +// do_work(); +// } +// "}); +// let point = cx.display_point(indoc! {" +// fn test() { +// do_wˇork(); +// } +// "}); +// cx.update_editor(|editor, cx| deploy_context_menu(editor, Default::default(), point, cx)); + +// cx.assert_editor_state(indoc! {" +// fn test() { +// do_wˇork(); +// } +// "}); +// cx.editor(|editor, app| assert!(editor.mouse_context_menu.read(app).visible())); +// } +// } diff --git a/crates/editor2/src/movement.rs b/crates/editor2/src/movement.rs new file mode 100644 index 0000000000..962f4f60e6 --- /dev/null +++ b/crates/editor2/src/movement.rs @@ -0,0 +1,933 @@ +use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint}; +use crate::{char_kind, CharKind, EditorStyle, ToOffset, ToPoint}; +use gpui::{px, TextSystem}; +use language::Point; +use serde::de::IntoDeserializer; +use std::ops::Range; + +#[derive(Debug, PartialEq)] +pub enum FindRange { + SingleLine, + MultiLine, +} + +/// TextLayoutDetails encompasses everything we need to move vertically +/// taking into account variable width characters. +pub struct TextLayoutDetails { + pub text_system: TextSystem, + pub editor_style: EditorStyle, +} + +pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint { + if point.column() > 0 { + *point.column_mut() -= 1; + } else if point.row() > 0 { + *point.row_mut() -= 1; + *point.column_mut() = map.line_len(point.row()); + } + map.clip_point(point, Bias::Left) +} + +pub fn saturating_left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint { + if point.column() > 0 { + *point.column_mut() -= 1; + } + map.clip_point(point, Bias::Left) +} + +pub fn right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint { + let max_column = map.line_len(point.row()); + if point.column() < max_column { + *point.column_mut() += 1; + } else if point.row() < map.max_point().row() { + *point.row_mut() += 1; + *point.column_mut() = 0; + } + map.clip_point(point, Bias::Right) +} + +pub fn saturating_right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint { + *point.column_mut() += 1; + map.clip_point(point, Bias::Right) +} + +pub fn up( + map: &DisplaySnapshot, + start: DisplayPoint, + goal: SelectionGoal, + preserve_column_at_start: bool, + text_layout_details: &TextLayoutDetails, +) -> (DisplayPoint, SelectionGoal) { + up_by_rows( + map, + start, + 1, + goal, + preserve_column_at_start, + text_layout_details, + ) +} + +pub fn down( + map: &DisplaySnapshot, + start: DisplayPoint, + goal: SelectionGoal, + preserve_column_at_end: bool, + text_layout_details: &TextLayoutDetails, +) -> (DisplayPoint, SelectionGoal) { + down_by_rows( + map, + start, + 1, + goal, + preserve_column_at_end, + text_layout_details, + ) +} + +pub fn up_by_rows( + map: &DisplaySnapshot, + start: DisplayPoint, + row_count: u32, + goal: SelectionGoal, + preserve_column_at_start: bool, + text_layout_details: &TextLayoutDetails, +) -> (DisplayPoint, SelectionGoal) { + let mut goal_x = match goal { + SelectionGoal::HorizontalPosition(x) => x.into(), // todo!("Can the fields in SelectionGoal by Pixels? We should extract a geometry crate and depend on that.") + SelectionGoal::WrappedHorizontalPosition((_, x)) => x.into(), + SelectionGoal::HorizontalRange { end, .. } => end.into(), + _ => map.x_for_point(start, text_layout_details), + }; + + let prev_row = start.row().saturating_sub(row_count); + let mut point = map.clip_point( + DisplayPoint::new(prev_row, map.line_len(prev_row)), + Bias::Left, + ); + if point.row() < start.row() { + *point.column_mut() = map.column_for_x(point.row(), goal_x, text_layout_details) + } else if preserve_column_at_start { + return (start, goal); + } else { + point = DisplayPoint::new(0, 0); + goal_x = px(0.); + } + + let mut clipped_point = map.clip_point(point, Bias::Left); + if clipped_point.row() < point.row() { + clipped_point = map.clip_point(point, Bias::Right); + } + ( + clipped_point, + SelectionGoal::HorizontalPosition(goal_x.into()), + ) +} + +pub fn down_by_rows( + map: &DisplaySnapshot, + start: DisplayPoint, + row_count: u32, + goal: SelectionGoal, + preserve_column_at_end: bool, + text_layout_details: &TextLayoutDetails, +) -> (DisplayPoint, SelectionGoal) { + let mut goal_x = match goal { + SelectionGoal::HorizontalPosition(x) => x.into(), + SelectionGoal::WrappedHorizontalPosition((_, x)) => x.into(), + SelectionGoal::HorizontalRange { end, .. } => end.into(), + _ => map.x_for_point(start, text_layout_details), + }; + + let new_row = start.row() + row_count; + let mut point = map.clip_point(DisplayPoint::new(new_row, 0), Bias::Right); + if point.row() > start.row() { + *point.column_mut() = map.column_for_x(point.row(), goal_x, text_layout_details) + } else if preserve_column_at_end { + return (start, goal); + } else { + point = map.max_point(); + goal_x = map.x_for_point(point, text_layout_details) + } + + let mut clipped_point = map.clip_point(point, Bias::Right); + if clipped_point.row() > point.row() { + clipped_point = map.clip_point(point, Bias::Left); + } + ( + clipped_point, + SelectionGoal::HorizontalPosition(goal_x.into()), + ) +} + +pub fn line_beginning( + map: &DisplaySnapshot, + display_point: DisplayPoint, + stop_at_soft_boundaries: bool, +) -> DisplayPoint { + let point = display_point.to_point(map); + let soft_line_start = map.clip_point(DisplayPoint::new(display_point.row(), 0), Bias::Right); + let line_start = map.prev_line_boundary(point).1; + + if stop_at_soft_boundaries && display_point != soft_line_start { + soft_line_start + } else { + line_start + } +} + +pub fn indented_line_beginning( + map: &DisplaySnapshot, + display_point: DisplayPoint, + stop_at_soft_boundaries: bool, +) -> DisplayPoint { + let point = display_point.to_point(map); + let soft_line_start = map.clip_point(DisplayPoint::new(display_point.row(), 0), Bias::Right); + let indent_start = Point::new( + point.row, + map.buffer_snapshot.indent_size_for_line(point.row).len, + ) + .to_display_point(map); + let line_start = map.prev_line_boundary(point).1; + + if stop_at_soft_boundaries && soft_line_start > indent_start && display_point != soft_line_start + { + soft_line_start + } else if stop_at_soft_boundaries && display_point != indent_start { + indent_start + } else { + line_start + } +} + +pub fn line_end( + map: &DisplaySnapshot, + display_point: DisplayPoint, + stop_at_soft_boundaries: bool, +) -> DisplayPoint { + let soft_line_end = map.clip_point( + DisplayPoint::new(display_point.row(), map.line_len(display_point.row())), + Bias::Left, + ); + if stop_at_soft_boundaries && display_point != soft_line_end { + soft_line_end + } else { + map.next_line_boundary(display_point.to_point(map)).1 + } +} + +pub fn previous_word_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint { + let raw_point = point.to_point(map); + let scope = map.buffer_snapshot.language_scope_at(raw_point); + + find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| { + (char_kind(&scope, left) != char_kind(&scope, right) && !right.is_whitespace()) + || left == '\n' + }) +} + +pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint { + let raw_point = point.to_point(map); + let scope = map.buffer_snapshot.language_scope_at(raw_point); + + find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| { + let is_word_start = + char_kind(&scope, left) != char_kind(&scope, right) && !right.is_whitespace(); + let is_subword_start = + left == '_' && right != '_' || left.is_lowercase() && right.is_uppercase(); + is_word_start || is_subword_start || left == '\n' + }) +} + +pub fn next_word_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint { + let raw_point = point.to_point(map); + let scope = map.buffer_snapshot.language_scope_at(raw_point); + + find_boundary(map, point, FindRange::MultiLine, |left, right| { + (char_kind(&scope, left) != char_kind(&scope, right) && !left.is_whitespace()) + || right == '\n' + }) +} + +pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint { + let raw_point = point.to_point(map); + let scope = map.buffer_snapshot.language_scope_at(raw_point); + + find_boundary(map, point, FindRange::MultiLine, |left, right| { + let is_word_end = + (char_kind(&scope, left) != char_kind(&scope, right)) && !left.is_whitespace(); + let is_subword_end = + left != '_' && right == '_' || left.is_lowercase() && right.is_uppercase(); + is_word_end || is_subword_end || right == '\n' + }) +} + +pub fn start_of_paragraph( + map: &DisplaySnapshot, + display_point: DisplayPoint, + mut count: usize, +) -> DisplayPoint { + let point = display_point.to_point(map); + if point.row == 0 { + return DisplayPoint::zero(); + } + + let mut found_non_blank_line = false; + for row in (0..point.row + 1).rev() { + let blank = map.buffer_snapshot.is_line_blank(row); + if found_non_blank_line && blank { + if count <= 1 { + return Point::new(row, 0).to_display_point(map); + } + count -= 1; + found_non_blank_line = false; + } + + found_non_blank_line |= !blank; + } + + DisplayPoint::zero() +} + +pub fn end_of_paragraph( + map: &DisplaySnapshot, + display_point: DisplayPoint, + mut count: usize, +) -> DisplayPoint { + let point = display_point.to_point(map); + if point.row == map.max_buffer_row() { + return map.max_point(); + } + + let mut found_non_blank_line = false; + for row in point.row..map.max_buffer_row() + 1 { + let blank = map.buffer_snapshot.is_line_blank(row); + if found_non_blank_line && blank { + if count <= 1 { + return Point::new(row, 0).to_display_point(map); + } + count -= 1; + found_non_blank_line = false; + } + + found_non_blank_line |= !blank; + } + + map.max_point() +} + +/// Scans for a boundary preceding the given start point `from` until a boundary is found, +/// indicated by the given predicate returning true. +/// The predicate is called with the character to the left and right of the candidate boundary location. +/// If FindRange::SingleLine is specified and no boundary is found before the start of the current line, the start of the current line will be returned. +pub fn find_preceding_boundary( + map: &DisplaySnapshot, + from: DisplayPoint, + find_range: FindRange, + mut is_boundary: impl FnMut(char, char) -> bool, +) -> DisplayPoint { + let mut prev_ch = None; + let mut offset = from.to_point(map).to_offset(&map.buffer_snapshot); + + for ch in map.buffer_snapshot.reversed_chars_at(offset) { + if find_range == FindRange::SingleLine && ch == '\n' { + break; + } + if let Some(prev_ch) = prev_ch { + if is_boundary(ch, prev_ch) { + break; + } + } + + offset -= ch.len_utf8(); + prev_ch = Some(ch); + } + + map.clip_point(offset.to_display_point(map), Bias::Left) +} + +/// Scans for a boundary following the given start point until a boundary is found, indicated by the +/// given predicate returning true. The predicate is called with the character to the left and right +/// of the candidate boundary location, and will be called with `\n` characters indicating the start +/// or end of a line. +pub fn find_boundary( + map: &DisplaySnapshot, + from: DisplayPoint, + find_range: FindRange, + mut is_boundary: impl FnMut(char, char) -> bool, +) -> DisplayPoint { + let mut offset = from.to_offset(&map, Bias::Right); + let mut prev_ch = None; + + for ch in map.buffer_snapshot.chars_at(offset) { + if find_range == FindRange::SingleLine && ch == '\n' { + break; + } + if let Some(prev_ch) = prev_ch { + if is_boundary(prev_ch, ch) { + break; + } + } + + offset += ch.len_utf8(); + prev_ch = Some(ch); + } + map.clip_point(offset.to_display_point(map), Bias::Right) +} + +pub fn chars_after( + map: &DisplaySnapshot, + mut offset: usize, +) -> impl Iterator)> + '_ { + map.buffer_snapshot.chars_at(offset).map(move |ch| { + let before = offset; + offset = offset + ch.len_utf8(); + (ch, before..offset) + }) +} + +pub fn chars_before( + map: &DisplaySnapshot, + mut offset: usize, +) -> impl Iterator)> + '_ { + map.buffer_snapshot + .reversed_chars_at(offset) + .map(move |ch| { + let after = offset; + offset = offset - ch.len_utf8(); + (ch, offset..after) + }) +} + +pub fn is_inside_word(map: &DisplaySnapshot, point: DisplayPoint) -> bool { + let raw_point = point.to_point(map); + let scope = map.buffer_snapshot.language_scope_at(raw_point); + let ix = map.clip_point(point, Bias::Left).to_offset(map, Bias::Left); + let text = &map.buffer_snapshot; + let next_char_kind = text.chars_at(ix).next().map(|c| char_kind(&scope, c)); + let prev_char_kind = text + .reversed_chars_at(ix) + .next() + .map(|c| char_kind(&scope, c)); + prev_char_kind.zip(next_char_kind) == Some((CharKind::Word, CharKind::Word)) +} + +pub fn surrounding_word(map: &DisplaySnapshot, position: DisplayPoint) -> Range { + let position = map + .clip_point(position, Bias::Left) + .to_offset(map, Bias::Left); + let (range, _) = map.buffer_snapshot.surrounding_word(position); + let start = range + .start + .to_point(&map.buffer_snapshot) + .to_display_point(map); + let end = range + .end + .to_point(&map.buffer_snapshot) + .to_display_point(map); + start..end +} + +pub fn split_display_range_by_lines( + map: &DisplaySnapshot, + range: Range, +) -> Vec> { + let mut result = Vec::new(); + + let mut start = range.start; + // Loop over all the covered rows until the one containing the range end + for row in range.start.row()..range.end.row() { + let row_end_column = map.line_len(row); + let end = map.clip_point(DisplayPoint::new(row, row_end_column), Bias::Left); + if start != end { + result.push(start..end); + } + start = map.clip_point(DisplayPoint::new(row + 1, 0), Bias::Left); + } + + // Add the final range from the start of the last end to the original range end. + result.push(start..range.end); + + result +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::{ +// display_map::Inlay, +// test::{}, +// Buffer, DisplayMap, ExcerptRange, InlayId, MultiBuffer, +// }; +// use project::Project; +// use settings::SettingsStore; +// use util::post_inc; + +// #[gpui::test] +// fn test_previous_word_start(cx: &mut gpui::AppContext) { +// init_test(cx); + +// fn assert(marked_text: &str, cx: &mut gpui::AppContext) { +// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); +// assert_eq!( +// previous_word_start(&snapshot, display_points[1]), +// display_points[0] +// ); +// } + +// assert("\nˇ ˇlorem", cx); +// assert("ˇ\nˇ lorem", cx); +// assert(" ˇloremˇ", cx); +// assert("ˇ ˇlorem", cx); +// assert(" ˇlorˇem", cx); +// assert("\nlorem\nˇ ˇipsum", cx); +// assert("\n\nˇ\nˇ", cx); +// assert(" ˇlorem ˇipsum", cx); +// assert("loremˇ-ˇipsum", cx); +// assert("loremˇ-#$@ˇipsum", cx); +// assert("ˇlorem_ˇipsum", cx); +// assert(" ˇdefγˇ", cx); +// assert(" ˇbcΔˇ", cx); +// assert(" abˇ——ˇcd", cx); +// } + +// #[gpui::test] +// fn test_previous_subword_start(cx: &mut gpui::AppContext) { +// init_test(cx); + +// fn assert(marked_text: &str, cx: &mut gpui::AppContext) { +// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); +// assert_eq!( +// previous_subword_start(&snapshot, display_points[1]), +// display_points[0] +// ); +// } + +// // Subword boundaries are respected +// assert("lorem_ˇipˇsum", cx); +// assert("lorem_ˇipsumˇ", cx); +// assert("ˇlorem_ˇipsum", cx); +// assert("lorem_ˇipsum_ˇdolor", cx); +// assert("loremˇIpˇsum", cx); +// assert("loremˇIpsumˇ", cx); + +// // Word boundaries are still respected +// assert("\nˇ ˇlorem", cx); +// assert(" ˇloremˇ", cx); +// assert(" ˇlorˇem", cx); +// assert("\nlorem\nˇ ˇipsum", cx); +// assert("\n\nˇ\nˇ", cx); +// assert(" ˇlorem ˇipsum", cx); +// assert("loremˇ-ˇipsum", cx); +// assert("loremˇ-#$@ˇipsum", cx); +// assert(" ˇdefγˇ", cx); +// assert(" bcˇΔˇ", cx); +// assert(" ˇbcδˇ", cx); +// assert(" abˇ——ˇcd", cx); +// } + +// #[gpui::test] +// fn test_find_preceding_boundary(cx: &mut gpui::AppContext) { +// init_test(cx); + +// fn assert( +// marked_text: &str, +// cx: &mut gpui::AppContext, +// is_boundary: impl FnMut(char, char) -> bool, +// ) { +// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); +// assert_eq!( +// find_preceding_boundary( +// &snapshot, +// display_points[1], +// FindRange::MultiLine, +// is_boundary +// ), +// display_points[0] +// ); +// } + +// assert("abcˇdef\ngh\nijˇk", cx, |left, right| { +// left == 'c' && right == 'd' +// }); +// assert("abcdef\nˇgh\nijˇk", cx, |left, right| { +// left == '\n' && right == 'g' +// }); +// let mut line_count = 0; +// assert("abcdef\nˇgh\nijˇk", cx, |left, _| { +// if left == '\n' { +// line_count += 1; +// line_count == 2 +// } else { +// false +// } +// }); +// } + +// #[gpui::test] +// fn test_find_preceding_boundary_with_inlays(cx: &mut gpui::AppContext) { +// init_test(cx); + +// let input_text = "abcdefghijklmnopqrstuvwxys"; +// let family_id = cx +// .font_cache() +// .load_family(&["Helvetica"], &Default::default()) +// .unwrap(); +// let font_id = cx +// .font_cache() +// .select_font(family_id, &Default::default()) +// .unwrap(); +// let font_size = 14.0; +// let buffer = MultiBuffer::build_simple(input_text, cx); +// let buffer_snapshot = buffer.read(cx).snapshot(cx); +// let display_map = +// cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx)); + +// // add all kinds of inlays between two word boundaries: we should be able to cross them all, when looking for another boundary +// let mut id = 0; +// let inlays = (0..buffer_snapshot.len()) +// .map(|offset| { +// [ +// Inlay { +// id: InlayId::Suggestion(post_inc(&mut id)), +// position: buffer_snapshot.anchor_at(offset, Bias::Left), +// text: format!("test").into(), +// }, +// Inlay { +// id: InlayId::Suggestion(post_inc(&mut id)), +// position: buffer_snapshot.anchor_at(offset, Bias::Right), +// text: format!("test").into(), +// }, +// Inlay { +// id: InlayId::Hint(post_inc(&mut id)), +// position: buffer_snapshot.anchor_at(offset, Bias::Left), +// text: format!("test").into(), +// }, +// Inlay { +// id: InlayId::Hint(post_inc(&mut id)), +// position: buffer_snapshot.anchor_at(offset, Bias::Right), +// text: format!("test").into(), +// }, +// ] +// }) +// .flatten() +// .collect(); +// let snapshot = display_map.update(cx, |map, cx| { +// map.splice_inlays(Vec::new(), inlays, cx); +// map.snapshot(cx) +// }); + +// assert_eq!( +// find_preceding_boundary( +// &snapshot, +// buffer_snapshot.len().to_display_point(&snapshot), +// FindRange::MultiLine, +// |left, _| left == 'e', +// ), +// snapshot +// .buffer_snapshot +// .offset_to_point(5) +// .to_display_point(&snapshot), +// "Should not stop at inlays when looking for boundaries" +// ); +// } + +// #[gpui::test] +// fn test_next_word_end(cx: &mut gpui::AppContext) { +// init_test(cx); + +// fn assert(marked_text: &str, cx: &mut gpui::AppContext) { +// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); +// assert_eq!( +// next_word_end(&snapshot, display_points[0]), +// display_points[1] +// ); +// } + +// assert("\nˇ loremˇ", cx); +// assert(" ˇloremˇ", cx); +// assert(" lorˇemˇ", cx); +// assert(" loremˇ ˇ\nipsum\n", cx); +// assert("\nˇ\nˇ\n\n", cx); +// assert("loremˇ ipsumˇ ", cx); +// assert("loremˇ-ˇipsum", cx); +// assert("loremˇ#$@-ˇipsum", cx); +// assert("loremˇ_ipsumˇ", cx); +// assert(" ˇbcΔˇ", cx); +// assert(" abˇ——ˇcd", cx); +// } + +// #[gpui::test] +// fn test_next_subword_end(cx: &mut gpui::AppContext) { +// init_test(cx); + +// fn assert(marked_text: &str, cx: &mut gpui::AppContext) { +// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); +// assert_eq!( +// next_subword_end(&snapshot, display_points[0]), +// display_points[1] +// ); +// } + +// // Subword boundaries are respected +// assert("loˇremˇ_ipsum", cx); +// assert("ˇloremˇ_ipsum", cx); +// assert("loremˇ_ipsumˇ", cx); +// assert("loremˇ_ipsumˇ_dolor", cx); +// assert("loˇremˇIpsum", cx); +// assert("loremˇIpsumˇDolor", cx); + +// // Word boundaries are still respected +// assert("\nˇ loremˇ", cx); +// assert(" ˇloremˇ", cx); +// assert(" lorˇemˇ", cx); +// assert(" loremˇ ˇ\nipsum\n", cx); +// assert("\nˇ\nˇ\n\n", cx); +// assert("loremˇ ipsumˇ ", cx); +// assert("loremˇ-ˇipsum", cx); +// assert("loremˇ#$@-ˇipsum", cx); +// assert("loremˇ_ipsumˇ", cx); +// assert(" ˇbcˇΔ", cx); +// assert(" abˇ——ˇcd", cx); +// } + +// #[gpui::test] +// fn test_find_boundary(cx: &mut gpui::AppContext) { +// init_test(cx); + +// fn assert( +// marked_text: &str, +// cx: &mut gpui::AppContext, +// is_boundary: impl FnMut(char, char) -> bool, +// ) { +// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); +// assert_eq!( +// find_boundary( +// &snapshot, +// display_points[0], +// FindRange::MultiLine, +// is_boundary +// ), +// display_points[1] +// ); +// } + +// assert("abcˇdef\ngh\nijˇk", cx, |left, right| { +// left == 'j' && right == 'k' +// }); +// assert("abˇcdef\ngh\nˇijk", cx, |left, right| { +// left == '\n' && right == 'i' +// }); +// let mut line_count = 0; +// assert("abcˇdef\ngh\nˇijk", cx, |left, _| { +// if left == '\n' { +// line_count += 1; +// line_count == 2 +// } else { +// false +// } +// }); +// } + +// #[gpui::test] +// fn test_surrounding_word(cx: &mut gpui::AppContext) { +// init_test(cx); + +// fn assert(marked_text: &str, cx: &mut gpui::AppContext) { +// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); +// assert_eq!( +// surrounding_word(&snapshot, display_points[1]), +// display_points[0]..display_points[2], +// "{}", +// marked_text.to_string() +// ); +// } + +// assert("ˇˇloremˇ ipsum", cx); +// assert("ˇloˇremˇ ipsum", cx); +// assert("ˇloremˇˇ ipsum", cx); +// assert("loremˇ ˇ ˇipsum", cx); +// assert("lorem\nˇˇˇ\nipsum", cx); +// assert("lorem\nˇˇipsumˇ", cx); +// assert("loremˇ,ˇˇ ipsum", cx); +// assert("ˇloremˇˇ, ipsum", cx); +// } + +// #[gpui::test] +// async fn test_move_up_and_down_with_excerpts(cx: &mut gpui::TestAppContext) { +// cx.update(|cx| { +// init_test(cx); +// }); + +// let mut cx = EditorTestContext::new(cx).await; +// let editor = cx.editor.clone(); +// let window = cx.window.clone(); +// cx.update_window(window, |cx| { +// let text_layout_details = +// editor.read_with(cx, |editor, cx| editor.text_layout_details(cx)); + +// let family_id = cx +// .font_cache() +// .load_family(&["Helvetica"], &Default::default()) +// .unwrap(); +// let font_id = cx +// .font_cache() +// .select_font(family_id, &Default::default()) +// .unwrap(); + +// let buffer = +// cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "abc\ndefg\nhijkl\nmn")); +// let multibuffer = cx.add_model(|cx| { +// let mut multibuffer = MultiBuffer::new(0); +// multibuffer.push_excerpts( +// buffer.clone(), +// [ +// ExcerptRange { +// context: Point::new(0, 0)..Point::new(1, 4), +// primary: None, +// }, +// ExcerptRange { +// context: Point::new(2, 0)..Point::new(3, 2), +// primary: None, +// }, +// ], +// cx, +// ); +// multibuffer +// }); +// let display_map = +// cx.add_model(|cx| DisplayMap::new(multibuffer, font_id, 14.0, None, 2, 2, cx)); +// let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx)); + +// assert_eq!(snapshot.text(), "\n\nabc\ndefg\n\n\nhijkl\nmn"); + +// let col_2_x = snapshot.x_for_point(DisplayPoint::new(2, 2), &text_layout_details); + +// // Can't move up into the first excerpt's header +// assert_eq!( +// up( +// &snapshot, +// DisplayPoint::new(2, 2), +// SelectionGoal::HorizontalPosition(col_2_x), +// false, +// &text_layout_details +// ), +// ( +// DisplayPoint::new(2, 0), +// SelectionGoal::HorizontalPosition(0.0) +// ), +// ); +// assert_eq!( +// up( +// &snapshot, +// DisplayPoint::new(2, 0), +// SelectionGoal::None, +// false, +// &text_layout_details +// ), +// ( +// DisplayPoint::new(2, 0), +// SelectionGoal::HorizontalPosition(0.0) +// ), +// ); + +// let col_4_x = snapshot.x_for_point(DisplayPoint::new(3, 4), &text_layout_details); + +// // Move up and down within first excerpt +// assert_eq!( +// up( +// &snapshot, +// DisplayPoint::new(3, 4), +// SelectionGoal::HorizontalPosition(col_4_x), +// false, +// &text_layout_details +// ), +// ( +// DisplayPoint::new(2, 3), +// SelectionGoal::HorizontalPosition(col_4_x) +// ), +// ); +// assert_eq!( +// down( +// &snapshot, +// DisplayPoint::new(2, 3), +// SelectionGoal::HorizontalPosition(col_4_x), +// false, +// &text_layout_details +// ), +// ( +// DisplayPoint::new(3, 4), +// SelectionGoal::HorizontalPosition(col_4_x) +// ), +// ); + +// let col_5_x = snapshot.x_for_point(DisplayPoint::new(6, 5), &text_layout_details); + +// // Move up and down across second excerpt's header +// assert_eq!( +// up( +// &snapshot, +// DisplayPoint::new(6, 5), +// SelectionGoal::HorizontalPosition(col_5_x), +// false, +// &text_layout_details +// ), +// ( +// DisplayPoint::new(3, 4), +// SelectionGoal::HorizontalPosition(col_5_x) +// ), +// ); +// assert_eq!( +// down( +// &snapshot, +// DisplayPoint::new(3, 4), +// SelectionGoal::HorizontalPosition(col_5_x), +// false, +// &text_layout_details +// ), +// ( +// DisplayPoint::new(6, 5), +// SelectionGoal::HorizontalPosition(col_5_x) +// ), +// ); + +// let max_point_x = snapshot.x_for_point(DisplayPoint::new(7, 2), &text_layout_details); + +// // Can't move down off the end +// assert_eq!( +// down( +// &snapshot, +// DisplayPoint::new(7, 0), +// SelectionGoal::HorizontalPosition(0.0), +// false, +// &text_layout_details +// ), +// ( +// DisplayPoint::new(7, 2), +// SelectionGoal::HorizontalPosition(max_point_x) +// ), +// ); +// assert_eq!( +// down( +// &snapshot, +// DisplayPoint::new(7, 2), +// SelectionGoal::HorizontalPosition(max_point_x), +// false, +// &text_layout_details +// ), +// ( +// DisplayPoint::new(7, 2), +// SelectionGoal::HorizontalPosition(max_point_x) +// ), +// ); +// }); +// } + +// fn init_test(cx: &mut gpui::AppContext) { +// cx.set_global(SettingsStore::test(cx)); +// theme::init(cx); +// language::init(cx); +// crate::init(cx); +// Project::init_settings(cx); +// } +// } diff --git a/crates/editor2/src/persistence.rs b/crates/editor2/src/persistence.rs new file mode 100644 index 0000000000..6e37735c13 --- /dev/null +++ b/crates/editor2/src/persistence.rs @@ -0,0 +1,83 @@ +use std::path::PathBuf; + +use db::sqlez_macros::sql; +use db::{define_connection, query}; + +use workspace::{ItemId, WorkspaceDb, WorkspaceId}; + +define_connection!( + // Current schema shape using pseudo-rust syntax: + // editors( + // item_id: usize, + // workspace_id: usize, + // path: PathBuf, + // scroll_top_row: usize, + // scroll_vertical_offset: f32, + // scroll_horizontal_offset: f32, + // ) + pub static ref DB: EditorDb = + &[sql! ( + CREATE TABLE editors( + item_id INTEGER NOT NULL, + workspace_id INTEGER NOT NULL, + path BLOB NOT NULL, + PRIMARY KEY(item_id, workspace_id), + FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) + ON DELETE CASCADE + ON UPDATE CASCADE + ) STRICT; + ), + sql! ( + ALTER TABLE editors ADD COLUMN scroll_top_row INTEGER NOT NULL DEFAULT 0; + ALTER TABLE editors ADD COLUMN scroll_horizontal_offset REAL NOT NULL DEFAULT 0; + ALTER TABLE editors ADD COLUMN scroll_vertical_offset REAL NOT NULL DEFAULT 0; + )]; +); + +impl EditorDb { + query! { + pub fn get_path(item_id: ItemId, workspace_id: WorkspaceId) -> Result> { + SELECT path FROM editors + WHERE item_id = ? AND workspace_id = ? + } + } + + query! { + pub async fn save_path(item_id: ItemId, workspace_id: WorkspaceId, path: PathBuf) -> Result<()> { + INSERT INTO editors + (item_id, workspace_id, path) + VALUES + (?1, ?2, ?3) + ON CONFLICT DO UPDATE SET + item_id = ?1, + workspace_id = ?2, + path = ?3 + } + } + + // Returns the scroll top row, and offset + query! { + pub fn get_scroll_position(item_id: ItemId, workspace_id: WorkspaceId) -> Result> { + SELECT scroll_top_row, scroll_horizontal_offset, scroll_vertical_offset + FROM editors + WHERE item_id = ? AND workspace_id = ? + } + } + + query! { + pub async fn save_scroll_position( + item_id: ItemId, + workspace_id: WorkspaceId, + top_row: u32, + vertical_offset: f32, + horizontal_offset: f32 + ) -> Result<()> { + UPDATE OR IGNORE editors + SET + scroll_top_row = ?3, + scroll_horizontal_offset = ?4, + scroll_vertical_offset = ?5 + WHERE item_id = ?1 AND workspace_id = ?2 + } + } +} diff --git a/crates/editor2/src/scroll.rs b/crates/editor2/src/scroll.rs new file mode 100644 index 0000000000..5e4b32265a --- /dev/null +++ b/crates/editor2/src/scroll.rs @@ -0,0 +1,448 @@ +pub mod actions; +pub mod autoscroll; +pub mod scroll_amount; + +use crate::{ + display_map::{DisplaySnapshot, ToDisplayPoint}, + hover_popover::hide_hover, + persistence::DB, + Anchor, DisplayPoint, Editor, EditorMode, Event, InlayHintRefreshReason, MultiBufferSnapshot, + ToPoint, +}; +use gpui::{point, px, AppContext, Entity, Pixels, Styled, Task, ViewContext}; +use language::{Bias, Point}; +use std::{ + cmp::Ordering, + time::{Duration, Instant}, +}; +use util::ResultExt; +use workspace::{ItemId, WorkspaceId}; + +use self::{ + autoscroll::{Autoscroll, AutoscrollStrategy}, + scroll_amount::ScrollAmount, +}; + +pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28); +pub const VERTICAL_SCROLL_MARGIN: f32 = 3.; +const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1); + +#[derive(Default)] +pub struct ScrollbarAutoHide(pub bool); + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct ScrollAnchor { + pub offset: gpui::Point, + pub anchor: Anchor, +} + +impl ScrollAnchor { + fn new() -> Self { + Self { + offset: gpui::Point::default(), + anchor: Anchor::min(), + } + } + + pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point { + let mut scroll_position = self.offset; + if self.anchor != Anchor::min() { + let scroll_top = self.anchor.to_display_point(snapshot).row() as f32; + scroll_position.y = scroll_top + scroll_position.y; + } else { + scroll_position.y = 0.; + } + scroll_position + } + + pub fn top_row(&self, buffer: &MultiBufferSnapshot) -> u32 { + self.anchor.to_point(buffer).row + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum Axis { + Vertical, + Horizontal, +} + +#[derive(Clone, Copy, Debug)] +pub struct OngoingScroll { + last_event: Instant, + axis: Option, +} + +impl OngoingScroll { + fn new() -> Self { + Self { + last_event: Instant::now() - SCROLL_EVENT_SEPARATION, + axis: None, + } + } + + pub fn filter(&self, delta: &mut gpui::Point) -> Option { + const UNLOCK_PERCENT: f32 = 1.9; + const UNLOCK_LOWER_BOUND: Pixels = px(6.); + let mut axis = self.axis; + + let x = delta.x.abs(); + let y = delta.y.abs(); + let duration = Instant::now().duration_since(self.last_event); + if duration > SCROLL_EVENT_SEPARATION { + //New ongoing scroll will start, determine axis + axis = if x <= y { + Some(Axis::Vertical) + } else { + Some(Axis::Horizontal) + }; + } else if x.max(y) >= UNLOCK_LOWER_BOUND { + //Check if the current ongoing will need to unlock + match axis { + Some(Axis::Vertical) => { + if x > y && x >= y * UNLOCK_PERCENT { + axis = None; + } + } + + Some(Axis::Horizontal) => { + if y > x && y >= x * UNLOCK_PERCENT { + axis = None; + } + } + + None => {} + } + } + + match axis { + Some(Axis::Vertical) => { + *delta = point(px(0.), delta.y); + } + Some(Axis::Horizontal) => { + *delta = point(delta.x, px(0.)); + } + None => {} + } + + axis + } +} + +pub struct ScrollManager { + vertical_scroll_margin: f32, + anchor: ScrollAnchor, + ongoing: OngoingScroll, + autoscroll_request: Option<(Autoscroll, bool)>, + last_autoscroll: Option<(gpui::Point, f32, f32, AutoscrollStrategy)>, + show_scrollbars: bool, + hide_scrollbar_task: Option>, + visible_line_count: Option, +} + +impl ScrollManager { + pub fn new() -> Self { + ScrollManager { + vertical_scroll_margin: VERTICAL_SCROLL_MARGIN, + anchor: ScrollAnchor::new(), + ongoing: OngoingScroll::new(), + autoscroll_request: None, + show_scrollbars: true, + hide_scrollbar_task: None, + last_autoscroll: None, + visible_line_count: None, + } + } + + pub fn clone_state(&mut self, other: &Self) { + self.anchor = other.anchor; + self.ongoing = other.ongoing; + } + + pub fn anchor(&self) -> ScrollAnchor { + self.anchor + } + + pub fn ongoing_scroll(&self) -> OngoingScroll { + self.ongoing + } + + pub fn update_ongoing_scroll(&mut self, axis: Option) { + self.ongoing.last_event = Instant::now(); + self.ongoing.axis = axis; + } + + pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point { + self.anchor.scroll_position(snapshot) + } + + fn set_scroll_position( + &mut self, + scroll_position: gpui::Point, + map: &DisplaySnapshot, + local: bool, + autoscroll: bool, + workspace_id: Option, + cx: &mut ViewContext, + ) { + let (new_anchor, top_row) = if scroll_position.y <= 0. { + ( + ScrollAnchor { + anchor: Anchor::min(), + offset: scroll_position.max(&gpui::Point::default()), + }, + 0, + ) + } else { + let scroll_top_buffer_point = + DisplayPoint::new(scroll_position.y as u32, 0).to_point(&map); + let top_anchor = map + .buffer_snapshot + .anchor_at(scroll_top_buffer_point, Bias::Right); + + ( + ScrollAnchor { + anchor: top_anchor, + offset: point( + scroll_position.x, + scroll_position.y - top_anchor.to_display_point(&map).row() as f32, + ), + }, + scroll_top_buffer_point.row, + ) + }; + + self.set_anchor(new_anchor, top_row, local, autoscroll, workspace_id, cx); + } + + fn set_anchor( + &mut self, + anchor: ScrollAnchor, + top_row: u32, + local: bool, + autoscroll: bool, + workspace_id: Option, + cx: &mut ViewContext, + ) { + self.anchor = anchor; + cx.emit(Event::ScrollPositionChanged { local, autoscroll }); + self.show_scrollbar(cx); + self.autoscroll_request.take(); + if let Some(workspace_id) = workspace_id { + let item_id = cx.view().entity_id().as_u64() as ItemId; + + cx.foreground_executor() + .spawn(async move { + DB.save_scroll_position( + item_id, + workspace_id, + top_row, + anchor.offset.x, + anchor.offset.y, + ) + .await + .log_err() + }) + .detach() + } + cx.notify(); + } + + pub fn show_scrollbar(&mut self, cx: &mut ViewContext) { + if !self.show_scrollbars { + self.show_scrollbars = true; + cx.notify(); + } + + if cx.default_global::().0 { + self.hide_scrollbar_task = Some(cx.spawn(|editor, mut cx| async move { + cx.background_executor() + .timer(SCROLLBAR_SHOW_INTERVAL) + .await; + editor + .update(&mut cx, |editor, cx| { + editor.scroll_manager.show_scrollbars = false; + cx.notify(); + }) + .log_err(); + })); + } else { + self.hide_scrollbar_task = None; + } + } + + pub fn scrollbars_visible(&self) -> bool { + self.show_scrollbars + } + + pub fn has_autoscroll_request(&self) -> bool { + self.autoscroll_request.is_some() + } + + pub fn clamp_scroll_left(&mut self, max: f32) -> bool { + if max < self.anchor.offset.x { + self.anchor.offset.x = max; + true + } else { + false + } + } +} + +// todo!() +impl Editor { + // pub fn vertical_scroll_margin(&mut self) -> usize { + // self.scroll_manager.vertical_scroll_margin as usize + // } + + // pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext) { + // self.scroll_manager.vertical_scroll_margin = margin_rows as f32; + // cx.notify(); + // } + + pub fn visible_line_count(&self) -> Option { + self.scroll_manager.visible_line_count + } + + // pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext) { + // let opened_first_time = self.scroll_manager.visible_line_count.is_none(); + // self.scroll_manager.visible_line_count = Some(lines); + // if opened_first_time { + // cx.spawn(|editor, mut cx| async move { + // editor + // .update(&mut cx, |editor, cx| { + // editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx) + // }) + // .ok() + // }) + // .detach() + // } + // } + + pub fn set_scroll_position( + &mut self, + scroll_position: gpui::Point, + cx: &mut ViewContext, + ) { + self.set_scroll_position_internal(scroll_position, true, false, cx); + } + + pub(crate) fn set_scroll_position_internal( + &mut self, + scroll_position: gpui::Point, + local: bool, + autoscroll: bool, + cx: &mut ViewContext, + ) { + let map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + + hide_hover(self, cx); + let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1); + self.scroll_manager.set_scroll_position( + scroll_position, + &map, + local, + autoscroll, + workspace_id, + cx, + ); + + self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); + } + + // pub fn scroll_position(&self, cx: &mut ViewContext) -> gpui::Point { + // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + // self.scroll_manager.anchor.scroll_position(&display_map) + // } + + pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext) { + hide_hover(self, cx); + let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1); + let top_row = scroll_anchor + .anchor + .to_point(&self.buffer().read(cx).snapshot(cx)) + .row; + self.scroll_manager + .set_anchor(scroll_anchor, top_row, true, false, workspace_id, cx); + } + + pub(crate) fn set_scroll_anchor_remote( + &mut self, + scroll_anchor: ScrollAnchor, + cx: &mut ViewContext, + ) { + hide_hover(self, cx); + let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1); + let top_row = scroll_anchor + .anchor + .to_point(&self.buffer().read(cx).snapshot(cx)) + .row; + self.scroll_manager + .set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx); + } + + // pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext) { + // if matches!(self.mode, EditorMode::SingleLine) { + // cx.propagate_action(); + // return; + // } + + // if self.take_rename(true, cx).is_some() { + // return; + // } + + // let cur_position = self.scroll_position(cx); + // let new_pos = cur_position + point(0., amount.lines(self)); + // self.set_scroll_position(new_pos, cx); + // } + + // /// Returns an ordering. The newest selection is: + // /// Ordering::Equal => on screen + // /// Ordering::Less => above the screen + // /// Ordering::Greater => below the screen + // pub fn newest_selection_on_screen(&self, cx: &mut AppContext) -> Ordering { + // let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + // let newest_head = self + // .selections + // .newest_anchor() + // .head() + // .to_display_point(&snapshot); + // let screen_top = self + // .scroll_manager + // .anchor + // .anchor + // .to_display_point(&snapshot); + + // if screen_top > newest_head { + // return Ordering::Less; + // } + + // if let Some(visible_lines) = self.visible_line_count() { + // if newest_head.row() < screen_top.row() + visible_lines as u32 { + // return Ordering::Equal; + // } + // } + + // Ordering::Greater + // } + + pub fn read_scroll_position_from_db( + &mut self, + item_id: usize, + workspace_id: WorkspaceId, + cx: &mut ViewContext, + ) { + let scroll_position = DB.get_scroll_position(item_id, workspace_id); + if let Ok(Some((top_row, x, y))) = scroll_position { + let top_anchor = self + .buffer() + .read(cx) + .snapshot(cx) + .anchor_at(Point::new(top_row as u32, 0), Bias::Left); + let scroll_anchor = ScrollAnchor { + offset: gpui::Point::new(x, y), + anchor: top_anchor, + }; + self.set_scroll_anchor(scroll_anchor, cx); + } + } +} diff --git a/crates/editor2/src/scroll/actions.rs b/crates/editor2/src/scroll/actions.rs new file mode 100644 index 0000000000..ba39d3849b --- /dev/null +++ b/crates/editor2/src/scroll/actions.rs @@ -0,0 +1,148 @@ +use gpui::AppContext; + +// actions!( +// editor, +// [ +// LineDown, +// LineUp, +// HalfPageDown, +// HalfPageUp, +// PageDown, +// PageUp, +// NextScreen, +// ScrollCursorTop, +// ScrollCursorCenter, +// ScrollCursorBottom, +// ] +// ); + +pub fn init(cx: &mut AppContext) { + // todo!() + // cx.add_action(Editor::next_screen); + // cx.add_action(Editor::scroll_cursor_top); + // cx.add_action(Editor::scroll_cursor_center); + // cx.add_action(Editor::scroll_cursor_bottom); + // cx.add_action(|this: &mut Editor, _: &LineDown, cx| { + // this.scroll_screen(&ScrollAmount::Line(1.), cx) + // }); + // cx.add_action(|this: &mut Editor, _: &LineUp, cx| { + // this.scroll_screen(&ScrollAmount::Line(-1.), cx) + // }); + // cx.add_action(|this: &mut Editor, _: &HalfPageDown, cx| { + // this.scroll_screen(&ScrollAmount::Page(0.5), cx) + // }); + // cx.add_action(|this: &mut Editor, _: &HalfPageUp, cx| { + // this.scroll_screen(&ScrollAmount::Page(-0.5), cx) + // }); + // cx.add_action(|this: &mut Editor, _: &PageDown, cx| { + // this.scroll_screen(&ScrollAmount::Page(1.), cx) + // }); + // cx.add_action(|this: &mut Editor, _: &PageUp, cx| { + // this.scroll_screen(&ScrollAmount::Page(-1.), cx) + // }); +} + +// impl Editor { +// pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext) -> Option<()> { +// if self.take_rename(true, cx).is_some() { +// return None; +// } + +// if self.mouse_context_menu.read(cx).visible() { +// return None; +// } + +// if matches!(self.mode, EditorMode::SingleLine) { +// cx.propagate_action(); +// return None; +// } +// self.request_autoscroll(Autoscroll::Next, cx); +// Some(()) +// } + +// pub fn scroll( +// &mut self, +// scroll_position: Vector2F, +// axis: Option, +// cx: &mut ViewContext, +// ) { +// self.scroll_manager.update_ongoing_scroll(axis); +// self.set_scroll_position(scroll_position, cx); +// } + +// fn scroll_cursor_top(editor: &mut Editor, _: &ScrollCursorTop, cx: &mut ViewContext) { +// let snapshot = editor.snapshot(cx).display_snapshot; +// let scroll_margin_rows = editor.vertical_scroll_margin() as u32; + +// let mut new_screen_top = editor.selections.newest_display(cx).head(); +// *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(scroll_margin_rows); +// *new_screen_top.column_mut() = 0; +// let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left); +// let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top); + +// editor.set_scroll_anchor( +// ScrollAnchor { +// anchor: new_anchor, +// offset: Default::default(), +// }, +// cx, +// ) +// } + +// fn scroll_cursor_center( +// editor: &mut Editor, +// _: &ScrollCursorCenter, +// cx: &mut ViewContext, +// ) { +// let snapshot = editor.snapshot(cx).display_snapshot; +// let visible_rows = if let Some(visible_rows) = editor.visible_line_count() { +// visible_rows as u32 +// } else { +// return; +// }; + +// let mut new_screen_top = editor.selections.newest_display(cx).head(); +// *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(visible_rows / 2); +// *new_screen_top.column_mut() = 0; +// let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left); +// let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top); + +// editor.set_scroll_anchor( +// ScrollAnchor { +// anchor: new_anchor, +// offset: Default::default(), +// }, +// cx, +// ) +// } + +// fn scroll_cursor_bottom( +// editor: &mut Editor, +// _: &ScrollCursorBottom, +// cx: &mut ViewContext, +// ) { +// let snapshot = editor.snapshot(cx).display_snapshot; +// let scroll_margin_rows = editor.vertical_scroll_margin() as u32; +// let visible_rows = if let Some(visible_rows) = editor.visible_line_count() { +// visible_rows as u32 +// } else { +// return; +// }; + +// let mut new_screen_top = editor.selections.newest_display(cx).head(); +// *new_screen_top.row_mut() = new_screen_top +// .row() +// .saturating_sub(visible_rows.saturating_sub(scroll_margin_rows)); +// *new_screen_top.column_mut() = 0; +// let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left); +// let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top); + +// editor.set_scroll_anchor( +// ScrollAnchor { +// anchor: new_anchor, +// offset: Default::default(), +// }, +// cx, +// ) +// } +// } diff --git a/crates/editor2/src/scroll/autoscroll.rs b/crates/editor2/src/scroll/autoscroll.rs new file mode 100644 index 0000000000..a4c37a258e --- /dev/null +++ b/crates/editor2/src/scroll/autoscroll.rs @@ -0,0 +1,253 @@ +use std::{cmp, f32}; + +use gpui::{px, Pixels, ViewContext}; +use language::Point; + +use crate::{display_map::ToDisplayPoint, Editor, EditorMode, LineWithInvisibles}; + +#[derive(PartialEq, Eq)] +pub enum Autoscroll { + Next, + Strategy(AutoscrollStrategy), +} + +impl Autoscroll { + pub fn fit() -> Self { + Self::Strategy(AutoscrollStrategy::Fit) + } + + pub fn newest() -> Self { + Self::Strategy(AutoscrollStrategy::Newest) + } + + pub fn center() -> Self { + Self::Strategy(AutoscrollStrategy::Center) + } +} + +#[derive(PartialEq, Eq, Default)] +pub enum AutoscrollStrategy { + Fit, + Newest, + #[default] + Center, + Top, + Bottom, +} + +impl AutoscrollStrategy { + fn next(&self) -> Self { + match self { + AutoscrollStrategy::Center => AutoscrollStrategy::Top, + AutoscrollStrategy::Top => AutoscrollStrategy::Bottom, + _ => AutoscrollStrategy::Center, + } + } +} + +impl Editor { + pub fn autoscroll_vertically( + &mut self, + viewport_height: f32, + line_height: f32, + cx: &mut ViewContext, + ) -> bool { + let visible_lines = viewport_height / line_height; + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let mut scroll_position = self.scroll_manager.scroll_position(&display_map); + 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); + self.set_scroll_position(scroll_position, cx); + } + + let Some((autoscroll, local)) = self.scroll_manager.autoscroll_request.take() else { + return false; + }; + + let mut target_top; + let mut target_bottom; + if let Some(highlighted_rows) = &self.highlighted_rows { + target_top = highlighted_rows.start as f32; + target_bottom = target_top + 1.; + } else { + let selections = self.selections.all::(cx); + target_top = selections + .first() + .unwrap() + .head() + .to_display_point(&display_map) + .row() as f32; + target_bottom = selections + .last() + .unwrap() + .head() + .to_display_point(&display_map) + .row() as f32 + + 1.0; + + // If the selections can't all fit on screen, scroll to the newest. + if autoscroll == Autoscroll::newest() + || autoscroll == Autoscroll::fit() && target_bottom - target_top > visible_lines + { + let newest_selection_top = selections + .iter() + .max_by_key(|s| s.id) + .unwrap() + .head() + .to_display_point(&display_map) + .row() as f32; + target_top = newest_selection_top; + target_bottom = newest_selection_top + 1.; + } + } + + let margin = if matches!(self.mode, EditorMode::AutoHeight { .. }) { + 0. + } else { + ((visible_lines - (target_bottom - target_top)) / 2.0).floor() + }; + + let strategy = match autoscroll { + Autoscroll::Strategy(strategy) => strategy, + Autoscroll::Next => { + let last_autoscroll = &self.scroll_manager.last_autoscroll; + if let Some(last_autoscroll) = last_autoscroll { + if self.scroll_manager.anchor.offset == last_autoscroll.0 + && target_top == last_autoscroll.1 + && target_bottom == last_autoscroll.2 + { + last_autoscroll.3.next() + } else { + AutoscrollStrategy::default() + } + } else { + AutoscrollStrategy::default() + } + } + }; + + match strategy { + AutoscrollStrategy::Fit | AutoscrollStrategy::Newest => { + let margin = margin.min(self.scroll_manager.vertical_scroll_margin); + let target_top = (target_top - margin).max(0.0); + let target_bottom = target_bottom + margin; + let start_row = scroll_position.y; + let end_row = start_row + visible_lines; + + let needs_scroll_up = target_top < start_row; + let needs_scroll_down = target_bottom >= end_row; + + if needs_scroll_up && !needs_scroll_down { + scroll_position.y = (target_top); + self.set_scroll_position_internal(scroll_position, local, true, cx); + } + if !needs_scroll_up && needs_scroll_down { + scroll_position.y = (target_bottom - visible_lines); + self.set_scroll_position_internal(scroll_position, local, true, cx); + } + } + AutoscrollStrategy::Center => { + scroll_position.y = ((target_top - margin).max(0.0)); + self.set_scroll_position_internal(scroll_position, local, true, cx); + } + AutoscrollStrategy::Top => { + scroll_position.y = ((target_top).max(0.0)); + self.set_scroll_position_internal(scroll_position, local, true, cx); + } + AutoscrollStrategy::Bottom => { + scroll_position.y = ((target_bottom - visible_lines).max(0.0)); + self.set_scroll_position_internal(scroll_position, local, true, cx); + } + } + + self.scroll_manager.last_autoscroll = Some(( + self.scroll_manager.anchor.offset, + target_top, + target_bottom, + strategy, + )); + + true + } + + pub fn autoscroll_horizontally( + &mut self, + start_row: u32, + viewport_width: Pixels, + scroll_width: Pixels, + max_glyph_width: Pixels, + layouts: &[LineWithInvisibles], + cx: &mut ViewContext, + ) -> bool { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let selections = self.selections.all::(cx); + + let mut target_left; + let mut target_right; + + if self.highlighted_rows.is_some() { + target_left = px(0.); + target_right = px(0.); + } else { + target_left = px(f32::INFINITY); + target_right = px(0.); + for selection in selections { + let head = selection.head().to_display_point(&display_map); + 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] + .line + .x_for_index(start_column as usize), + ); + target_right = target_right.max( + layouts[(head.row() - start_row) as usize] + .line + .x_for_index(end_column as usize) + + max_glyph_width, + ); + } + } + } + + target_right = target_right.min(scroll_width); + + if target_right - target_left > viewport_width { + return false; + } + + let scroll_left = self.scroll_manager.anchor.offset.x * max_glyph_width; + let scroll_right = scroll_left + viewport_width; + + if target_left < scroll_left { + self.scroll_manager.anchor.offset.x = (target_left / max_glyph_width).into(); + true + } else if target_right > scroll_right { + self.scroll_manager.anchor.offset.x = + ((target_right - viewport_width) / max_glyph_width).into(); + true + } else { + false + } + } + + pub fn request_autoscroll(&mut self, autoscroll: Autoscroll, cx: &mut ViewContext) { + self.scroll_manager.autoscroll_request = Some((autoscroll, true)); + cx.notify(); + } + + pub(crate) fn request_autoscroll_remotely( + &mut self, + autoscroll: Autoscroll, + cx: &mut ViewContext, + ) { + self.scroll_manager.autoscroll_request = Some((autoscroll, false)); + cx.notify(); + } +} diff --git a/crates/editor2/src/scroll/scroll_amount.rs b/crates/editor2/src/scroll/scroll_amount.rs new file mode 100644 index 0000000000..89d188e324 --- /dev/null +++ b/crates/editor2/src/scroll/scroll_amount.rs @@ -0,0 +1,29 @@ +use crate::Editor; +use serde::Deserialize; + +#[derive(Clone, PartialEq, Deserialize)] +pub enum ScrollAmount { + // Scroll N lines (positive is towards the end of the document) + Line(f32), + // Scroll N pages (positive is towards the end of the document) + Page(f32), +} + +impl ScrollAmount { + pub fn lines(&self, editor: &mut Editor) -> f32 { + todo!() + // match self { + // Self::Line(count) => *count, + // Self::Page(count) => editor + // .visible_line_count() + // .map(|mut l| { + // // for full pages subtract one to leave an anchor line + // if count.abs() == 1.0 { + // l -= 1.0 + // } + // (l * count).trunc() + // }) + // .unwrap_or(0.), + // } + } +} diff --git a/crates/editor2/src/selections_collection.rs b/crates/editor2/src/selections_collection.rs new file mode 100644 index 0000000000..2762fd369e --- /dev/null +++ b/crates/editor2/src/selections_collection.rs @@ -0,0 +1,887 @@ +use std::{ + cell::Ref, + iter, mem, + ops::{Deref, DerefMut, Range, Sub}, + sync::Arc, +}; + +use collections::HashMap; +use gpui::{AppContext, Model, Pixels}; +use itertools::Itertools; +use language::{Bias, Point, Selection, SelectionGoal, TextDimension, ToPoint}; +use util::post_inc; + +use crate::{ + display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint}, + movement::TextLayoutDetails, + Anchor, DisplayPoint, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode, ToOffset, +}; + +#[derive(Debug, Clone)] +pub struct PendingSelection { + pub selection: Selection, + pub mode: SelectMode, +} + +#[derive(Debug, Clone)] +pub struct SelectionsCollection { + display_map: Model, + buffer: Model, + pub next_selection_id: usize, + pub line_mode: bool, + disjoint: Arc<[Selection]>, + pending: Option, +} + +impl SelectionsCollection { + pub fn new(display_map: Model, buffer: Model) -> Self { + Self { + display_map, + buffer, + next_selection_id: 1, + line_mode: false, + disjoint: Arc::from([]), + pending: Some(PendingSelection { + selection: Selection { + id: 0, + start: Anchor::min(), + end: Anchor::min(), + reversed: false, + goal: SelectionGoal::None, + }, + mode: SelectMode::Character, + }), + } + } + + pub fn display_map(&self, cx: &mut AppContext) -> DisplaySnapshot { + self.display_map.update(cx, |map, cx| map.snapshot(cx)) + } + + fn buffer<'a>(&self, cx: &'a AppContext) -> Ref<'a, MultiBufferSnapshot> { + self.buffer.read(cx).read(cx) + } + + pub fn clone_state(&mut self, other: &SelectionsCollection) { + self.next_selection_id = other.next_selection_id; + self.line_mode = other.line_mode; + self.disjoint = other.disjoint.clone(); + self.pending = other.pending.clone(); + } + + pub fn count(&self) -> usize { + let mut count = self.disjoint.len(); + if self.pending.is_some() { + count += 1; + } + count + } + + /// The non-pending, non-overlapping selections. There could still be a pending + /// selection that overlaps these if the mouse is being dragged, etc. Returned as + /// selections over Anchors. + pub fn disjoint_anchors(&self) -> Arc<[Selection]> { + self.disjoint.clone() + } + + pub fn pending_anchor(&self) -> Option> { + self.pending + .as_ref() + .map(|pending| pending.selection.clone()) + } + + pub fn pending>( + &self, + cx: &AppContext, + ) -> Option> { + self.pending_anchor() + .as_ref() + .map(|pending| pending.map(|p| p.summary::(&self.buffer(cx)))) + } + + pub fn pending_mode(&self) -> Option { + self.pending.as_ref().map(|pending| pending.mode.clone()) + } + + pub fn all<'a, D>(&self, cx: &AppContext) -> Vec> + where + D: 'a + TextDimension + Ord + Sub + std::fmt::Debug, + { + let disjoint_anchors = &self.disjoint; + let mut disjoint = + resolve_multiple::(disjoint_anchors.iter(), &self.buffer(cx)).peekable(); + + let mut pending_opt = self.pending::(cx); + + iter::from_fn(move || { + if let Some(pending) = pending_opt.as_mut() { + while let Some(next_selection) = disjoint.peek() { + if pending.start <= next_selection.end && pending.end >= next_selection.start { + let next_selection = disjoint.next().unwrap(); + if next_selection.start < pending.start { + pending.start = next_selection.start; + } + if next_selection.end > pending.end { + pending.end = next_selection.end; + } + } else if next_selection.end < pending.start { + return disjoint.next(); + } else { + break; + } + } + + pending_opt.take() + } else { + disjoint.next() + } + }) + .collect() + } + + /// Returns all of the selections, adjusted to take into account the selection line_mode + pub fn all_adjusted(&self, cx: &mut AppContext) -> Vec> { + let mut selections = self.all::(cx); + if self.line_mode { + let map = self.display_map(cx); + for selection in &mut selections { + let new_range = map.expand_to_line(selection.range()); + selection.start = new_range.start; + selection.end = new_range.end; + } + } + selections + } + + pub fn all_adjusted_display( + &self, + cx: &mut AppContext, + ) -> (DisplaySnapshot, Vec>) { + if self.line_mode { + let selections = self.all::(cx); + let map = self.display_map(cx); + let result = selections + .into_iter() + .map(|mut selection| { + let new_range = map.expand_to_line(selection.range()); + selection.start = new_range.start; + selection.end = new_range.end; + selection.map(|point| point.to_display_point(&map)) + }) + .collect(); + (map, result) + } else { + self.all_display(cx) + } + } + + pub fn disjoint_in_range<'a, D>( + &self, + range: Range, + cx: &AppContext, + ) -> Vec> + where + D: 'a + TextDimension + Ord + Sub + std::fmt::Debug, + { + let buffer = self.buffer(cx); + let start_ix = match self + .disjoint + .binary_search_by(|probe| probe.end.cmp(&range.start, &buffer)) + { + Ok(ix) | Err(ix) => ix, + }; + let end_ix = match self + .disjoint + .binary_search_by(|probe| probe.start.cmp(&range.end, &buffer)) + { + Ok(ix) => ix + 1, + Err(ix) => ix, + }; + resolve_multiple(&self.disjoint[start_ix..end_ix], &buffer).collect() + } + + pub fn all_display( + &self, + cx: &mut AppContext, + ) -> (DisplaySnapshot, Vec>) { + let display_map = self.display_map(cx); + let selections = self + .all::(cx) + .into_iter() + .map(|selection| selection.map(|point| point.to_display_point(&display_map))) + .collect(); + (display_map, selections) + } + + pub fn newest_anchor(&self) -> &Selection { + self.pending + .as_ref() + .map(|s| &s.selection) + .or_else(|| self.disjoint.iter().max_by_key(|s| s.id)) + .unwrap() + } + + pub fn newest>( + &self, + cx: &AppContext, + ) -> Selection { + resolve(self.newest_anchor(), &self.buffer(cx)) + } + + pub fn newest_display(&self, cx: &mut AppContext) -> Selection { + let display_map = self.display_map(cx); + let selection = self + .newest_anchor() + .map(|point| point.to_display_point(&display_map)); + selection + } + + pub fn oldest_anchor(&self) -> &Selection { + self.disjoint + .iter() + .min_by_key(|s| s.id) + .or_else(|| self.pending.as_ref().map(|p| &p.selection)) + .unwrap() + } + + pub fn oldest>( + &self, + cx: &AppContext, + ) -> Selection { + resolve(self.oldest_anchor(), &self.buffer(cx)) + } + + pub fn first_anchor(&self) -> Selection { + self.disjoint[0].clone() + } + + pub fn first>( + &self, + cx: &AppContext, + ) -> Selection { + self.all(cx).first().unwrap().clone() + } + + pub fn last>( + &self, + cx: &AppContext, + ) -> Selection { + self.all(cx).last().unwrap().clone() + } + + #[cfg(any(test, feature = "test-support"))] + pub fn ranges + std::fmt::Debug>( + &self, + cx: &AppContext, + ) -> Vec> { + self.all::(cx) + .iter() + .map(|s| { + if s.reversed { + s.end.clone()..s.start.clone() + } else { + s.start.clone()..s.end.clone() + } + }) + .collect() + } + + #[cfg(any(test, feature = "test-support"))] + pub fn display_ranges(&self, cx: &mut AppContext) -> Vec> { + let display_map = self.display_map(cx); + self.disjoint_anchors() + .iter() + .chain(self.pending_anchor().as_ref()) + .map(|s| { + if s.reversed { + s.end.to_display_point(&display_map)..s.start.to_display_point(&display_map) + } else { + s.start.to_display_point(&display_map)..s.end.to_display_point(&display_map) + } + }) + .collect() + } + + // pub fn build_columnar_selection( + // &mut self, + // display_map: &DisplaySnapshot, + // row: u32, + // positions: &Range, + // reversed: bool, + // text_layout_details: &TextLayoutDetails, + // ) -> Option> { + // let is_empty = positions.start == positions.end; + // let line_len = display_map.line_len(row); + + // let layed_out_line = display_map.lay_out_line_for_row(row, &text_layout_details); + + // let start_col = layed_out_line.closest_index_for_x(positions.start) as u32; + // if start_col < line_len || (is_empty && positions.start == layed_out_line.width()) { + // let start = DisplayPoint::new(row, start_col); + // let end_col = layed_out_line.closest_index_for_x(positions.end) as u32; + // let end = DisplayPoint::new(row, end_col); + + // Some(Selection { + // id: post_inc(&mut self.next_selection_id), + // start: start.to_point(display_map), + // end: end.to_point(display_map), + // reversed, + // goal: SelectionGoal::HorizontalRange { + // start: positions.start, + // end: positions.end, + // }, + // }) + // } else { + // None + // } + // } + + pub(crate) fn change_with( + &mut self, + cx: &mut AppContext, + change: impl FnOnce(&mut MutableSelectionsCollection) -> R, + ) -> (bool, R) { + let mut mutable_collection = MutableSelectionsCollection { + collection: self, + selections_changed: false, + cx, + }; + + let result = change(&mut mutable_collection); + assert!( + !mutable_collection.disjoint.is_empty() || mutable_collection.pending.is_some(), + "There must be at least one selection" + ); + (mutable_collection.selections_changed, result) + } +} + +pub struct MutableSelectionsCollection<'a> { + collection: &'a mut SelectionsCollection, + selections_changed: bool, + cx: &'a mut AppContext, +} + +impl<'a> MutableSelectionsCollection<'a> { + pub fn display_map(&mut self) -> DisplaySnapshot { + self.collection.display_map(self.cx) + } + + fn buffer(&self) -> Ref { + self.collection.buffer(self.cx) + } + + pub fn clear_disjoint(&mut self) { + self.collection.disjoint = Arc::from([]); + } + + pub fn delete(&mut self, selection_id: usize) { + let mut changed = false; + self.collection.disjoint = self + .disjoint + .iter() + .filter(|selection| { + let found = selection.id == selection_id; + changed |= found; + !found + }) + .cloned() + .collect(); + + self.selections_changed |= changed; + } + + pub fn clear_pending(&mut self) { + if self.collection.pending.is_some() { + self.collection.pending = None; + self.selections_changed = true; + } + } + + pub fn set_pending_anchor_range(&mut self, range: Range, mode: SelectMode) { + self.collection.pending = Some(PendingSelection { + selection: Selection { + id: post_inc(&mut self.collection.next_selection_id), + start: range.start, + end: range.end, + reversed: false, + goal: SelectionGoal::None, + }, + mode, + }); + self.selections_changed = true; + } + + pub fn set_pending_display_range(&mut self, range: Range, mode: SelectMode) { + let (start, end, reversed) = { + let display_map = self.display_map(); + let buffer = self.buffer(); + let mut start = range.start; + let mut end = range.end; + let reversed = if start > end { + mem::swap(&mut start, &mut end); + true + } else { + false + }; + + let end_bias = if end > start { Bias::Left } else { Bias::Right }; + ( + buffer.anchor_before(start.to_point(&display_map)), + buffer.anchor_at(end.to_point(&display_map), end_bias), + reversed, + ) + }; + + let new_pending = PendingSelection { + selection: Selection { + id: post_inc(&mut self.collection.next_selection_id), + start, + end, + reversed, + goal: SelectionGoal::None, + }, + mode, + }; + + self.collection.pending = Some(new_pending); + self.selections_changed = true; + } + + pub fn set_pending(&mut self, selection: Selection, mode: SelectMode) { + self.collection.pending = Some(PendingSelection { selection, mode }); + self.selections_changed = true; + } + + pub fn try_cancel(&mut self) -> bool { + if let Some(pending) = self.collection.pending.take() { + if self.disjoint.is_empty() { + self.collection.disjoint = Arc::from([pending.selection]); + } + self.selections_changed = true; + return true; + } + + let mut oldest = self.oldest_anchor().clone(); + if self.count() > 1 { + self.collection.disjoint = Arc::from([oldest]); + self.selections_changed = true; + return true; + } + + if !oldest.start.cmp(&oldest.end, &self.buffer()).is_eq() { + let head = oldest.head(); + oldest.start = head.clone(); + oldest.end = head; + self.collection.disjoint = Arc::from([oldest]); + self.selections_changed = true; + return true; + } + + false + } + + pub fn insert_range(&mut self, range: Range) + where + T: 'a + ToOffset + ToPoint + TextDimension + Ord + Sub + std::marker::Copy, + { + let mut selections = self.all(self.cx); + let mut start = range.start.to_offset(&self.buffer()); + let mut end = range.end.to_offset(&self.buffer()); + let reversed = if start > end { + mem::swap(&mut start, &mut end); + true + } else { + false + }; + selections.push(Selection { + id: post_inc(&mut self.collection.next_selection_id), + start, + end, + reversed, + goal: SelectionGoal::None, + }); + self.select(selections); + } + + pub fn select(&mut self, mut selections: Vec>) + where + T: ToOffset + ToPoint + Ord + std::marker::Copy + std::fmt::Debug, + { + let buffer = self.buffer.read(self.cx).snapshot(self.cx); + selections.sort_unstable_by_key(|s| s.start); + // Merge overlapping selections. + let mut i = 1; + while i < selections.len() { + if selections[i - 1].end >= selections[i].start { + let removed = selections.remove(i); + if removed.start < selections[i - 1].start { + selections[i - 1].start = removed.start; + } + if removed.end > selections[i - 1].end { + selections[i - 1].end = removed.end; + } + } else { + i += 1; + } + } + + self.collection.disjoint = Arc::from_iter(selections.into_iter().map(|selection| { + let end_bias = if selection.end > selection.start { + Bias::Left + } else { + Bias::Right + }; + Selection { + id: selection.id, + start: buffer.anchor_after(selection.start), + end: buffer.anchor_at(selection.end, end_bias), + reversed: selection.reversed, + goal: selection.goal, + } + })); + + self.collection.pending = None; + self.selections_changed = true; + } + + pub fn select_anchors(&mut self, selections: Vec>) { + let buffer = self.buffer.read(self.cx).snapshot(self.cx); + let resolved_selections = + resolve_multiple::(&selections, &buffer).collect::>(); + self.select(resolved_selections); + } + + pub fn select_ranges(&mut self, ranges: I) + where + I: IntoIterator>, + T: ToOffset, + { + let buffer = self.buffer.read(self.cx).snapshot(self.cx); + let ranges = ranges + .into_iter() + .map(|range| range.start.to_offset(&buffer)..range.end.to_offset(&buffer)); + self.select_offset_ranges(ranges); + } + + fn select_offset_ranges(&mut self, ranges: I) + where + I: IntoIterator>, + { + let selections = ranges + .into_iter() + .map(|range| { + let mut start = range.start; + let mut end = range.end; + let reversed = if start > end { + mem::swap(&mut start, &mut end); + true + } else { + false + }; + Selection { + id: post_inc(&mut self.collection.next_selection_id), + start, + end, + reversed, + goal: SelectionGoal::None, + } + }) + .collect::>(); + + self.select(selections) + } + + pub fn select_anchor_ranges>>(&mut self, ranges: I) { + todo!() + // let buffer = self.buffer.read(self.cx).snapshot(self.cx); + // let selections = ranges + // .into_iter() + // .map(|range| { + // let mut start = range.start; + // let mut end = range.end; + // let reversed = if start.cmp(&end, &buffer).is_gt() { + // mem::swap(&mut start, &mut end); + // true + // } else { + // false + // }; + // Selection { + // id: post_inc(&mut self.collection.next_selection_id), + // start, + // end, + // reversed, + // goal: SelectionGoal::None, + // } + // }) + // .collect::>(); + + // self.select_anchors(selections) + } + + pub fn new_selection_id(&mut self) -> usize { + post_inc(&mut self.next_selection_id) + } + + pub fn select_display_ranges(&mut self, ranges: T) + where + T: IntoIterator>, + { + let display_map = self.display_map(); + let selections = ranges + .into_iter() + .map(|range| { + let mut start = range.start; + let mut end = range.end; + let reversed = if start > end { + mem::swap(&mut start, &mut end); + true + } else { + false + }; + Selection { + id: post_inc(&mut self.collection.next_selection_id), + start: start.to_point(&display_map), + end: end.to_point(&display_map), + reversed, + goal: SelectionGoal::None, + } + }) + .collect(); + self.select(selections); + } + + pub fn move_with( + &mut self, + mut move_selection: impl FnMut(&DisplaySnapshot, &mut Selection), + ) { + let mut changed = false; + let display_map = self.display_map(); + let selections = self + .all::(self.cx) + .into_iter() + .map(|selection| { + let mut moved_selection = + selection.map(|point| point.to_display_point(&display_map)); + move_selection(&display_map, &mut moved_selection); + let moved_selection = + moved_selection.map(|display_point| display_point.to_point(&display_map)); + if selection != moved_selection { + changed = true; + } + moved_selection + }) + .collect(); + + if changed { + self.select(selections) + } + } + + pub fn move_offsets_with( + &mut self, + mut move_selection: impl FnMut(&MultiBufferSnapshot, &mut Selection), + ) { + let mut changed = false; + let snapshot = self.buffer().clone(); + let selections = self + .all::(self.cx) + .into_iter() + .map(|selection| { + let mut moved_selection = selection.clone(); + move_selection(&snapshot, &mut moved_selection); + if selection != moved_selection { + changed = true; + } + moved_selection + }) + .collect(); + drop(snapshot); + + if changed { + self.select(selections) + } + } + + pub fn move_heads_with( + &mut self, + mut update_head: impl FnMut( + &DisplaySnapshot, + DisplayPoint, + SelectionGoal, + ) -> (DisplayPoint, SelectionGoal), + ) { + self.move_with(|map, selection| { + let (new_head, new_goal) = update_head(map, selection.head(), selection.goal); + selection.set_head(new_head, new_goal); + }); + } + + pub fn move_cursors_with( + &mut self, + mut update_cursor_position: impl FnMut( + &DisplaySnapshot, + DisplayPoint, + SelectionGoal, + ) -> (DisplayPoint, SelectionGoal), + ) { + self.move_with(|map, selection| { + let (cursor, new_goal) = update_cursor_position(map, selection.head(), selection.goal); + selection.collapse_to(cursor, new_goal) + }); + } + + pub fn maybe_move_cursors_with( + &mut self, + mut update_cursor_position: impl FnMut( + &DisplaySnapshot, + DisplayPoint, + SelectionGoal, + ) -> Option<(DisplayPoint, SelectionGoal)>, + ) { + self.move_cursors_with(|map, point, goal| { + update_cursor_position(map, point, goal).unwrap_or((point, goal)) + }) + } + + pub fn replace_cursors_with( + &mut self, + mut find_replacement_cursors: impl FnMut(&DisplaySnapshot) -> Vec, + ) { + let display_map = self.display_map(); + let new_selections = find_replacement_cursors(&display_map) + .into_iter() + .map(|cursor| { + let cursor_point = cursor.to_point(&display_map); + Selection { + id: post_inc(&mut self.collection.next_selection_id), + start: cursor_point, + end: cursor_point, + reversed: false, + goal: SelectionGoal::None, + } + }) + .collect(); + self.select(new_selections); + } + + /// Compute new ranges for any selections that were located in excerpts that have + /// since been removed. + /// + /// Returns a `HashMap` indicating which selections whose former head position + /// was no longer present. The keys of the map are selection ids. The values are + /// the id of the new excerpt where the head of the selection has been moved. + pub fn refresh(&mut self) -> HashMap { + let mut pending = self.collection.pending.take(); + let mut selections_with_lost_position = HashMap::default(); + + let anchors_with_status = { + let buffer = self.buffer(); + let disjoint_anchors = self + .disjoint + .iter() + .flat_map(|selection| [&selection.start, &selection.end]); + buffer.refresh_anchors(disjoint_anchors) + }; + let adjusted_disjoint: Vec<_> = anchors_with_status + .chunks(2) + .map(|selection_anchors| { + let (anchor_ix, start, kept_start) = selection_anchors[0].clone(); + let (_, end, kept_end) = selection_anchors[1].clone(); + let selection = &self.disjoint[anchor_ix / 2]; + let kept_head = if selection.reversed { + kept_start + } else { + kept_end + }; + if !kept_head { + selections_with_lost_position.insert(selection.id, selection.head().excerpt_id); + } + + Selection { + id: selection.id, + start, + end, + reversed: selection.reversed, + goal: selection.goal, + } + }) + .collect(); + + if !adjusted_disjoint.is_empty() { + let resolved_selections = + resolve_multiple(adjusted_disjoint.iter(), &self.buffer()).collect(); + self.select::(resolved_selections); + } + + if let Some(pending) = pending.as_mut() { + let buffer = self.buffer(); + let anchors = + buffer.refresh_anchors([&pending.selection.start, &pending.selection.end]); + let (_, start, kept_start) = anchors[0].clone(); + let (_, end, kept_end) = anchors[1].clone(); + let kept_head = if pending.selection.reversed { + kept_start + } else { + kept_end + }; + if !kept_head { + selections_with_lost_position + .insert(pending.selection.id, pending.selection.head().excerpt_id); + } + + pending.selection.start = start; + pending.selection.end = end; + } + self.collection.pending = pending; + self.selections_changed = true; + + selections_with_lost_position + } +} + +impl<'a> Deref for MutableSelectionsCollection<'a> { + type Target = SelectionsCollection; + fn deref(&self) -> &Self::Target { + self.collection + } +} + +impl<'a> DerefMut for MutableSelectionsCollection<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.collection + } +} + +// Panics if passed selections are not in order +pub fn resolve_multiple<'a, D, I>( + selections: I, + snapshot: &MultiBufferSnapshot, +) -> impl 'a + Iterator> +where + D: TextDimension + Ord + Sub + std::fmt::Debug, + I: 'a + IntoIterator>, +{ + let (to_summarize, selections) = selections.into_iter().tee(); + let mut summaries = snapshot + .summaries_for_anchors::( + to_summarize + .flat_map(|s| [&s.start, &s.end]) + .collect::>(), + ) + .into_iter(); + selections.map(move |s| Selection { + id: s.id, + start: summaries.next().unwrap(), + end: summaries.next().unwrap(), + reversed: s.reversed, + goal: s.goal, + }) +} + +fn resolve>( + selection: &Selection, + buffer: &MultiBufferSnapshot, +) -> Selection { + selection.map(|p| p.summary::(buffer)) +} diff --git a/crates/editor2/src/test.rs b/crates/editor2/src/test.rs new file mode 100644 index 0000000000..14619c4a98 --- /dev/null +++ b/crates/editor2/src/test.rs @@ -0,0 +1,81 @@ +pub mod editor_lsp_test_context; +pub mod editor_test_context; + +// todo!() +// use crate::{ +// display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint}, +// DisplayPoint, Editor, EditorMode, MultiBuffer, +// }; + +// use gpui::{Model, ViewContext}; + +// use project::Project; +// use util::test::{marked_text_offsets, marked_text_ranges}; + +// #[cfg(test)] +// #[ctor::ctor] +// fn init_logger() { +// if std::env::var("RUST_LOG").is_ok() { +// env_logger::init(); +// } +// } + +// // Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one. +// pub fn marked_display_snapshot( +// text: &str, +// cx: &mut gpui::AppContext, +// ) -> (DisplaySnapshot, Vec) { +// let (unmarked_text, markers) = marked_text_offsets(text); + +// let family_id = cx +// .font_cache() +// .load_family(&["Helvetica"], &Default::default()) +// .unwrap(); +// let font_id = cx +// .font_cache() +// .select_font(family_id, &Default::default()) +// .unwrap(); +// let font_size = 14.0; + +// let buffer = MultiBuffer::build_simple(&unmarked_text, cx); +// let display_map = +// cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx)); +// let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx)); +// let markers = markers +// .into_iter() +// .map(|offset| offset.to_display_point(&snapshot)) +// .collect(); + +// (snapshot, markers) +// } + +// pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext) { +// let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true); +// assert_eq!(editor.text(cx), unmarked_text); +// editor.change_selections(None, cx, |s| s.select_ranges(text_ranges)); +// } + +// pub fn assert_text_with_selections( +// editor: &mut Editor, +// marked_text: &str, +// cx: &mut ViewContext, +// ) { +// let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true); +// assert_eq!(editor.text(cx), unmarked_text); +// assert_eq!(editor.selections.ranges(cx), text_ranges); +// } + +// // RA thinks this is dead code even though it is used in a whole lot of tests +// #[allow(dead_code)] +// #[cfg(any(test, feature = "test-support"))] +// pub(crate) fn build_editor(buffer: Model, cx: &mut ViewContext) -> Editor { +// Editor::new(EditorMode::Full, buffer, None, None, cx) +// } + +// pub(crate) fn build_editor_with_project( +// project: Model, +// buffer: Model, +// cx: &mut ViewContext, +// ) -> Editor { +// Editor::new(EditorMode::Full, buffer, Some(project), None, cx) +// } diff --git a/crates/editor2/src/test/editor_lsp_test_context.rs b/crates/editor2/src/test/editor_lsp_test_context.rs new file mode 100644 index 0000000000..5a6bdd2723 --- /dev/null +++ b/crates/editor2/src/test/editor_lsp_test_context.rs @@ -0,0 +1,297 @@ +// use std::{ +// borrow::Cow, +// ops::{Deref, DerefMut, Range}, +// sync::Arc, +// }; + +// use anyhow::Result; + +// use crate::{Editor, ToPoint}; +// use collections::HashSet; +// use futures::Future; +// use gpui::{json, View, ViewContext}; +// use indoc::indoc; +// use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries}; +// use lsp::{notification, request}; +// use multi_buffer::ToPointUtf16; +// use project::Project; +// use smol::stream::StreamExt; +// use workspace::{AppState, Workspace, WorkspaceHandle}; + +// use super::editor_test_context::EditorTestContext; + +// pub struct EditorLspTestContext<'a> { +// pub cx: EditorTestContext<'a>, +// pub lsp: lsp::FakeLanguageServer, +// pub workspace: View, +// pub buffer_lsp_url: lsp::Url, +// } + +// impl<'a> EditorLspTestContext<'a> { +// pub async fn new( +// mut language: Language, +// capabilities: lsp::ServerCapabilities, +// cx: &'a mut gpui::TestAppContext, +// ) -> EditorLspTestContext<'a> { +// use json::json; + +// let app_state = cx.update(AppState::test); + +// cx.update(|cx| { +// language::init(cx); +// crate::init(cx); +// workspace::init(app_state.clone(), cx); +// Project::init_settings(cx); +// }); + +// let file_name = format!( +// "file.{}", +// language +// .path_suffixes() +// .first() +// .expect("language must have a path suffix for EditorLspTestContext") +// ); + +// let mut fake_servers = language +// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { +// capabilities, +// ..Default::default() +// })) +// .await; + +// let project = Project::test(app_state.fs.clone(), [], cx).await; +// project.update(cx, |project, _| project.languages().add(Arc::new(language))); + +// app_state +// .fs +// .as_fake() +// .insert_tree("/root", json!({ "dir": { file_name.clone(): "" }})) +// .await; + +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// project +// .update(cx, |project, cx| { +// project.find_or_create_local_worktree("/root", true, cx) +// }) +// .await +// .unwrap(); +// cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx)) +// .await; + +// let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone()); +// let item = workspace +// .update(cx, |workspace, cx| { +// workspace.open_path(file, None, true, cx) +// }) +// .await +// .expect("Could not open test file"); + +// let editor = cx.update(|cx| { +// item.act_as::(cx) +// .expect("Opened test file wasn't an editor") +// }); +// editor.update(cx, |_, cx| cx.focus_self()); + +// let lsp = fake_servers.next().await.unwrap(); + +// Self { +// cx: EditorTestContext { +// cx, +// window: window.into(), +// editor, +// }, +// lsp, +// workspace, +// buffer_lsp_url: lsp::Url::from_file_path(format!("/root/dir/{file_name}")).unwrap(), +// } +// } + +// pub async fn new_rust( +// capabilities: lsp::ServerCapabilities, +// cx: &'a mut gpui::TestAppContext, +// ) -> EditorLspTestContext<'a> { +// let language = Language::new( +// LanguageConfig { +// name: "Rust".into(), +// path_suffixes: vec!["rs".to_string()], +// ..Default::default() +// }, +// Some(tree_sitter_rust::language()), +// ) +// .with_queries(LanguageQueries { +// indents: Some(Cow::from(indoc! {r#" +// [ +// ((where_clause) _ @end) +// (field_expression) +// (call_expression) +// (assignment_expression) +// (let_declaration) +// (let_chain) +// (await_expression) +// ] @indent + +// (_ "[" "]" @end) @indent +// (_ "<" ">" @end) @indent +// (_ "{" "}" @end) @indent +// (_ "(" ")" @end) @indent"#})), +// brackets: Some(Cow::from(indoc! {r#" +// ("(" @open ")" @close) +// ("[" @open "]" @close) +// ("{" @open "}" @close) +// ("<" @open ">" @close) +// ("\"" @open "\"" @close) +// (closure_parameters "|" @open "|" @close)"#})), +// ..Default::default() +// }) +// .expect("Could not parse queries"); + +// Self::new(language, capabilities, cx).await +// } + +// pub async fn new_typescript( +// capabilities: lsp::ServerCapabilities, +// cx: &'a mut gpui::TestAppContext, +// ) -> EditorLspTestContext<'a> { +// let mut word_characters: HashSet = Default::default(); +// word_characters.insert('$'); +// word_characters.insert('#'); +// let language = Language::new( +// LanguageConfig { +// name: "Typescript".into(), +// path_suffixes: vec!["ts".to_string()], +// brackets: language::BracketPairConfig { +// pairs: vec![language::BracketPair { +// start: "{".to_string(), +// end: "}".to_string(), +// close: true, +// newline: true, +// }], +// disabled_scopes_by_bracket_ix: Default::default(), +// }, +// word_characters, +// ..Default::default() +// }, +// Some(tree_sitter_typescript::language_typescript()), +// ) +// .with_queries(LanguageQueries { +// brackets: Some(Cow::from(indoc! {r#" +// ("(" @open ")" @close) +// ("[" @open "]" @close) +// ("{" @open "}" @close) +// ("<" @open ">" @close) +// ("\"" @open "\"" @close)"#})), +// indents: Some(Cow::from(indoc! {r#" +// [ +// (call_expression) +// (assignment_expression) +// (member_expression) +// (lexical_declaration) +// (variable_declaration) +// (assignment_expression) +// (if_statement) +// (for_statement) +// ] @indent + +// (_ "[" "]" @end) @indent +// (_ "<" ">" @end) @indent +// (_ "{" "}" @end) @indent +// (_ "(" ")" @end) @indent +// "#})), +// ..Default::default() +// }) +// .expect("Could not parse queries"); + +// Self::new(language, capabilities, cx).await +// } + +// // Constructs lsp range using a marked string with '[', ']' range delimiters +// pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range { +// let ranges = self.ranges(marked_text); +// self.to_lsp_range(ranges[0].clone()) +// } + +// pub fn to_lsp_range(&mut self, range: Range) -> lsp::Range { +// let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx)); +// let start_point = range.start.to_point(&snapshot.buffer_snapshot); +// let end_point = range.end.to_point(&snapshot.buffer_snapshot); + +// self.editor(|editor, cx| { +// let buffer = editor.buffer().read(cx); +// let start = point_to_lsp( +// buffer +// .point_to_buffer_offset(start_point, cx) +// .unwrap() +// .1 +// .to_point_utf16(&buffer.read(cx)), +// ); +// let end = point_to_lsp( +// buffer +// .point_to_buffer_offset(end_point, cx) +// .unwrap() +// .1 +// .to_point_utf16(&buffer.read(cx)), +// ); + +// lsp::Range { start, end } +// }) +// } + +// pub fn to_lsp(&mut self, offset: usize) -> lsp::Position { +// let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx)); +// let point = offset.to_point(&snapshot.buffer_snapshot); + +// self.editor(|editor, cx| { +// let buffer = editor.buffer().read(cx); +// point_to_lsp( +// buffer +// .point_to_buffer_offset(point, cx) +// .unwrap() +// .1 +// .to_point_utf16(&buffer.read(cx)), +// ) +// }) +// } + +// pub fn update_workspace(&mut self, update: F) -> T +// where +// F: FnOnce(&mut Workspace, &mut ViewContext) -> T, +// { +// self.workspace.update(self.cx.cx, update) +// } + +// pub fn handle_request( +// &self, +// mut handler: F, +// ) -> futures::channel::mpsc::UnboundedReceiver<()> +// where +// T: 'static + request::Request, +// T::Params: 'static + Send, +// F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, +// Fut: 'static + Send + Future>, +// { +// let url = self.buffer_lsp_url.clone(); +// self.lsp.handle_request::(move |params, cx| { +// let url = url.clone(); +// handler(url, params, cx) +// }) +// } + +// pub fn notify(&self, params: T::Params) { +// self.lsp.notify::(params); +// } +// } + +// impl<'a> Deref for EditorLspTestContext<'a> { +// type Target = EditorTestContext<'a>; + +// fn deref(&self) -> &Self::Target { +// &self.cx +// } +// } + +// impl<'a> DerefMut for EditorLspTestContext<'a> { +// fn deref_mut(&mut self) -> &mut Self::Target { +// &mut self.cx +// } +// } diff --git a/crates/editor2/src/test/editor_test_context.rs b/crates/editor2/src/test/editor_test_context.rs new file mode 100644 index 0000000000..4bf32d0613 --- /dev/null +++ b/crates/editor2/src/test/editor_test_context.rs @@ -0,0 +1,331 @@ +use crate::{ + display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer, +}; +use futures::Future; +use gpui::{ + AnyWindowHandle, AppContext, ForegroundExecutor, Keystroke, ModelContext, View, ViewContext, +}; +use indoc::indoc; +use language::{Buffer, BufferSnapshot}; +use project::{FakeFs, Project}; +use std::{ + any::TypeId, + ops::{Deref, DerefMut, Range}, +}; +use util::{ + assert_set_eq, + test::{generate_marked_text, marked_text_ranges}, +}; + +// use super::build_editor_with_project; + +// pub struct EditorTestContext<'a> { +// pub cx: &'a mut gpui::TestAppContext, +// pub window: AnyWindowHandle, +// pub editor: View, +// } + +// impl<'a> EditorTestContext<'a> { +// pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> { +// let fs = FakeFs::new(cx.background()); +// // fs.insert_file("/file", "".to_owned()).await; +// fs.insert_tree( +// "/root", +// gpui::serde_json::json!({ +// "file": "", +// }), +// ) +// .await; +// let project = Project::test(fs, ["/root".as_ref()], cx).await; +// let buffer = project +// .update(cx, |project, cx| { +// project.open_local_buffer("/root/file", cx) +// }) +// .await +// .unwrap(); +// let window = cx.add_window(|cx| { +// cx.focus_self(); +// build_editor_with_project(project, MultiBuffer::build_from_buffer(buffer, cx), cx) +// }); +// let editor = window.root(cx); +// Self { +// cx, +// window: window.into(), +// editor, +// } +// } + +// pub fn condition( +// &self, +// predicate: impl FnMut(&Editor, &AppContext) -> bool, +// ) -> impl Future { +// self.editor.condition(self.cx, predicate) +// } + +// pub fn editor(&self, read: F) -> T +// where +// F: FnOnce(&Editor, &ViewContext) -> T, +// { +// self.editor.update(self.cx, read) +// } + +// pub fn update_editor(&mut self, update: F) -> T +// where +// F: FnOnce(&mut Editor, &mut ViewContext) -> T, +// { +// self.editor.update(self.cx, update) +// } + +// pub fn multibuffer(&self, read: F) -> T +// where +// F: FnOnce(&MultiBuffer, &AppContext) -> T, +// { +// self.editor(|editor, cx| read(editor.buffer().read(cx), cx)) +// } + +// pub fn update_multibuffer(&mut self, update: F) -> T +// where +// F: FnOnce(&mut MultiBuffer, &mut ModelContext) -> T, +// { +// self.update_editor(|editor, cx| editor.buffer().update(cx, update)) +// } + +// pub fn buffer_text(&self) -> String { +// self.multibuffer(|buffer, cx| buffer.snapshot(cx).text()) +// } + +// pub fn buffer(&self, read: F) -> T +// where +// F: FnOnce(&Buffer, &AppContext) -> T, +// { +// self.multibuffer(|multibuffer, cx| { +// let buffer = multibuffer.as_singleton().unwrap().read(cx); +// read(buffer, cx) +// }) +// } + +// pub fn update_buffer(&mut self, update: F) -> T +// where +// F: FnOnce(&mut Buffer, &mut ModelContext) -> T, +// { +// self.update_multibuffer(|multibuffer, cx| { +// let buffer = multibuffer.as_singleton().unwrap(); +// buffer.update(cx, update) +// }) +// } + +// pub fn buffer_snapshot(&self) -> BufferSnapshot { +// self.buffer(|buffer, _| buffer.snapshot()) +// } + +// pub fn simulate_keystroke(&mut self, keystroke_text: &str) -> ContextHandle { +// let keystroke_under_test_handle = +// self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text)); +// let keystroke = Keystroke::parse(keystroke_text).unwrap(); + +// self.cx.dispatch_keystroke(self.window, keystroke, false); + +// keystroke_under_test_handle +// } + +// pub fn simulate_keystrokes( +// &mut self, +// keystroke_texts: [&str; COUNT], +// ) -> ContextHandle { +// let keystrokes_under_test_handle = +// self.add_assertion_context(format!("Simulated Keystrokes: {:?}", keystroke_texts)); +// for keystroke_text in keystroke_texts.into_iter() { +// self.simulate_keystroke(keystroke_text); +// } +// // it is common for keyboard shortcuts to kick off async actions, so this ensures that they are complete +// // before returning. +// // NOTE: we don't do this in simulate_keystroke() because a possible cause of bugs is that typing too +// // quickly races with async actions. +// if let Foreground::Deterministic { cx_id: _, executor } = self.cx.foreground().as_ref() { +// executor.run_until_parked(); +// } else { +// unreachable!(); +// } + +// keystrokes_under_test_handle +// } + +// pub fn ranges(&self, marked_text: &str) -> Vec> { +// let (unmarked_text, ranges) = marked_text_ranges(marked_text, false); +// assert_eq!(self.buffer_text(), unmarked_text); +// ranges +// } + +// pub fn display_point(&mut self, marked_text: &str) -> DisplayPoint { +// let ranges = self.ranges(marked_text); +// let snapshot = self +// .editor +// .update(self.cx, |editor, cx| editor.snapshot(cx)); +// ranges[0].start.to_display_point(&snapshot) +// } + +// // Returns anchors for the current buffer using `«` and `»` +// pub fn text_anchor_range(&self, marked_text: &str) -> Range { +// let ranges = self.ranges(marked_text); +// let snapshot = self.buffer_snapshot(); +// snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end) +// } + +// pub fn set_diff_base(&mut self, diff_base: Option<&str>) { +// let diff_base = diff_base.map(String::from); +// self.update_buffer(|buffer, cx| buffer.set_diff_base(diff_base, cx)); +// } + +// /// Change the editor's text and selections using a string containing +// /// embedded range markers that represent the ranges and directions of +// /// each selection. +// /// +// /// Returns a context handle so that assertion failures can print what +// /// editor state was needed to cause the failure. +// /// +// /// See the `util::test::marked_text_ranges` function for more information. +// pub fn set_state(&mut self, marked_text: &str) -> ContextHandle { +// let state_context = self.add_assertion_context(format!( +// "Initial Editor State: \"{}\"", +// marked_text.escape_debug().to_string() +// )); +// let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true); +// self.editor.update(self.cx, |editor, cx| { +// editor.set_text(unmarked_text, cx); +// editor.change_selections(Some(Autoscroll::fit()), cx, |s| { +// s.select_ranges(selection_ranges) +// }) +// }); +// state_context +// } + +// /// Only change the editor's selections +// pub fn set_selections_state(&mut self, marked_text: &str) -> ContextHandle { +// let state_context = self.add_assertion_context(format!( +// "Initial Editor State: \"{}\"", +// marked_text.escape_debug().to_string() +// )); +// let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true); +// self.editor.update(self.cx, |editor, cx| { +// assert_eq!(editor.text(cx), unmarked_text); +// editor.change_selections(Some(Autoscroll::fit()), cx, |s| { +// s.select_ranges(selection_ranges) +// }) +// }); +// state_context +// } + +// /// Make an assertion about the editor's text and the ranges and directions +// /// of its selections using a string containing embedded range markers. +// /// +// /// See the `util::test::marked_text_ranges` function for more information. +// #[track_caller] +// pub fn assert_editor_state(&mut self, marked_text: &str) { +// let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true); +// let buffer_text = self.buffer_text(); + +// if buffer_text != unmarked_text { +// panic!("Unmarked text doesn't match buffer text\nBuffer text: {buffer_text:?}\nUnmarked text: {unmarked_text:?}\nRaw buffer text\n{buffer_text}Raw unmarked text\n{unmarked_text}"); +// } + +// self.assert_selections(expected_selections, marked_text.to_string()) +// } + +// pub fn editor_state(&mut self) -> String { +// generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true) +// } + +// #[track_caller] +// pub fn assert_editor_background_highlights(&mut self, marked_text: &str) { +// let expected_ranges = self.ranges(marked_text); +// let actual_ranges: Vec> = self.update_editor(|editor, cx| { +// let snapshot = editor.snapshot(cx); +// editor +// .background_highlights +// .get(&TypeId::of::()) +// .map(|h| h.1.clone()) +// .unwrap_or_default() +// .into_iter() +// .map(|range| range.to_offset(&snapshot.buffer_snapshot)) +// .collect() +// }); +// assert_set_eq!(actual_ranges, expected_ranges); +// } + +// #[track_caller] +// pub fn assert_editor_text_highlights(&mut self, marked_text: &str) { +// let expected_ranges = self.ranges(marked_text); +// let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx)); +// let actual_ranges: Vec> = snapshot +// .text_highlight_ranges::() +// .map(|ranges| ranges.as_ref().clone().1) +// .unwrap_or_default() +// .into_iter() +// .map(|range| range.to_offset(&snapshot.buffer_snapshot)) +// .collect(); +// assert_set_eq!(actual_ranges, expected_ranges); +// } + +// #[track_caller] +// pub fn assert_editor_selections(&mut self, expected_selections: Vec>) { +// let expected_marked_text = +// generate_marked_text(&self.buffer_text(), &expected_selections, true); +// self.assert_selections(expected_selections, expected_marked_text) +// } + +// fn editor_selections(&self) -> Vec> { +// self.editor +// .read_with(self.cx, |editor, cx| editor.selections.all::(cx)) +// .into_iter() +// .map(|s| { +// if s.reversed { +// s.end..s.start +// } else { +// s.start..s.end +// } +// }) +// .collect::>() +// } + +// #[track_caller] +// fn assert_selections( +// &mut self, +// expected_selections: Vec>, +// expected_marked_text: String, +// ) { +// let actual_selections = self.editor_selections(); +// let actual_marked_text = +// generate_marked_text(&self.buffer_text(), &actual_selections, true); +// if expected_selections != actual_selections { +// panic!( +// indoc! {" + +// {}Editor has unexpected selections. + +// Expected selections: +// {} + +// Actual selections: +// {} +// "}, +// self.assertion_context(), +// expected_marked_text, +// actual_marked_text, +// ); +// } +// } +// } +// +// impl<'a> Deref for EditorTestContext<'a> { +// type Target = gpui::TestAppContext; + +// fn deref(&self) -> &Self::Target { +// self.cx +// } +// } + +// impl<'a> DerefMut for EditorTestContext<'a> { +// fn deref_mut(&mut self) -> &mut Self::Target { +// &mut self.cx +// } +// } diff --git a/crates/gpui2/Cargo.toml b/crates/gpui2/Cargo.toml index fa072dadc3..df461af7b8 100644 --- a/crates/gpui2/Cargo.toml +++ b/crates/gpui2/Cargo.toml @@ -15,7 +15,6 @@ doctest = false [dependencies] collections = { path = "../collections" } -gpui_macros = { path = "../gpui_macros" } gpui2_macros = { path = "../gpui2_macros" } util = { path = "../util" } sum_tree = { path = "../sum_tree" } diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index da4d59daed..9ec76a86d4 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -373,6 +373,10 @@ impl AppContext { self.platform.reveal_path(path) } + pub fn should_auto_hide_scrollbars(&self) -> bool { + self.platform.should_auto_hide_scrollbars() + } + pub(crate) fn push_effect(&mut self, effect: Effect) { match &effect { Effect::Notify { emitter } => { diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index 25e88068c3..b6fd5b2318 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -29,6 +29,7 @@ pub struct ForegroundExecutor { } #[must_use] +#[derive(Debug)] pub enum Task { Ready(Option), Spawned(async_task::Task), @@ -49,11 +50,11 @@ impl Task { impl Task> where - T: 'static + Send, - E: 'static + Send + Debug, + T: 'static, + E: 'static + Debug, { pub fn detach_and_log_err(self, cx: &mut AppContext) { - cx.background_executor().spawn(self.log_err()).detach(); + cx.foreground_executor().spawn(self.log_err()).detach(); } } diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index 7d4073144c..b2fad4efda 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -755,6 +755,10 @@ impl Pixels { pub fn pow(&self, exponent: f32) -> Self { Self(self.0.powf(exponent)) } + + pub fn abs(&self) -> Self { + Self(self.0.abs()) + } } impl Mul for Pixels { @@ -815,6 +819,18 @@ impl From for f64 { } } +impl From for u32 { + fn from(pixels: Pixels) -> Self { + pixels.0 as u32 + } +} + +impl From for usize { + fn from(pixels: Pixels) -> Self { + pixels.0 as usize + } +} + #[derive( Add, AddAssign, Clone, Copy, Default, Div, Eq, Hash, Ord, PartialEq, PartialOrd, Sub, SubAssign, )] diff --git a/crates/gpui2/src/platform/mac/text_system.rs b/crates/gpui2/src/platform/mac/text_system.rs index a4c56c3523..b87db09dc0 100644 --- a/crates/gpui2/src/platform/mac/text_system.rs +++ b/crates/gpui2/src/platform/mac/text_system.rs @@ -411,6 +411,7 @@ impl MacTextSystemState { descent: typographic_bounds.descent.into(), runs, font_size, + len: text.len(), } } diff --git a/crates/gpui2/src/style.rs b/crates/gpui2/src/style.rs index 2544989ebc..b30d4aa002 100644 --- a/crates/gpui2/src/style.rs +++ b/crates/gpui2/src/style.rs @@ -167,6 +167,15 @@ impl TextStyle { Ok(self) } + pub fn font(&self) -> Font { + Font { + family: self.font_family.clone(), + features: self.font_features.clone(), + weight: self.font_weight, + style: self.font_style, + } + } + pub fn to_run(&self, len: usize) -> TextRun { TextRun { len, diff --git a/crates/gpui2/src/text_system.rs b/crates/gpui2/src/text_system.rs index 7c96b1a180..dd0689396e 100644 --- a/crates/gpui2/src/text_system.rs +++ b/crates/gpui2/src/text_system.rs @@ -7,7 +7,7 @@ use anyhow::anyhow; pub use font_features::*; pub use line::*; pub use line_layout::*; -use line_wrapper::*; +pub use line_wrapper::*; use smallvec::SmallVec; use crate::{ @@ -151,7 +151,7 @@ impl TextSystem { pub fn layout_text( &self, - text: &SharedString, + text: &str, font_size: Pixels, runs: &[TextRun], wrap_width: Option, diff --git a/crates/gpui2/src/text_system/line.rs b/crates/gpui2/src/text_system/line.rs index bfd1487d91..63aac96faf 100644 --- a/crates/gpui2/src/text_system/line.rs +++ b/crates/gpui2/src/text_system/line.rs @@ -2,6 +2,7 @@ use crate::{ black, point, px, size, BorrowWindow, Bounds, Hsla, Pixels, Point, Result, Size, UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout, }; +use derive_more::{Deref, DerefMut}; use smallvec::SmallVec; use std::sync::Arc; @@ -12,8 +13,10 @@ pub struct DecorationRun { pub underline: Option, } -#[derive(Clone, Default, Debug)] +#[derive(Clone, Default, Debug, Deref, DerefMut)] pub struct Line { + #[deref] + #[deref_mut] pub(crate) layout: Arc, pub(crate) decorations: SmallVec<[DecorationRun; 32]>, } @@ -26,6 +29,10 @@ impl Line { ) } + pub fn width(&self) -> Pixels { + self.layout.width + } + pub fn wrap_count(&self) -> usize { self.layout.wrap_boundaries.len() } diff --git a/crates/gpui2/src/text_system/line_layout.rs b/crates/gpui2/src/text_system/line_layout.rs index 8ce859218b..a310c32cd1 100644 --- a/crates/gpui2/src/text_system/line_layout.rs +++ b/crates/gpui2/src/text_system/line_layout.rs @@ -16,6 +16,7 @@ pub struct LineLayout { pub ascent: Pixels, pub descent: Pixels, pub runs: Vec, + pub len: usize, } #[derive(Debug)] @@ -48,6 +49,28 @@ impl LineLayout { } } + /// closest_index_for_x returns the character boundary closest to the given x coordinate + /// (e.g. to handle aligning up/down arrow keys) + pub fn closest_index_for_x(&self, x: Pixels) -> usize { + let mut prev_index = 0; + let mut prev_x = px(0.); + + for run in self.runs.iter() { + for glyph in run.glyphs.iter() { + if glyph.position.x >= x { + if glyph.position.x - x < x - prev_x { + return glyph.index; + } else { + return prev_index; + } + } + prev_index = glyph.index; + prev_x = glyph.position.x; + } + } + prev_index + } + pub fn x_for_index(&self, index: usize) -> Pixels { for run in &self.runs { for glyph in &run.glyphs { diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index d81df5b21c..d12d84f43b 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -1,7 +1,7 @@ use crate::{ private::Sealed, AnyBox, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, - BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, LayoutId, - Model, Pixels, Size, ViewContext, VisualContext, WeakModel, WindowContext, + Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, LayoutId, Model, Pixels, + Size, ViewContext, VisualContext, WeakModel, WindowContext, }; use anyhow::{Context, Result}; use std::{ @@ -196,31 +196,9 @@ impl From> for AnyView { fn from(value: View) -> Self { AnyView { model: value.model.into_any(), - initialize: |view, cx| { - cx.with_element_id(view.model.entity_id, |_, cx| { - let view = view.clone().downcast::().unwrap(); - let element = view.update(cx, |view, cx| { - let mut element = AnyElement::new(view.render(cx)); - element.initialize(view, cx); - element - }); - Box::new(element) - }) - }, - layout: |view, element, cx| { - cx.with_element_id(view.model.entity_id, |_, cx| { - let view = view.clone().downcast::().unwrap(); - let element = element.downcast_mut::>().unwrap(); - view.update(cx, |view, cx| element.layout(view, cx)) - }) - }, - paint: |view, element, cx| { - cx.with_element_id(view.model.entity_id, |_, cx| { - let view = view.clone().downcast::().unwrap(); - let element = element.downcast_mut::>().unwrap(); - view.update(cx, |view, cx| element.paint(view, cx)) - }) - }, + initialize: any_view::initialize::, + layout: any_view::layout::, + paint: any_view::paint::, } } } @@ -280,6 +258,17 @@ impl AnyWeakView { } } +impl From> for AnyWeakView { + fn from(view: WeakView) -> Self { + Self { + model: view.model.into(), + initialize: any_view::initialize::, + layout: any_view::layout::, + paint: any_view::paint::, + } + } +} + impl Render for T where T: 'static + FnMut(&mut WindowContext) -> E, @@ -291,3 +280,44 @@ where (self)(cx) } } + +mod any_view { + use crate::{AnyElement, AnyView, BorrowWindow, LayoutId, Render, WindowContext}; + use std::any::Any; + + pub(crate) fn initialize(view: &AnyView, cx: &mut WindowContext) -> Box { + cx.with_element_id(view.model.entity_id, |_, cx| { + let view = view.clone().downcast::().unwrap(); + let element = view.update(cx, |view, cx| { + let mut element = AnyElement::new(view.render(cx)); + element.initialize(view, cx); + element + }); + Box::new(element) + }) + } + + pub(crate) fn layout( + view: &AnyView, + element: &mut Box, + cx: &mut WindowContext, + ) -> LayoutId { + cx.with_element_id(view.model.entity_id, |_, cx| { + let view = view.clone().downcast::().unwrap(); + let element = element.downcast_mut::>().unwrap(); + view.update(cx, |view, cx| element.layout(view, cx)) + }) + } + + pub(crate) fn paint( + view: &AnyView, + element: &mut Box, + cx: &mut WindowContext, + ) { + cx.with_element_id(view.model.entity_id, |_, cx| { + let view = view.clone().downcast::().unwrap(); + let element = element.downcast_mut::>().unwrap(); + view.update(cx, |view, cx| element.paint(view, cx)) + }) + } +} diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index cebf546217..2284f3ccc2 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1655,6 +1655,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { } } + // todo!("change this to return a reference"); pub fn view(&self) -> View { self.view.clone() } @@ -1663,6 +1664,11 @@ impl<'a, V: 'static> ViewContext<'a, V> { self.view.model.clone() } + /// Access the underlying window context. + pub fn window_context(&mut self) -> &mut WindowContext<'a> { + &mut self.window_cx + } + pub fn stack(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R { self.window.z_index_stack.push(order); let result = f(self); diff --git a/crates/language2/Cargo.toml b/crates/language2/Cargo.toml index 0e4d9addfa..2ffcbc62cf 100644 --- a/crates/language2/Cargo.toml +++ b/crates/language2/Cargo.toml @@ -58,6 +58,7 @@ unicase = "2.6" rand = { workspace = true, optional = true } tree-sitter-rust = { workspace = true, optional = true } tree-sitter-typescript = { workspace = true, optional = true } +pulldown-cmark = { version = "0.9.2", default-features = false } [dev-dependencies] client = { package = "client2", path = "../client2", features = ["test-support"] } diff --git a/crates/language2/src/buffer.rs b/crates/language2/src/buffer.rs index 36c1f39e1c..b19cf97354 100644 --- a/crates/language2/src/buffer.rs +++ b/crates/language2/src/buffer.rs @@ -1,6 +1,7 @@ pub use crate::{ diagnostic_set::DiagnosticSet, highlight_map::{HighlightId, HighlightMap}, + markdown::ParsedMarkdown, proto, BracketPair, Grammar, Language, LanguageConfig, LanguageRegistry, PLAIN_TEXT, }; use crate::{ diff --git a/crates/language2/src/language2.rs b/crates/language2/src/language2.rs index 381284659b..826817c99a 100644 --- a/crates/language2/src/language2.rs +++ b/crates/language2/src/language2.rs @@ -8,6 +8,7 @@ mod syntax_map; #[cfg(test)] mod buffer_tests; +pub mod markdown; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; diff --git a/crates/language2/src/markdown.rs b/crates/language2/src/markdown.rs new file mode 100644 index 0000000000..df75b610ef --- /dev/null +++ b/crates/language2/src/markdown.rs @@ -0,0 +1,301 @@ +use std::sync::Arc; +use std::{ops::Range, path::PathBuf}; + +use crate::{HighlightId, Language, LanguageRegistry}; +use gpui::{px, FontStyle, FontWeight, HighlightStyle, UnderlineStyle}; +use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; + +#[derive(Debug, Clone)] +pub struct ParsedMarkdown { + pub text: String, + pub highlights: Vec<(Range, MarkdownHighlight)>, + pub region_ranges: Vec>, + pub regions: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MarkdownHighlight { + Style(MarkdownHighlightStyle), + Code(HighlightId), +} + +impl MarkdownHighlight { + pub fn to_highlight_style(&self, theme: &theme::SyntaxTheme) -> Option { + match self { + MarkdownHighlight::Style(style) => { + let mut highlight = HighlightStyle::default(); + + if style.italic { + highlight.font_style = Some(FontStyle::Italic); + } + + if style.underline { + highlight.underline = Some(UnderlineStyle { + thickness: px(1.), + ..Default::default() + }); + } + + if style.weight != FontWeight::default() { + highlight.font_weight = Some(style.weight); + } + + Some(highlight) + } + + MarkdownHighlight::Code(id) => id.style(theme), + } + } +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct MarkdownHighlightStyle { + pub italic: bool, + pub underline: bool, + pub weight: FontWeight, +} + +#[derive(Debug, Clone)] +pub struct ParsedRegion { + pub code: bool, + pub link: Option, +} + +#[derive(Debug, Clone)] +pub enum Link { + Web { url: String }, + Path { path: PathBuf }, +} + +impl Link { + fn identify(text: String) -> Option { + if text.starts_with("http") { + return Some(Link::Web { url: text }); + } + + let path = PathBuf::from(text); + if path.is_absolute() { + return Some(Link::Path { path }); + } + + None + } +} + +pub async fn parse_markdown( + markdown: &str, + language_registry: &Arc, + language: Option>, +) -> ParsedMarkdown { + let mut text = String::new(); + let mut highlights = Vec::new(); + let mut region_ranges = Vec::new(); + let mut regions = Vec::new(); + + parse_markdown_block( + markdown, + language_registry, + language, + &mut text, + &mut highlights, + &mut region_ranges, + &mut regions, + ) + .await; + + ParsedMarkdown { + text, + highlights, + region_ranges, + regions, + } +} + +pub async fn parse_markdown_block( + markdown: &str, + language_registry: &Arc, + language: Option>, + text: &mut String, + highlights: &mut Vec<(Range, MarkdownHighlight)>, + region_ranges: &mut Vec>, + regions: &mut Vec, +) { + let mut bold_depth = 0; + let mut italic_depth = 0; + let mut link_url = None; + let mut current_language = None; + let mut list_stack = Vec::new(); + + for event in Parser::new_ext(&markdown, Options::all()) { + let prev_len = text.len(); + match event { + Event::Text(t) => { + if let Some(language) = ¤t_language { + highlight_code(text, highlights, t.as_ref(), language); + } else { + text.push_str(t.as_ref()); + + let mut style = MarkdownHighlightStyle::default(); + + if bold_depth > 0 { + style.weight = FontWeight::BOLD; + } + + if italic_depth > 0 { + style.italic = true; + } + + if let Some(link) = link_url.clone().and_then(|u| Link::identify(u)) { + region_ranges.push(prev_len..text.len()); + regions.push(ParsedRegion { + code: false, + link: Some(link), + }); + style.underline = true; + } + + if style != MarkdownHighlightStyle::default() { + let mut new_highlight = true; + if let Some((last_range, MarkdownHighlight::Style(last_style))) = + highlights.last_mut() + { + if last_range.end == prev_len && last_style == &style { + last_range.end = text.len(); + new_highlight = false; + } + } + if new_highlight { + let range = prev_len..text.len(); + highlights.push((range, MarkdownHighlight::Style(style))); + } + } + } + } + + Event::Code(t) => { + text.push_str(t.as_ref()); + region_ranges.push(prev_len..text.len()); + + let link = link_url.clone().and_then(|u| Link::identify(u)); + if link.is_some() { + highlights.push(( + prev_len..text.len(), + MarkdownHighlight::Style(MarkdownHighlightStyle { + underline: true, + ..Default::default() + }), + )); + } + regions.push(ParsedRegion { code: true, link }); + } + + Event::Start(tag) => match tag { + Tag::Paragraph => new_paragraph(text, &mut list_stack), + + Tag::Heading(_, _, _) => { + new_paragraph(text, &mut list_stack); + bold_depth += 1; + } + + Tag::CodeBlock(kind) => { + new_paragraph(text, &mut list_stack); + current_language = if let CodeBlockKind::Fenced(language) = kind { + language_registry + .language_for_name(language.as_ref()) + .await + .ok() + } else { + language.clone() + } + } + + Tag::Emphasis => italic_depth += 1, + + Tag::Strong => bold_depth += 1, + + Tag::Link(_, url, _) => link_url = Some(url.to_string()), + + Tag::List(number) => { + list_stack.push((number, false)); + } + + Tag::Item => { + let len = list_stack.len(); + if let Some((list_number, has_content)) = list_stack.last_mut() { + *has_content = false; + if !text.is_empty() && !text.ends_with('\n') { + text.push('\n'); + } + for _ in 0..len - 1 { + text.push_str(" "); + } + if let Some(number) = list_number { + text.push_str(&format!("{}. ", number)); + *number += 1; + *has_content = false; + } else { + text.push_str("- "); + } + } + } + + _ => {} + }, + + Event::End(tag) => match tag { + Tag::Heading(_, _, _) => bold_depth -= 1, + Tag::CodeBlock(_) => current_language = None, + Tag::Emphasis => italic_depth -= 1, + Tag::Strong => bold_depth -= 1, + Tag::Link(_, _, _) => link_url = None, + Tag::List(_) => drop(list_stack.pop()), + _ => {} + }, + + Event::HardBreak => text.push('\n'), + + Event::SoftBreak => text.push(' '), + + _ => {} + } + } +} + +pub fn highlight_code( + text: &mut String, + highlights: &mut Vec<(Range, MarkdownHighlight)>, + content: &str, + language: &Arc, +) { + let prev_len = text.len(); + text.push_str(content); + for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) { + let highlight = MarkdownHighlight::Code(highlight_id); + highlights.push((prev_len + range.start..prev_len + range.end, highlight)); + } +} + +pub fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { + let mut is_subsequent_paragraph_of_list = false; + if let Some((_, has_content)) = list_stack.last_mut() { + if *has_content { + is_subsequent_paragraph_of_list = true; + } else { + *has_content = true; + return; + } + } + + if !text.is_empty() { + if !text.ends_with('\n') { + text.push('\n'); + } + text.push('\n'); + } + for _ in 0..list_stack.len().saturating_sub(1) { + text.push_str(" "); + } + if is_subsequent_paragraph_of_list { + text.push_str(" "); + } +} diff --git a/crates/text2/src/selection.rs b/crates/text2/src/selection.rs index 480cb99d74..4f1f9a2922 100644 --- a/crates/text2/src/selection.rs +++ b/crates/text2/src/selection.rs @@ -5,7 +5,7 @@ use std::ops::Range; #[derive(Copy, Clone, Debug, PartialEq)] pub enum SelectionGoal { None, - HorizontalPosition(f32), + HorizontalPosition(f32), // todo!("Can we use pixels here without adding a runtime gpui dependency?") HorizontalRange { start: f32, end: f32 }, WrappedHorizontalPosition((u32, f32)), } diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index 15b387cbed..e933f373d4 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -12,10 +12,9 @@ use client2::{ Client, }; use gpui::{ - AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, HighlightStyle, Model, Pixels, - Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext, + AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, HighlightStyle, + Model, Pixels, Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext, }; -use parking_lot::Mutex; use project2::{Project, ProjectEntryId, ProjectPath}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -23,8 +22,10 @@ use settings2::Settings; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, + cell::RefCell, ops::Range, path::PathBuf, + rc::Rc, sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -90,7 +91,8 @@ pub struct BreadcrumbText { pub highlights: Option, HighlightStyle)>>, } -pub trait Item: Render + EventEmitter + Send { +pub trait Item: Render + EventEmitter { + fn focus_handle(&self) -> FocusHandle; fn deactivated(&mut self, _: &mut ViewContext) {} fn workspace_deactivated(&mut self, _: &mut ViewContext) {} fn navigate(&mut self, _: Box, _: &mut ViewContext) -> bool { @@ -104,7 +106,11 @@ pub trait Item: Render + EventEmitter + Send { } fn tab_content(&self, detail: Option, cx: &AppContext) -> AnyElement; - fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)) { + fn for_each_project_item( + &self, + _: &AppContext, + _: &mut dyn FnMut(EntityId, &dyn project2::Item), + ) { } // (model id, Item) fn is_singleton(&self, _cx: &AppContext) -> bool { false @@ -208,6 +214,7 @@ pub trait Item: Render + EventEmitter + Send { } pub trait ItemHandle: 'static + Send { + fn focus_handle(&self, cx: &WindowContext) -> FocusHandle; fn subscribe_to_item_events( &self, cx: &mut WindowContext, @@ -219,8 +226,12 @@ pub trait ItemHandle: 'static + Send { fn dragged_tab_content(&self, detail: Option, cx: &AppContext) -> AnyElement; fn project_path(&self, cx: &AppContext) -> Option; fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; - fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>; - fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project2::Item)); + fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[EntityId; 3]>; + fn for_each_project_item( + &self, + _: &AppContext, + _: &mut dyn FnMut(EntityId, &dyn project2::Item), + ); fn is_singleton(&self, cx: &AppContext) -> bool; fn boxed_clone(&self) -> Box; fn clone_on_split( @@ -253,7 +264,7 @@ pub trait ItemHandle: 'static + Send { fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option; fn to_followable_item_handle(&self, cx: &AppContext) -> Option>; fn on_release( - &mut self, + &self, cx: &mut AppContext, callback: Box, ) -> gpui::Subscription; @@ -282,6 +293,10 @@ impl dyn ItemHandle { } impl ItemHandle for View { + fn focus_handle(&self, cx: &WindowContext) -> FocusHandle { + self.read(cx).focus_handle() + } + fn subscribe_to_item_events( &self, cx: &mut WindowContext, @@ -331,7 +346,7 @@ impl ItemHandle for View { result } - fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]> { + fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[EntityId; 3]> { let mut result = SmallVec::new(); self.read(cx).for_each_project_item(cx, &mut |id, _| { result.push(id); @@ -342,7 +357,7 @@ impl ItemHandle for View { fn for_each_project_item( &self, cx: &AppContext, - f: &mut dyn FnMut(usize, &dyn project2::Item), + f: &mut dyn FnMut(EntityId, &dyn project2::Item), ) { self.read(cx).for_each_project_item(cx, f) } @@ -398,91 +413,94 @@ impl ItemHandle for View { .is_none() { let mut pending_autosave = DelayedDebouncedEditAction::new(); - let pending_update = Arc::new(Mutex::new(None)); + let pending_update = Rc::new(RefCell::new(None)); let pending_update_scheduled = Arc::new(AtomicBool::new(false)); - let event_subscription = Some(cx.subscribe(self, move |workspace, item, event, cx| { - let pane = if let Some(pane) = workspace - .panes_by_item - .get(&item.id()) - .and_then(|pane| pane.upgrade()) - { - pane - } else { - log::error!("unexpected item event after pane was dropped"); - return; - }; - - if let Some(item) = item.to_followable_item_handle(cx) { - let _is_project_item = item.is_project_item(cx); - let leader_id = workspace.leader_for_pane(&pane); - - if leader_id.is_some() && item.should_unfollow_on_event(event, cx) { - workspace.unfollow(&pane, cx); - } - - if item.add_event_to_update_proto(event, &mut *pending_update.lock(), cx) - && !pending_update_scheduled.load(Ordering::SeqCst) + let mut event_subscription = + Some(cx.subscribe(self, move |workspace, item, event, cx| { + let pane = if let Some(pane) = workspace + .panes_by_item + .get(&item.id()) + .and_then(|pane| pane.upgrade()) { - pending_update_scheduled.store(true, Ordering::SeqCst); - todo!("replace with on_next_frame?"); - // cx.after_window_update({ - // let pending_update = pending_update.clone(); - // let pending_update_scheduled = pending_update_scheduled.clone(); - // move |this, cx| { - // pending_update_scheduled.store(false, Ordering::SeqCst); - // this.update_followers( - // is_project_item, - // proto::update_followers::Variant::UpdateView( - // proto::UpdateView { - // id: item - // .remote_id(&this.app_state.client, cx) - // .map(|id| id.to_proto()), - // variant: pending_update.borrow_mut().take(), - // leader_id, - // }, - // ), - // cx, - // ); - // } - // }); - } - } + pane + } else { + log::error!("unexpected item event after pane was dropped"); + return; + }; - for item_event in T::to_item_events(event).into_iter() { - match item_event { - ItemEvent::CloseItem => { - pane.update(cx, |pane, cx| { - pane.close_item_by_id(item.id(), crate::SaveIntent::Close, cx) - }) - .detach_and_log_err(cx); - return; + if let Some(item) = item.to_followable_item_handle(cx) { + let is_project_item = item.is_project_item(cx); + let leader_id = workspace.leader_for_pane(&pane); + + if leader_id.is_some() && item.should_unfollow_on_event(event, cx) { + workspace.unfollow(&pane, cx); } - ItemEvent::UpdateTab => { - pane.update(cx, |_, cx| { - cx.emit(pane::Event::ChangeItemTitle); - cx.notify(); + if item.add_event_to_update_proto( + event, + &mut *pending_update.borrow_mut(), + cx, + ) && !pending_update_scheduled.load(Ordering::SeqCst) + { + pending_update_scheduled.store(true, Ordering::SeqCst); + cx.on_next_frame({ + let pending_update = pending_update.clone(); + let pending_update_scheduled = pending_update_scheduled.clone(); + move |this, cx| { + pending_update_scheduled.store(false, Ordering::SeqCst); + this.update_followers( + is_project_item, + proto::update_followers::Variant::UpdateView( + proto::UpdateView { + id: item + .remote_id(&this.app_state.client, cx) + .map(|id| id.to_proto()), + variant: pending_update.borrow_mut().take(), + leader_id, + }, + ), + cx, + ); + } }); } + } - ItemEvent::Edit => { - let autosave = WorkspaceSettings::get_global(cx).autosave; - if let AutosaveSetting::AfterDelay { milliseconds } = autosave { - let delay = Duration::from_millis(milliseconds); - let item = item.clone(); - pending_autosave.fire_new(delay, cx, move |workspace, cx| { - Pane::autosave_item(&item, workspace.project().clone(), cx) + for item_event in T::to_item_events(event).into_iter() { + match item_event { + ItemEvent::CloseItem => { + pane.update(cx, |pane, cx| { + pane.close_item_by_id(item.id(), crate::SaveIntent::Close, cx) + }) + .detach_and_log_err(cx); + return; + } + + ItemEvent::UpdateTab => { + pane.update(cx, |_, cx| { + cx.emit(pane::Event::ChangeItemTitle); + cx.notify(); }); } + + ItemEvent::Edit => { + let autosave = WorkspaceSettings::get_global(cx).autosave; + if let AutosaveSetting::AfterDelay { milliseconds } = autosave { + let delay = Duration::from_millis(milliseconds); + let item = item.clone(); + pending_autosave.fire_new(delay, cx, move |workspace, cx| { + Pane::autosave_item(&item, workspace.project().clone(), cx) + }); + } + } + + _ => {} } - - _ => {} } - } - })); + })); - todo!("observe focus"); + // todo!() // cx.observe_focus(self, move |workspace, item, focused, cx| { // if !focused // && WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange @@ -493,12 +511,12 @@ impl ItemHandle for View { // }) // .detach(); - // let item_id = self.id(); - // cx.observe_release(self, move |workspace, _, _| { - // workspace.panes_by_item.remove(&item_id); - // event_subscription.take(); - // }) - // .detach(); + let item_id = self.id(); + cx.observe_release(self, move |workspace, _, _| { + workspace.panes_by_item.remove(&item_id); + event_subscription.take(); + }) + .detach(); } cx.defer(|workspace, cx| { @@ -570,7 +588,7 @@ impl ItemHandle for View { } fn on_release( - &mut self, + &self, cx: &mut AppContext, callback: Box, ) -> gpui::Subscription { diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 16dbfda361..acc41deba8 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -9,8 +9,8 @@ use crate::{ use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use gpui::{ - AppContext, AsyncWindowContext, Component, Div, EntityId, EventEmitter, Model, PromptLevel, - Render, Task, View, ViewContext, VisualContext, WeakView, WindowContext, + AppContext, AsyncWindowContext, Component, Div, EntityId, EventEmitter, FocusHandle, Model, + PromptLevel, Render, Task, View, ViewContext, VisualContext, WeakView, WindowContext, }; use parking_lot::Mutex; use project2::{Project, ProjectEntryId, ProjectPath}; @@ -171,6 +171,7 @@ impl fmt::Debug for Event { } pub struct Pane { + focus_handle: FocusHandle, items: Vec>, activation_history: Vec, zoomed: bool, @@ -183,7 +184,6 @@ pub struct Pane { // tab_context_menu: ViewHandle, workspace: WeakView, project: Model, - has_focus: bool, // can_drop: Rc, &WindowContext) -> bool>, // can_split: bool, // render_tab_bar_buttons: Rc) -> AnyElement>, @@ -330,6 +330,7 @@ impl Pane { let handle = cx.view().downgrade(); Self { + focus_handle: cx.focus_handle(), items: Vec::new(), activation_history: Vec::new(), zoomed: false, @@ -353,7 +354,6 @@ impl Pane { // tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)), workspace, project, - has_focus: false, // can_drop: Rc::new(|_, _| true), // can_split: true, // render_tab_bar_buttons: Rc::new(move |pane, cx| { @@ -420,8 +420,8 @@ impl Pane { &self.workspace } - pub fn has_focus(&self) -> bool { - self.has_focus + pub fn has_focus(&self, cx: &WindowContext) -> bool { + self.focus_handle.contains_focused(cx) } pub fn active_item_index(&self) -> usize { @@ -639,19 +639,19 @@ impl Pane { // .pixel_position_of_cursor(cx) // } - // pub fn item_for_entry( - // &self, - // entry_id: ProjectEntryId, - // cx: &AppContext, - // ) -> Option> { - // self.items.iter().find_map(|item| { - // if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { - // Some(item.boxed_clone()) - // } else { - // None - // } - // }) - // } + pub fn item_for_entry( + &self, + entry_id: ProjectEntryId, + cx: &AppContext, + ) -> Option> { + self.items.iter().find_map(|item| { + if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { + Some(item.boxed_clone()) + } else { + None + } + }) + } pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option { self.items.iter().position(|i| i.id() == item.id()) @@ -1020,7 +1020,7 @@ impl Pane { // to activating the item to the left .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1)); - let should_activate = activate_pane || self.has_focus; + let should_activate = activate_pane || self.has_focus(cx); self.activate_item(index_to_activate, should_activate, should_activate, cx); } @@ -1184,11 +1184,15 @@ impl Pane { } } + pub fn focus(&mut self, cx: &mut ViewContext) { + cx.focus(&self.focus_handle); + } + pub fn focus_active_item(&mut self, cx: &mut ViewContext) { - todo!(); - // if let Some(active_item) = self.active_item() { - // cx.focus(active_item.as_any()); - // } + if let Some(active_item) = self.active_item() { + let focus_handle = active_item.focus_handle(cx); + cx.focus(&focus_handle); + } } // pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext) { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index bb9cb7e527..f7e9a0d585 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -14,9 +14,12 @@ mod status_bar; mod toolbar; mod workspace_settings; -use crate::persistence::model::{ - DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup, - SerializedWorkspace, +pub use crate::persistence::{ + model::{ + DockData, DockStructure, ItemId, SerializedItem, SerializedPane, SerializedPaneGroup, + SerializedWorkspace, + }, + WorkspaceDb, }; use anyhow::{anyhow, Context as _, Result}; use call2::ActiveCall; @@ -33,10 +36,10 @@ use futures::{ }; use gpui::{ div, point, size, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext, - AsyncWindowContext, Bounds, Component, Div, EntityId, EventEmitter, GlobalPixels, Model, - ModelContext, ParentElement, Point, Render, Size, StatefulInteractive, Styled, Subscription, - Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, - WindowOptions, + AsyncWindowContext, Bounds, Component, Div, Entity, EntityId, EventEmitter, FocusHandle, + GlobalPixels, Model, ModelContext, ParentElement, Point, Render, Size, StatefulInteractive, + Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, + WindowContext, WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use itertools::Itertools; @@ -46,15 +49,13 @@ use node_runtime::NodeRuntime; use notifications::{simple_message_notification::MessageNotification, NotificationHandle}; pub use pane::*; pub use pane_group::*; -use persistence::{ - model::{ItemId, WorkspaceLocation}, - DB, -}; +use persistence::{model::WorkspaceLocation, DB}; use postage::stream::Stream; use project2::{Project, ProjectEntryId, ProjectPath, Worktree}; use serde::Deserialize; use settings2::Settings; use status_bar::StatusBar; +pub use status_bar::StatusItemView; use std::{ any::TypeId, borrow::Cow, @@ -415,18 +416,17 @@ type ItemDeserializers = HashMap< ) -> Task>>, >; pub fn register_deserializable_item(cx: &mut AppContext) { - cx.update_global(|deserializers: &mut ItemDeserializers, _cx| { - if let Some(serialized_item_kind) = I::serialized_item_kind() { - deserializers.insert( - Arc::from(serialized_item_kind), - |project, workspace, workspace_id, item_id, cx| { - let task = I::deserialize(project, workspace, workspace_id, item_id, cx); - cx.foreground_executor() - .spawn(async { Ok(Box::new(task.await?) as Box<_>) }) - }, - ); - } - }); + if let Some(serialized_item_kind) = I::serialized_item_kind() { + let deserializers = cx.default_global::(); + deserializers.insert( + Arc::from(serialized_item_kind), + |project, workspace, workspace_id, item_id, cx| { + let task = I::deserialize(project, workspace, workspace_id, item_id, cx); + cx.foreground_executor() + .spawn(async { Ok(Box::new(task.await?) as Box<_>) }) + }, + ); + } } pub struct AppState { @@ -548,6 +548,7 @@ pub enum Event { pub struct Workspace { weak_self: WeakView, + focus_handle: FocusHandle, // modal: Option, zoomed: Option, zoomed_position: Option, @@ -767,6 +768,7 @@ impl Workspace { cx.defer(|this, cx| this.update_window_title(cx)); Workspace { weak_self: weak_handle.clone(), + focus_handle: cx.focus_handle(), // modal: None, zoomed: None, zoomed_position: None, @@ -1924,44 +1926,44 @@ impl Workspace { // self.zoomed.and_then(|view| view.upgrade(cx)) // } - // fn dismiss_zoomed_items_to_reveal( - // &mut self, - // dock_to_reveal: Option, - // cx: &mut ViewContext, - // ) { - // // If a center pane is zoomed, unzoom it. - // for pane in &self.panes { - // if pane != &self.active_pane || dock_to_reveal.is_some() { - // pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); - // } - // } + fn dismiss_zoomed_items_to_reveal( + &mut self, + dock_to_reveal: Option, + cx: &mut ViewContext, + ) { + // If a center pane is zoomed, unzoom it. + for pane in &self.panes { + if pane != &self.active_pane || dock_to_reveal.is_some() { + pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); + } + } - // // If another dock is zoomed, hide it. - // let mut focus_center = false; - // for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] { - // dock.update(cx, |dock, cx| { - // if Some(dock.position()) != dock_to_reveal { - // if let Some(panel) = dock.active_panel() { - // if panel.is_zoomed(cx) { - // focus_center |= panel.has_focus(cx); - // dock.set_open(false, cx); - // } - // } - // } - // }); - // } + // If another dock is zoomed, hide it. + let mut focus_center = false; + for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] { + dock.update(cx, |dock, cx| { + if Some(dock.position()) != dock_to_reveal { + if let Some(panel) = dock.active_panel() { + if panel.is_zoomed(cx) { + focus_center |= panel.has_focus(cx); + dock.set_open(false, cx); + } + } + } + }); + } - // if focus_center { - // cx.focus_self(); - // } + if focus_center { + cx.focus(&self.focus_handle); + } - // if self.zoomed_position != dock_to_reveal { - // self.zoomed = None; - // self.zoomed_position = None; - // } + if self.zoomed_position != dock_to_reveal { + self.zoomed = None; + self.zoomed_position = None; + } - // cx.notify(); - // } + cx.notify(); + } fn add_pane(&mut self, cx: &mut ViewContext) -> View { let pane = cx.build_view(|cx| { @@ -1997,22 +1999,22 @@ impl Workspace { // } // } - // pub fn add_item(&mut self, item: Box, cx: &mut ViewContext) { - // self.active_pane - // .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); - // } + pub fn add_item(&mut self, item: Box, cx: &mut ViewContext) { + self.active_pane + .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); + } - // pub fn split_item( - // &mut self, - // split_direction: SplitDirection, - // item: Box, - // cx: &mut ViewContext, - // ) { - // let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx); - // new_pane.update(cx, move |new_pane, cx| { - // new_pane.add_item(item, true, true, None, cx) - // }) - // } + pub fn split_item( + &mut self, + split_direction: SplitDirection, + item: Box, + cx: &mut ViewContext, + ) { + let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx); + new_pane.update(cx, move |new_pane, cx| { + new_pane.add_item(item, true, true, None, cx) + }) + } // pub fn open_abs_path( // &mut self, @@ -2142,53 +2144,55 @@ impl Workspace { }) } - // pub fn open_project_item( - // &mut self, - // project_item: ModelHandle, - // cx: &mut ViewContext, - // ) -> View - // where - // T: ProjectItem, - // { - // use project::Item as _; + pub fn open_project_item( + &mut self, + project_item: Model, + cx: &mut ViewContext, + ) -> View + where + T: ProjectItem, + { + use project2::Item as _; - // let entry_id = project_item.read(cx).entry_id(cx); - // if let Some(item) = entry_id - // .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) - // .and_then(|item| item.downcast()) - // { - // self.activate_item(&item, cx); - // return item; - // } + let entry_id = project_item.read(cx).entry_id(cx); + if let Some(item) = entry_id + .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) + .and_then(|item| item.downcast()) + { + self.activate_item(&item, cx); + return item; + } - // let item = cx.build_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); - // self.add_item(Box::new(item.clone()), cx); - // item - // } + let item = + cx.build_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); + self.add_item(Box::new(item.clone()), cx); + item + } - // pub fn split_project_item( - // &mut self, - // project_item: ModelHandle, - // cx: &mut ViewContext, - // ) -> View - // where - // T: ProjectItem, - // { - // use project::Item as _; + pub fn split_project_item( + &mut self, + project_item: Model, + cx: &mut ViewContext, + ) -> View + where + T: ProjectItem, + { + use project2::Item as _; - // let entry_id = project_item.read(cx).entry_id(cx); - // if let Some(item) = entry_id - // .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) - // .and_then(|item| item.downcast()) - // { - // self.activate_item(&item, cx); - // return item; - // } + let entry_id = project_item.read(cx).entry_id(cx); + if let Some(item) = entry_id + .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) + .and_then(|item| item.downcast()) + { + self.activate_item(&item, cx); + return item; + } - // let item = cx.build_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); - // self.split_item(SplitDirection::Right, Box::new(item.clone()), cx); - // item - // } + let item = + cx.build_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); + self.split_item(SplitDirection::Right, Box::new(item.clone()), cx); + item + } // pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext) { // if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) { @@ -2198,19 +2202,19 @@ impl Workspace { // } // } - // pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext) -> bool { - // let result = self.panes.iter().find_map(|pane| { - // pane.read(cx) - // .index_for_item(item) - // .map(|ix| (pane.clone(), ix)) - // }); - // if let Some((pane, ix)) = result { - // pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx)); - // true - // } else { - // false - // } - // } + pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext) -> bool { + let result = self.panes.iter().find_map(|pane| { + pane.read(cx) + .index_for_item(item) + .map(|ix| (pane.clone(), ix)) + }); + if let Some((pane, ix)) = result { + pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx)); + true + } else { + false + } + } // fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext) { // let panes = self.center.panes(); @@ -2288,214 +2292,213 @@ impl Workspace { // self.center.pane_at_pixel_position(target) // } - // fn handle_pane_focused(&mut self, pane: View, cx: &mut ViewContext) { - // if self.active_pane != pane { - // self.active_pane = pane.clone(); - // self.status_bar.update(cx, |status_bar, cx| { - // status_bar.set_active_pane(&self.active_pane, cx); - // }); - // self.active_item_path_changed(cx); - // self.last_active_center_pane = Some(pane.downgrade()); - // } + fn handle_pane_focused(&mut self, pane: View, cx: &mut ViewContext) { + if self.active_pane != pane { + self.active_pane = pane.clone(); + self.status_bar.update(cx, |status_bar, cx| { + status_bar.set_active_pane(&self.active_pane, cx); + }); + self.active_item_path_changed(cx); + self.last_active_center_pane = Some(pane.downgrade()); + } - // self.dismiss_zoomed_items_to_reveal(None, cx); - // if pane.read(cx).is_zoomed() { - // self.zoomed = Some(pane.downgrade().into_any()); - // } else { - // self.zoomed = None; - // } - // self.zoomed_position = None; - // self.update_active_view_for_followers(cx); + self.dismiss_zoomed_items_to_reveal(None, cx); + if pane.read(cx).is_zoomed() { + self.zoomed = Some(pane.downgrade().into()); + } else { + self.zoomed = None; + } + self.zoomed_position = None; + self.update_active_view_for_followers(cx); - // cx.notify(); - // } + cx.notify(); + } fn handle_pane_event( &mut self, - _pane: View, - _event: &pane::Event, - _cx: &mut ViewContext, + pane: View, + event: &pane::Event, + cx: &mut ViewContext, ) { - todo!() - // match event { - // pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx), - // pane::Event::Split(direction) => { - // self.split_and_clone(pane, *direction, cx); - // } - // pane::Event::Remove => self.remove_pane(pane, cx), - // pane::Event::ActivateItem { local } => { - // if *local { - // self.unfollow(&pane, cx); - // } - // if &pane == self.active_pane() { - // self.active_item_path_changed(cx); - // } - // } - // pane::Event::ChangeItemTitle => { - // if pane == self.active_pane { - // self.active_item_path_changed(cx); - // } - // self.update_window_edited(cx); - // } - // pane::Event::RemoveItem { item_id } => { - // self.update_window_edited(cx); - // if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) { - // if entry.get().id() == pane.id() { - // entry.remove(); - // } - // } - // } - // pane::Event::Focus => { - // self.handle_pane_focused(pane.clone(), cx); - // } - // pane::Event::ZoomIn => { - // if pane == self.active_pane { - // pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); - // if pane.read(cx).has_focus() { - // self.zoomed = Some(pane.downgrade().into_any()); - // self.zoomed_position = None; - // } - // cx.notify(); - // } - // } - // pane::Event::ZoomOut => { - // pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); - // if self.zoomed_position.is_none() { - // self.zoomed = None; - // } - // cx.notify(); - // } - // } + match event { + pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx), + pane::Event::Split(direction) => { + self.split_and_clone(pane, *direction, cx); + } + pane::Event::Remove => self.remove_pane(pane, cx), + pane::Event::ActivateItem { local } => { + if *local { + self.unfollow(&pane, cx); + } + if &pane == self.active_pane() { + self.active_item_path_changed(cx); + } + } + pane::Event::ChangeItemTitle => { + if pane == self.active_pane { + self.active_item_path_changed(cx); + } + self.update_window_edited(cx); + } + pane::Event::RemoveItem { item_id } => { + self.update_window_edited(cx); + if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) { + if entry.get().entity_id() == pane.entity_id() { + entry.remove(); + } + } + } + pane::Event::Focus => { + self.handle_pane_focused(pane.clone(), cx); + } + pane::Event::ZoomIn => { + if pane == self.active_pane { + pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); + if pane.read(cx).has_focus(cx) { + self.zoomed = Some(pane.downgrade().into()); + self.zoomed_position = None; + } + cx.notify(); + } + } + pane::Event::ZoomOut => { + pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); + if self.zoomed_position.is_none() { + self.zoomed = None; + } + cx.notify(); + } + } - // self.serialize_workspace(cx); + self.serialize_workspace(cx); } - // pub fn split_pane( - // &mut self, - // pane_to_split: View, - // split_direction: SplitDirection, - // cx: &mut ViewContext, - // ) -> View { - // let new_pane = self.add_pane(cx); - // self.center - // .split(&pane_to_split, &new_pane, split_direction) - // .unwrap(); - // cx.notify(); - // new_pane - // } + pub fn split_pane( + &mut self, + pane_to_split: View, + split_direction: SplitDirection, + cx: &mut ViewContext, + ) -> View { + let new_pane = self.add_pane(cx); + self.center + .split(&pane_to_split, &new_pane, split_direction) + .unwrap(); + cx.notify(); + new_pane + } - // pub fn split_and_clone( - // &mut self, - // pane: View, - // direction: SplitDirection, - // cx: &mut ViewContext, - // ) -> Option> { - // let item = pane.read(cx).active_item()?; - // let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) { - // let new_pane = self.add_pane(cx); - // new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx)); - // self.center.split(&pane, &new_pane, direction).unwrap(); - // Some(new_pane) - // } else { - // None - // }; - // cx.notify(); - // maybe_pane_handle - // } + pub fn split_and_clone( + &mut self, + pane: View, + direction: SplitDirection, + cx: &mut ViewContext, + ) -> Option> { + let item = pane.read(cx).active_item()?; + let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) { + let new_pane = self.add_pane(cx); + new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx)); + self.center.split(&pane, &new_pane, direction).unwrap(); + Some(new_pane) + } else { + None + }; + cx.notify(); + maybe_pane_handle + } - // pub fn split_pane_with_item( - // &mut self, - // pane_to_split: WeakView, - // split_direction: SplitDirection, - // from: WeakView, - // item_id_to_move: usize, - // cx: &mut ViewContext, - // ) { - // let Some(pane_to_split) = pane_to_split.upgrade(cx) else { - // return; - // }; - // let Some(from) = from.upgrade(cx) else { - // return; - // }; + pub fn split_pane_with_item( + &mut self, + pane_to_split: WeakView, + split_direction: SplitDirection, + from: WeakView, + item_id_to_move: EntityId, + cx: &mut ViewContext, + ) { + let Some(pane_to_split) = pane_to_split.upgrade() else { + return; + }; + let Some(from) = from.upgrade() else { + return; + }; - // let new_pane = self.add_pane(cx); - // self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx); - // self.center - // .split(&pane_to_split, &new_pane, split_direction) - // .unwrap(); - // cx.notify(); - // } + let new_pane = self.add_pane(cx); + self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx); + self.center + .split(&pane_to_split, &new_pane, split_direction) + .unwrap(); + cx.notify(); + } - // pub fn split_pane_with_project_entry( - // &mut self, - // pane_to_split: WeakView, - // split_direction: SplitDirection, - // project_entry: ProjectEntryId, - // cx: &mut ViewContext, - // ) -> Option>> { - // let pane_to_split = pane_to_split.upgrade(cx)?; - // let new_pane = self.add_pane(cx); - // self.center - // .split(&pane_to_split, &new_pane, split_direction) - // .unwrap(); + pub fn split_pane_with_project_entry( + &mut self, + pane_to_split: WeakView, + split_direction: SplitDirection, + project_entry: ProjectEntryId, + cx: &mut ViewContext, + ) -> Option>> { + let pane_to_split = pane_to_split.upgrade()?; + let new_pane = self.add_pane(cx); + self.center + .split(&pane_to_split, &new_pane, split_direction) + .unwrap(); - // let path = self.project.read(cx).path_for_entry(project_entry, cx)?; - // let task = self.open_path(path, Some(new_pane.downgrade()), true, cx); - // Some(cx.foreground().spawn(async move { - // task.await?; - // Ok(()) - // })) - // } + let path = self.project.read(cx).path_for_entry(project_entry, cx)?; + let task = self.open_path(path, Some(new_pane.downgrade()), true, cx); + Some(cx.foreground_executor().spawn(async move { + task.await?; + Ok(()) + })) + } - // pub fn move_item( - // &mut self, - // source: View, - // destination: View, - // item_id_to_move: usize, - // destination_index: usize, - // cx: &mut ViewContext, - // ) { - // let item_to_move = source - // .read(cx) - // .items() - // .enumerate() - // .find(|(_, item_handle)| item_handle.id() == item_id_to_move); + pub fn move_item( + &mut self, + source: View, + destination: View, + item_id_to_move: EntityId, + destination_index: usize, + cx: &mut ViewContext, + ) { + let item_to_move = source + .read(cx) + .items() + .enumerate() + .find(|(_, item_handle)| item_handle.id() == item_id_to_move); - // if item_to_move.is_none() { - // log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop"); - // return; - // } - // let (item_ix, item_handle) = item_to_move.unwrap(); - // let item_handle = item_handle.clone(); + if item_to_move.is_none() { + log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop"); + return; + } + let (item_ix, item_handle) = item_to_move.unwrap(); + let item_handle = item_handle.clone(); - // if source != destination { - // // Close item from previous pane - // source.update(cx, |source, cx| { - // source.remove_item(item_ix, false, cx); - // }); - // } + if source != destination { + // Close item from previous pane + source.update(cx, |source, cx| { + source.remove_item(item_ix, false, cx); + }); + } - // // This automatically removes duplicate items in the pane - // destination.update(cx, |destination, cx| { - // destination.add_item(item_handle, true, true, Some(destination_index), cx); - // cx.focus_self(); - // }); - // } + // This automatically removes duplicate items in the pane + destination.update(cx, |destination, cx| { + destination.add_item(item_handle, true, true, Some(destination_index), cx); + destination.focus(cx) + }); + } - // fn remove_pane(&mut self, pane: View, cx: &mut ViewContext) { - // if self.center.remove(&pane).unwrap() { - // self.force_remove_pane(&pane, cx); - // self.unfollow(&pane, cx); - // self.last_leaders_by_pane.remove(&pane.downgrade()); - // for removed_item in pane.read(cx).items() { - // self.panes_by_item.remove(&removed_item.id()); - // } + fn remove_pane(&mut self, pane: View, cx: &mut ViewContext) { + if self.center.remove(&pane).unwrap() { + self.force_remove_pane(&pane, cx); + self.unfollow(&pane, cx); + self.last_leaders_by_pane.remove(&pane.downgrade()); + for removed_item in pane.read(cx).items() { + self.panes_by_item.remove(&removed_item.id()); + } - // cx.notify(); - // } else { - // self.active_item_path_changed(cx); - // } - // } + cx.notify(); + } else { + self.active_item_path_changed(cx); + } + } pub fn panes(&self) -> &[View] { &self.panes @@ -2708,12 +2711,12 @@ impl Workspace { .child("Collab title bar Item") // self.titlebar_item } - // fn active_item_path_changed(&mut self, cx: &mut ViewContext) { - // let active_entry = self.active_project_path(cx); - // self.project - // .update(cx, |project, cx| project.set_active_path(active_entry, cx)); - // self.update_window_title(cx); - // } + fn active_item_path_changed(&mut self, cx: &mut ViewContext) { + let active_entry = self.active_project_path(cx); + self.project + .update(cx, |project, cx| project.set_active_path(active_entry, cx)); + self.update_window_title(cx); + } fn update_window_title(&mut self, cx: &mut ViewContext) { let project = self.project().read(cx); @@ -3010,7 +3013,7 @@ impl Workspace { fn update_active_view_for_followers(&mut self, cx: &mut ViewContext) { let mut is_project_item = true; let mut update = proto::UpdateActiveView::default(); - if self.active_pane.read(cx).has_focus() { + if self.active_pane.read(cx).has_focus(cx) { let item = self .active_item(cx) .and_then(|item| item.to_followable_item_handle(cx)); @@ -3105,7 +3108,7 @@ impl Workspace { } for (pane, item) in items_to_activate { - let pane_was_focused = pane.read(cx).has_focus(); + let pane_was_focused = pane.read(cx).has_focus(cx); if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); } else { @@ -3243,7 +3246,7 @@ impl Workspace { // } fn serialize_workspace(&self, cx: &mut ViewContext) { - fn serialize_pane_handle(pane_handle: &View, cx: &AppContext) -> SerializedPane { + fn serialize_pane_handle(pane_handle: &View, cx: &WindowContext) -> SerializedPane { let (items, active) = { let pane = pane_handle.read(cx); let active_item_id = pane.active_item().map(|item| item.id()); @@ -3257,7 +3260,7 @@ impl Workspace { }) }) .collect::>(), - pane.has_focus(), + pane.has_focus(cx), ) }; @@ -3266,7 +3269,7 @@ impl Workspace { fn build_serialized_pane_group( pane_group: &Member, - cx: &AppContext, + cx: &WindowContext, ) -> SerializedPaneGroup { match pane_group { Member::Axis(PaneAxis { diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index 71963ff30b..d510de24ee 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -34,7 +34,7 @@ copilot = { package = "copilot2", path = "../copilot2" } # copilot_button = { path = "../copilot_button" } # diagnostics = { path = "../diagnostics" } db = { package = "db2", path = "../db2" } -# editor = { path = "../editor" } +editor = { package="editor2", path = "../editor2" } # feedback = { path = "../feedback" } # file_finder = { path = "../file_finder" } # search = { path = "../search" } @@ -70,7 +70,7 @@ theme = { package = "theme2", path = "../theme2" } util = { path = "../util" } # semantic_index = { path = "../semantic_index" } # vim = { path = "../vim" } -workspace2 = { path = "../workspace2" } +workspace = { package = "workspace2", path = "../workspace2" } # welcome = { path = "../welcome" } # zed-actions = {path = "../zed-actions"} anyhow.workspace = true diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 6522d97fdc..79ba132e4f 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -4,18 +4,13 @@ // Allow binary to be called Zed for a nice application menu when running executable directly #![allow(non_snake_case)] -use crate::open_listener::{OpenListener, OpenRequest}; use anyhow::{anyhow, Context as _, Result}; use backtrace::Backtrace; -use cli::{ - ipc::{self, IpcSender}, - CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME, -}; +use cli::FORCE_CLI_MODE_ENV_VAR_NAME; use client::UserStore; -use collections::HashMap; use db::kvp::KEY_VALUE_STORE; use fs::RealFs; -use futures::{channel::mpsc, SinkExt, StreamExt}; +use futures::StreamExt; use gpui::{Action, App, AppContext, AsyncAppContext, Context, SemanticVersion, Task}; use isahc::{prelude::Configurable, Request}; use language::LanguageRegistry; @@ -35,7 +30,7 @@ use std::{ fs::OpenOptions, io::{IsTerminal, Write}, panic, - path::Path, + path::{Path, PathBuf}, sync::{ atomic::{AtomicU32, Ordering}, Arc, @@ -43,18 +38,18 @@ use std::{ thread, time::{SystemTime, UNIX_EPOCH}, }; -use text::Point; use util::{ async_maybe, channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL}, http::{self, HttpClient}, - paths::{self, PathLikeWithPosition}, - ResultExt, + paths, ResultExt, }; use uuid::Uuid; -use workspace2::{AppState, WorkspaceStore}; -use zed2::{build_window_options, initialize_workspace, languages}; -use zed2::{ensure_only_instance, Assets, IsOnlyInstance}; +use workspace::{AppState, WorkspaceStore}; +use zed2::{ + build_window_options, ensure_only_instance, handle_cli_connection, initialize_workspace, + languages, Assets, IsOnlyInstance, OpenListener, OpenRequest, +}; mod open_listener; @@ -143,7 +138,7 @@ fn main() { client::init(&client, cx); // command_palette::init(cx); language::init(cx); - // editor::init(cx); + editor::init(cx); // go_to_line::init(cx); // file_finder::init(cx); // outline::init(cx); @@ -194,7 +189,7 @@ fn main() { // audio::init(Assets, cx); // auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx); - workspace2::init(app_state.clone(), cx); + workspace::init(app_state.clone(), cx); // recent_projects::init(cx); // journal2::init(app_state.clone(), cx); @@ -213,6 +208,7 @@ fn main() { if stdout_is_a_pty() { cx.activate(true); let urls = collect_url_args(); + dbg!(&urls); if !urls.is_empty() { listener.open_urls(urls) } @@ -230,9 +226,27 @@ fn main() { let mut _triggered_authentication = false; + fn open_paths_and_log_errs( + paths: &[PathBuf], + app_state: &Arc, + cx: &mut AppContext, + ) { + let task = workspace::open_paths(&paths, &app_state, None, cx); + cx.spawn(|cx| async move { + if let Some((_window, results)) = task.await.log_err() { + for result in results { + if let Some(Err(e)) = result { + log::error!("Error opening path: {}", e); + } + } + } + }) + .detach(); + } + match open_rx.try_next() { Ok(Some(OpenRequest::Paths { paths })) => { - workspace2::open_paths(&paths, &app_state, None, cx).detach(); + open_paths_and_log_errs(&paths, &app_state, cx) } Ok(Some(OpenRequest::CliConnection { connection })) => { let app_state = app_state.clone(); @@ -240,6 +254,7 @@ fn main() { .detach(); } Ok(Some(OpenRequest::JoinChannel { channel_id: _ })) => { + todo!() // triggered_authentication = true; // let app_state = app_state.clone(); // let client = client.clone(); @@ -251,6 +266,9 @@ fn main() { // }) // .detach_and_log_err(cx) } + Ok(Some(OpenRequest::OpenChannelNotes { channel_id: _ })) => { + todo!() + } Ok(None) | Err(_) => cx .spawn({ let app_state = app_state.clone(); @@ -260,29 +278,25 @@ fn main() { } let app_state = app_state.clone(); - cx.spawn(|cx| { - async move { - while let Some(request) = open_rx.next().await { - match request { - OpenRequest::Paths { paths } => { - cx.update(|cx| workspace2::open_paths(&paths, &app_state, None, cx)) - .ok() - .map(|t| t.detach()); - } - OpenRequest::CliConnection { connection } => { - let app_state = app_state.clone(); - cx.spawn(move |cx| { - handle_cli_connection(connection, app_state.clone(), cx) - }) - .detach(); - } - OpenRequest::JoinChannel { channel_id: _ } => { - // cx - // .update(|cx| { - // workspace::join_channel(channel_id, app_state.clone(), None, cx) - // }) - // .detach() - } + cx.spawn(|cx| async move { + while let Some(request) = open_rx.next().await { + match request { + OpenRequest::Paths { paths } => { + cx.update(|cx| open_paths_and_log_errs(&paths, &app_state, cx)) + .ok(); + } + OpenRequest::CliConnection { connection } => { + let app_state = app_state.clone(); + cx.spawn(move |cx| { + handle_cli_connection(connection, app_state.clone(), cx) + }) + .detach(); + } + OpenRequest::JoinChannel { channel_id: _ } => { + todo!() + } + OpenRequest::OpenChannelNotes { channel_id: _ } => { + todo!() } } } @@ -325,8 +339,8 @@ async fn installation_id() -> Result { async fn restore_or_create_workspace(app_state: &Arc, mut cx: AsyncAppContext) { async_maybe!({ - if let Some(location) = workspace2::last_opened_workspace_paths().await { - cx.update(|cx| workspace2::open_paths(location.paths().as_ref(), app_state, None, cx))? + if let Some(location) = workspace::last_opened_workspace_paths().await { + cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx))? .await .log_err(); } else if matches!(KEY_VALUE_STORE.read_kvp("******* THIS IS A BAD KEY PLEASE UNCOMMENT BELOW TO FIX THIS VERY LONG LINE *******"), Ok(None)) { @@ -336,7 +350,7 @@ async fn restore_or_create_workspace(app_state: &Arc, mut cx: AsyncApp // cx.update(|cx| show_welcome_experience(app_state, cx)); } else { cx.update(|cx| { - workspace2::open_new(app_state, cx, |workspace, cx| { + workspace::open_new(app_state, cx, |workspace, cx| { // todo!(editor) // Editor::new_file(workspace, &Default::default(), cx) }) @@ -746,190 +760,6 @@ fn load_embedded_fonts(cx: &AppContext) { // #[cfg(not(debug_assertions))] // fn watch_file_types(_fs: Arc, _cx: &mut AppContext) {} -fn connect_to_cli( - server_name: &str, -) -> Result<(mpsc::Receiver, IpcSender)> { - let handshake_tx = cli::ipc::IpcSender::::connect(server_name.to_string()) - .context("error connecting to cli")?; - let (request_tx, request_rx) = ipc::channel::()?; - let (response_tx, response_rx) = ipc::channel::()?; - - handshake_tx - .send(IpcHandshake { - requests: request_tx, - responses: response_rx, - }) - .context("error sending ipc handshake")?; - - let (mut async_request_tx, async_request_rx) = - futures::channel::mpsc::channel::(16); - thread::spawn(move || { - while let Ok(cli_request) = request_rx.recv() { - if smol::block_on(async_request_tx.send(cli_request)).is_err() { - break; - } - } - Ok::<_, anyhow::Error>(()) - }); - - Ok((async_request_rx, response_tx)) -} - -async fn handle_cli_connection( - (mut requests, _responses): (mpsc::Receiver, IpcSender), - _app_state: Arc, - mut _cx: AsyncAppContext, -) { - if let Some(request) = requests.next().await { - match request { - CliRequest::Open { paths, wait } => { - let mut caret_positions = HashMap::default(); - - let paths = if paths.is_empty() { - workspace2::last_opened_workspace_paths() - .await - .map(|location| location.paths().to_vec()) - .unwrap_or_default() - } else { - paths - .into_iter() - .filter_map(|path_with_position_string| { - let path_with_position = PathLikeWithPosition::parse_str( - &path_with_position_string, - |path_str| { - Ok::<_, std::convert::Infallible>( - Path::new(path_str).to_path_buf(), - ) - }, - ) - .expect("Infallible"); - let path = path_with_position.path_like; - if let Some(row) = path_with_position.row { - if path.is_file() { - let row = row.saturating_sub(1); - let col = - path_with_position.column.unwrap_or(0).saturating_sub(1); - caret_positions.insert(path.clone(), Point::new(row, col)); - } - } - Some(path) - }) - .collect() - }; - - // todo!("editor") - // let mut errored = false; - // match cx - // .update(|cx| workspace2::open_paths(&paths, &app_state, None, cx)) - // .await - // { - // Ok((workspace, items)) => { - // let mut item_release_futures = Vec::new(); - - // for (item, path) in items.into_iter().zip(&paths) { - // match item { - // Some(Ok(item)) => { - // if let Some(point) = caret_positions.remove(path) { - // if let Some(active_editor) = item.downcast::() { - // active_editor - // .downgrade() - // .update(&mut cx, |editor, cx| { - // let snapshot = - // editor.snapshot(cx).display_snapshot; - // let point = snapshot - // .buffer_snapshot - // .clip_point(point, Bias::Left); - // editor.change_selections( - // Some(Autoscroll::center()), - // cx, - // |s| s.select_ranges([point..point]), - // ); - // }) - // .log_err(); - // } - // } - - // let released = oneshot::channel(); - // cx.update(|cx| { - // item.on_release( - // cx, - // Box::new(move |_| { - // let _ = released.0.send(()); - // }), - // ) - // .detach(); - // }); - // item_release_futures.push(released.1); - // } - // Some(Err(err)) => { - // responses - // .send(CliResponse::Stderr { - // message: format!("error opening {:?}: {}", path, err), - // }) - // .log_err(); - // errored = true; - // } - // None => {} - // } - // } - - // if wait { - // let background = cx.background(); - // let wait = async move { - // if paths.is_empty() { - // let (done_tx, done_rx) = oneshot::channel(); - // if let Some(workspace) = workspace.upgrade(&cx) { - // let _subscription = cx.update(|cx| { - // cx.observe_release(&workspace, move |_, _| { - // let _ = done_tx.send(()); - // }) - // }); - // drop(workspace); - // let _ = done_rx.await; - // } - // } else { - // let _ = - // futures::future::try_join_all(item_release_futures).await; - // }; - // } - // .fuse(); - // futures::pin_mut!(wait); - - // loop { - // // Repeatedly check if CLI is still open to avoid wasting resources - // // waiting for files or workspaces to close. - // let mut timer = background.timer(Duration::from_secs(1)).fuse(); - // futures::select_biased! { - // _ = wait => break, - // _ = timer => { - // if responses.send(CliResponse::Ping).is_err() { - // break; - // } - // } - // } - // } - // } - // } - // Err(error) => { - // errored = true; - // responses - // .send(CliResponse::Stderr { - // message: format!("error opening {:?}: {}", paths, error), - // }) - // .log_err(); - // } - // } - - // responses - // .send(CliResponse::Exit { - // status: i32::from(errored), - // }) - // .log_err(); - } - } - } -} - pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] { // &[ // ("Go to file", &file_finder::Toggle), diff --git a/crates/zed2/src/open_listener.rs b/crates/zed2/src/open_listener.rs index 9b416e14be..f4219f199d 100644 --- a/crates/zed2/src/open_listener.rs +++ b/crates/zed2/src/open_listener.rs @@ -1,15 +1,26 @@ -use anyhow::anyhow; +use anyhow::{anyhow, Context, Result}; +use cli::{ipc, IpcHandshake}; use cli::{ipc::IpcSender, CliRequest, CliResponse}; -use futures::channel::mpsc; +use editor::scroll::autoscroll::Autoscroll; +use editor::Editor; use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; +use futures::channel::{mpsc, oneshot}; +use futures::{FutureExt, SinkExt, StreamExt}; +use gpui::AsyncAppContext; +use language::{Bias, Point}; +use std::collections::HashMap; use std::ffi::OsStr; use std::os::unix::prelude::OsStrExt; +use std::path::Path; use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::thread; +use std::time::Duration; use std::{path::PathBuf, sync::atomic::AtomicBool}; use util::channel::parse_zed_link; +use util::paths::PathLikeWithPosition; use util::ResultExt; - -use crate::connect_to_cli; +use workspace::AppState; pub enum OpenRequest { Paths { @@ -21,6 +32,9 @@ pub enum OpenRequest { JoinChannel { channel_id: u64, }, + OpenChannelNotes { + channel_id: u64, + }, } pub struct OpenListener { @@ -74,7 +88,11 @@ impl OpenListener { if let Some(slug) = parts.next() { if let Some(id_str) = slug.split("-").last() { if let Ok(channel_id) = id_str.parse::() { - return Some(OpenRequest::JoinChannel { channel_id }); + if Some("notes") == parts.next() { + return Some(OpenRequest::OpenChannelNotes { channel_id }); + } else { + return Some(OpenRequest::JoinChannel { channel_id }); + } } } } @@ -96,3 +114,191 @@ impl OpenListener { Some(OpenRequest::Paths { paths }) } } + +fn connect_to_cli( + server_name: &str, +) -> Result<(mpsc::Receiver, IpcSender)> { + let handshake_tx = cli::ipc::IpcSender::::connect(server_name.to_string()) + .context("error connecting to cli")?; + let (request_tx, request_rx) = ipc::channel::()?; + let (response_tx, response_rx) = ipc::channel::()?; + + handshake_tx + .send(IpcHandshake { + requests: request_tx, + responses: response_rx, + }) + .context("error sending ipc handshake")?; + + let (mut async_request_tx, async_request_rx) = + futures::channel::mpsc::channel::(16); + thread::spawn(move || { + while let Ok(cli_request) = request_rx.recv() { + if smol::block_on(async_request_tx.send(cli_request)).is_err() { + break; + } + } + Ok::<_, anyhow::Error>(()) + }); + + Ok((async_request_rx, response_tx)) +} + +pub async fn handle_cli_connection( + (mut requests, responses): (mpsc::Receiver, IpcSender), + app_state: Arc, + mut cx: AsyncAppContext, +) { + if let Some(request) = requests.next().await { + match request { + CliRequest::Open { paths, wait } => { + let mut caret_positions = HashMap::new(); + + let paths = if paths.is_empty() { + workspace::last_opened_workspace_paths() + .await + .map(|location| location.paths().to_vec()) + .unwrap_or_default() + } else { + paths + .into_iter() + .filter_map(|path_with_position_string| { + let path_with_position = PathLikeWithPosition::parse_str( + &path_with_position_string, + |path_str| { + Ok::<_, std::convert::Infallible>( + Path::new(path_str).to_path_buf(), + ) + }, + ) + .expect("Infallible"); + let path = path_with_position.path_like; + if let Some(row) = path_with_position.row { + if path.is_file() { + let row = row.saturating_sub(1); + let col = + path_with_position.column.unwrap_or(0).saturating_sub(1); + caret_positions.insert(path.clone(), Point::new(row, col)); + } + } + Some(path) + }) + .collect() + }; + + let mut errored = false; + + match cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) { + Ok(task) => match task.await { + Ok((workspace, items)) => { + let mut item_release_futures = Vec::new(); + + for (item, path) in items.into_iter().zip(&paths) { + match item { + Some(Ok(item)) => { + if let Some(point) = caret_positions.remove(path) { + if let Some(active_editor) = item.downcast::() { + workspace + .update(&mut cx, |_, cx| { + active_editor.update(cx, |editor, cx| { + let snapshot = editor + .snapshot(cx) + .display_snapshot; + let point = snapshot + .buffer_snapshot + .clip_point(point, Bias::Left); + editor.change_selections( + Some(Autoscroll::center()), + cx, + |s| s.select_ranges([point..point]), + ); + }); + }) + .log_err(); + } + } + + cx.update(|cx| { + let released = oneshot::channel(); + item.on_release( + cx, + Box::new(move |_| { + let _ = released.0.send(()); + }), + ) + .detach(); + item_release_futures.push(released.1); + }) + .log_err(); + } + Some(Err(err)) => { + responses + .send(CliResponse::Stderr { + message: format!( + "error opening {:?}: {}", + path, err + ), + }) + .log_err(); + errored = true; + } + None => {} + } + } + + if wait { + let background = cx.background_executor().clone(); + let wait = async move { + if paths.is_empty() { + let (done_tx, done_rx) = oneshot::channel(); + let _subscription = + workspace.update(&mut cx, |workspace, cx| { + cx.on_release(move |_, _| { + let _ = done_tx.send(()); + }) + }); + let _ = done_rx.await; + } else { + let _ = futures::future::try_join_all(item_release_futures) + .await; + }; + } + .fuse(); + futures::pin_mut!(wait); + + loop { + // Repeatedly check if CLI is still open to avoid wasting resources + // waiting for files or workspaces to close. + let mut timer = background.timer(Duration::from_secs(1)).fuse(); + futures::select_biased! { + _ = wait => break, + _ = timer => { + if responses.send(CliResponse::Ping).is_err() { + break; + } + } + } + } + } + } + Err(error) => { + errored = true; + responses + .send(CliResponse::Stderr { + message: format!("error opening {:?}: {}", paths, error), + }) + .log_err(); + } + }, + Err(_) => errored = true, + } + + responses + .send(CliResponse::Exit { + status: i32::from(errored), + }) + .log_err(); + } + } + } +} diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index a33498538e..7368d3a5ef 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -7,216 +7,17 @@ mod only_instance; mod open_listener; pub use assets::*; -use collections::HashMap; use gpui::{ - point, px, AppContext, AsyncAppContext, AsyncWindowContext, Point, Task, TitlebarOptions, - WeakView, WindowBounds, WindowKind, WindowOptions, + point, px, AppContext, AsyncWindowContext, Task, TitlebarOptions, WeakView, WindowBounds, + WindowKind, WindowOptions, }; pub use only_instance::*; pub use open_listener::*; -use anyhow::{Context, Result}; -use cli::{ - ipc::{self, IpcSender}, - CliRequest, CliResponse, IpcHandshake, -}; -use futures::{ - channel::{mpsc, oneshot}, - FutureExt, SinkExt, StreamExt, -}; -use std::{path::Path, sync::Arc, thread, time::Duration}; -use util::{paths::PathLikeWithPosition, ResultExt}; +use anyhow::Result; +use std::sync::Arc; use uuid::Uuid; -use workspace2::{AppState, Workspace}; - -pub fn connect_to_cli( - server_name: &str, -) -> Result<(mpsc::Receiver, IpcSender)> { - let handshake_tx = cli::ipc::IpcSender::::connect(server_name.to_string()) - .context("error connecting to cli")?; - let (request_tx, request_rx) = ipc::channel::()?; - let (response_tx, response_rx) = ipc::channel::()?; - - handshake_tx - .send(IpcHandshake { - requests: request_tx, - responses: response_rx, - }) - .context("error sending ipc handshake")?; - - let (mut async_request_tx, async_request_rx) = - futures::channel::mpsc::channel::(16); - thread::spawn(move || { - while let Ok(cli_request) = request_rx.recv() { - if smol::block_on(async_request_tx.send(cli_request)).is_err() { - break; - } - } - Ok::<_, anyhow::Error>(()) - }); - - Ok((async_request_rx, response_tx)) -} - -pub async fn handle_cli_connection( - (mut requests, responses): (mpsc::Receiver, IpcSender), - app_state: Arc, - mut cx: AsyncAppContext, -) { - if let Some(request) = requests.next().await { - match request { - CliRequest::Open { paths, wait } => { - let mut caret_positions = HashMap::default(); - - let paths = if paths.is_empty() { - workspace2::last_opened_workspace_paths() - .await - .map(|location| location.paths().to_vec()) - .unwrap_or_default() - } else { - paths - .into_iter() - .filter_map(|path_with_position_string| { - let path_with_position = PathLikeWithPosition::parse_str( - &path_with_position_string, - |path_str| { - Ok::<_, std::convert::Infallible>( - Path::new(path_str).to_path_buf(), - ) - }, - ) - .expect("Infallible"); - let path = path_with_position.path_like; - if let Some(row) = path_with_position.row { - if path.is_file() { - let row = row.saturating_sub(1); - let col = - path_with_position.column.unwrap_or(0).saturating_sub(1); - caret_positions.insert(path.clone(), Point::new(row, col)); - } - } - Some(path) - }) - .collect::>() - }; - - let mut errored = false; - - if let Some(open_paths_task) = cx - .update(|cx| workspace2::open_paths(&paths, &app_state, None, cx)) - .log_err() - { - match open_paths_task.await { - Ok((workspace, items)) => { - let mut item_release_futures = Vec::new(); - - for (item, path) in items.into_iter().zip(&paths) { - match item { - Some(Ok(mut item)) => { - if let Some(point) = caret_positions.remove(path) { - todo!("editor") - // if let Some(active_editor) = item.downcast::() { - // active_editor - // .downgrade() - // .update(&mut cx, |editor, cx| { - // let snapshot = - // editor.snapshot(cx).display_snapshot; - // let point = snapshot - // .buffer_snapshot - // .clip_point(point, Bias::Left); - // editor.change_selections( - // Some(Autoscroll::center()), - // cx, - // |s| s.select_ranges([point..point]), - // ); - // }) - // .log_err(); - // } - } - - let released = oneshot::channel(); - cx.update(move |cx| { - item.on_release( - cx, - Box::new(move |_| { - let _ = released.0.send(()); - }), - ) - .detach(); - }) - .ok(); - item_release_futures.push(released.1); - } - Some(Err(err)) => { - responses - .send(CliResponse::Stderr { - message: format!( - "error opening {:?}: {}", - path, err - ), - }) - .log_err(); - errored = true; - } - None => {} - } - } - - if wait { - let executor = cx.background_executor().clone(); - let wait = async move { - if paths.is_empty() { - let (done_tx, done_rx) = oneshot::channel(); - let _subscription = - workspace.update(&mut cx, move |_, cx| { - cx.on_release(|_, _| { - let _ = done_tx.send(()); - }) - }); - let _ = done_rx.await; - } else { - let _ = futures::future::try_join_all(item_release_futures) - .await; - }; - } - .fuse(); - futures::pin_mut!(wait); - - loop { - // Repeatedly check if CLI is still open to avoid wasting resources - // waiting for files or workspaces to close. - let mut timer = executor.timer(Duration::from_secs(1)).fuse(); - futures::select_biased! { - _ = wait => break, - _ = timer => { - if responses.send(CliResponse::Ping).is_err() { - break; - } - } - } - } - } - } - Err(error) => { - errored = true; - responses - .send(CliResponse::Stderr { - message: format!("error opening {:?}: {}", paths, error), - }) - .log_err(); - } - } - - responses - .send(CliResponse::Exit { - status: i32::from(errored), - }) - .log_err(); - } - } - } - } -} +use workspace::{AppState, Workspace}; pub fn build_window_options( bounds: Option, @@ -257,7 +58,7 @@ pub fn initialize_workspace( let workspace_handle = cx.view(); cx.subscribe(&workspace_handle, { move |workspace, _, event, cx| { - if let workspace2::Event::PaneAdded(pane) = event { + if let workspace::Event::PaneAdded(pane) = event { pane.update(cx, |pane, cx| { pane.toolbar().update(cx, |toolbar, cx| { // todo!()