Detailed changes
@@ -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",
@@ -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" }
@@ -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,
@@ -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
@@ -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>) -> Self {
+ // Make sure we blink the cursors if the setting is re-enabled
+ cx.observe_global::<SettingsStore>(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>) {
+ 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<Self>) {
+ if epoch == self.blink_epoch {
+ self.blinking_paused = false;
+ self.blink_cursors(epoch, cx);
+ }
+ }
+
+ fn blink_cursors(&mut self, epoch: usize, cx: &mut ModelContext<Self>) {
+ 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>) {
+ 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>) {
+ self.enabled = false;
+ }
+
+ pub fn visible(&self) -> bool {
+ self.visible
+ }
+}
@@ -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<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
+type InlayHighlights = BTreeMap<TypeId, HashMap<InlayId, (HighlightStyle, InlayHighlight)>>;
+
+pub struct DisplayMap {
+ buffer: Model<MultiBuffer>,
+ buffer_subscription: BufferSubscription,
+ fold_map: FoldMap,
+ inlay_map: InlayMap,
+ tab_map: TabMap,
+ wrap_map: Model<WrapMap>,
+ block_map: BlockMap,
+ text_highlights: TextHighlights,
+ inlay_highlights: InlayHighlights,
+ pub clip_at_line_ends: bool,
+}
+
+impl DisplayMap {
+ pub fn new(
+ buffer: Model<MultiBuffer>,
+ font: Font,
+ font_size: Pixels,
+ wrap_width: Option<Pixels>,
+ buffer_header_height: u8,
+ excerpt_header_height: u8,
+ cx: &mut ModelContext<Self>,
+ ) -> 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<Self>) -> 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>) {
+ self.fold(
+ other
+ .folds_in_range(0..other.buffer_snapshot.len())
+ .map(|fold| fold.to_offset(&other.buffer_snapshot)),
+ cx,
+ );
+ }
+
+ pub fn fold<T: ToOffset>(
+ &mut self,
+ ranges: impl IntoIterator<Item = Range<T>>,
+ cx: &mut ModelContext<Self>,
+ ) {
+ 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<T: ToOffset>(
+ &mut self,
+ ranges: impl IntoIterator<Item = Range<T>>,
+ inclusive: bool,
+ cx: &mut ModelContext<Self>,
+ ) {
+ 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<Item = BlockProperties<Anchor>>,
+ cx: &mut ModelContext<Self>,
+ ) -> Vec<BlockId> {
+ 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<BlockId, RenderBlock>) {
+ self.block_map.replace(styles);
+ }
+
+ pub fn remove_blocks(&mut self, ids: HashSet<BlockId>, cx: &mut ModelContext<Self>) {
+ 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<Range<Anchor>>,
+ style: HighlightStyle,
+ ) {
+ self.text_highlights
+ .insert(Some(type_id), Arc::new((style, ranges)));
+ }
+
+ pub fn highlight_inlays(
+ &mut self,
+ type_id: TypeId,
+ highlights: Vec<InlayHighlight>,
+ 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<Anchor>])> {
+ 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<Self>) -> 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<Pixels>, cx: &mut ModelContext<Self>) -> bool {
+ self.wrap_map
+ .update(cx, |map, cx| map.set_wrap_width(width, cx))
+ }
+
+ pub fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
+ self.inlay_map.current_inlays()
+ }
+
+ pub fn splice_inlays(
+ &mut self,
+ to_remove: Vec<InlayId>,
+ to_insert: Vec<Inlay>,
+ cx: &mut ModelContext<Self>,
+ ) {
+ 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<MultiBuffer>, cx: &mut ModelContext<Self>) -> 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<HighlightStyle>,
+ pub suggestion_highlight_style: Option<HighlightStyle>,
+}
+
+pub struct HighlightedChunk<'a> {
+ pub chunk: &'a str,
+ pub style: Option<HighlightStyle>,
+ 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<Point>) -> Range<Point> {
+ 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<Item = &str> {
+ 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<Item = &str> {
+ (0..=display_row).into_iter().rev().flat_map(|row| {
+ self.block_snapshot
+ .chunks(row..row + 1, false, Highlights::default())
+ .map(|h| h.text)
+ .collect::<Vec<_>>()
+ .into_iter()
+ .rev()
+ })
+ }
+
+ pub fn chunks<'a>(
+ &'a self,
+ display_rows: Range<u32>,
+ language_aware: bool,
+ inlay_highlight_style: Option<HighlightStyle>,
+ suggestion_highlight_style: Option<HighlightStyle>,
+ ) -> 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<u32>,
+ // language_aware: bool,
+ // style: &'a EditorStyle,
+ // ) -> impl Iterator<Item = HighlightedChunk<'a>> {
+ // 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<Item = (char, DisplayPoint)> + '_ {
+ 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<Item = (char, DisplayPoint)> + '_ {
+ 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<T>(&self, range: Range<T>) -> impl Iterator<Item = &Range<Anchor>>
+ where
+ T: ToOffset,
+ {
+ self.fold_snapshot.folds_in_range(range)
+ }
+
+ pub fn blocks_in_range(
+ &self,
+ rows: Range<u32>,
+ ) -> impl Iterator<Item = (u32, &TransformBlock)> {
+ self.block_snapshot.blocks_in_range(rows)
+ }
+
+ pub fn intersects_fold<T: ToOffset>(&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<u32> {
+ 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<FoldStatus> {
+ 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<Range<Point>> {
+ 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<Tag: ?Sized + 'static>(
+ &self,
+ ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
+ let type_id = TypeId::of::<Tag>();
+ self.text_highlights.get(&Some(type_id)).cloned()
+ }
+
+ #[cfg(any(test, feature = "test-support"))]
+ pub fn inlay_highlights<Tag: ?Sized + 'static>(
+ &self,
+ ) -> Option<&HashMap<InlayId, (HighlightStyle, InlayHighlight)>> {
+ let type_id = TypeId::of::<Tag>();
+ 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<Item = u32> {
+ 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::<String>();
+// 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::<SettingsStore, _, _>(|store, cx| {
+// store.update_user_settings::<AllLanguageSettings>(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::<Vec<_>>();
+// 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::<String>(),
+// "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::<String>(),
+// "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::<String>(),
+// "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::<String>()
+// .lines()
+// .next(),
+// Some(" b bbbbb")
+// );
+// assert_eq!(
+// map.update(cx, |map, cx| map.snapshot(cx))
+// .text_chunks(2)
+// .collect::<String>()
+// .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::<MyType>(),
+// 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::<String>(),
+// "✅ α\nβ \n🏀β γ"
+// );
+// assert_eq!(map.text_chunks(1).collect::<String>(), "β \n🏀β γ");
+// assert_eq!(map.text_chunks(2).collect::<String>(), "🏀β γ");
+
+// 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<u32>,
+// map: &Model<DisplayMap>,
+// theme: &'a SyntaxTheme,
+// cx: &mut AppContext,
+// ) -> Vec<(String, Option<Hsla>)> {
+// chunks(rows, map, theme, cx)
+// .into_iter()
+// .map(|(text, color, _)| (text, color))
+// .collect()
+// }
+
+// fn chunks<'a>(
+// rows: Range<u32>,
+// map: &Model<DisplayMap>,
+// theme: &'a SyntaxTheme,
+// cx: &mut AppContext,
+// ) -> Vec<(String, Option<Hsla>, Option<Hsla>)> {
+// let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
+// let mut chunks: Vec<(String, Option<Hsla>, Option<Hsla>)> = 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::<SettingsStore, _, _>(|store, cx| {
+// store.update_user_settings::<AllLanguageSettings>(cx, f);
+// });
+// }
+// }
@@ -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<WrapSnapshot>,
+ blocks: Vec<Arc<Block>>,
+ transforms: RefCell<SumTree<Transform>>,
+ buffer_header_height: u8,
+ excerpt_header_height: u8,
+}
+
+pub struct BlockMapWriter<'a>(&'a mut BlockMap);
+
+pub struct BlockSnapshot {
+ wrap_snapshot: WrapSnapshot,
+ transforms: SumTree<Transform>,
+}
+
+#[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<dyn Fn(&mut BlockContext) -> AnyElement<Editor>>;
+
+pub struct Block {
+ id: BlockId,
+ position: Anchor,
+ height: u8,
+ style: BlockStyle,
+ render: Mutex<RenderBlock>,
+ disposition: BlockDisposition,
+}
+
+#[derive(Clone)]
+pub struct BlockProperties<P>
+where
+ P: Clone,
+{
+ pub position: P,
+ pub height: u8,
+ pub style: BlockStyle,
+ pub render: Arc<dyn Fn(&mut BlockContext) -> AnyElement<Editor>>,
+ 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<TransformBlock>,
+}
+
+#[allow(clippy::large_enum_variant)]
+#[derive(Clone)]
+pub enum TransformBlock {
+ Custom(Arc<Block>),
+ ExcerptHeader {
+ id: ExcerptId,
+ buffer: BufferSnapshot,
+ range: ExcerptRange<text::Anchor>,
+ 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<u32>) -> 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<u32>) -> BlockMapWriter {
+ self.sync(&wrap_snapshot, edits);
+ *self.wrap_snapshot.borrow_mut() = wrap_snapshot;
+ BlockMapWriter(self)
+ }
+
+ fn sync(&self, wrap_snapshot: &WrapSnapshot, mut edits: Patch<u32>) {
+ 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::<WrapRow>();
+ 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<BlockId, RenderBlock>) {
+ for block in &self.blocks {
+ if let Some(render) = renderers.remove(&block.id) {
+ *block.render.lock() = render;
+ }
+ }
+ }
+}
+
+fn push_isomorphic(tree: &mut SumTree<Transform>, 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<Item = BlockProperties<Anchor>>,
+ ) -> Vec<BlockId> {
+ 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<BlockId>) {
+ 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<u32>,
+ 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<u32>,
+ ) -> impl Iterator<Item = (u32, &TransformBlock)> {
+ let mut cursor = self.transforms.cursor::<BlockRow>();
+ 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<Self::Item> {
+ 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<u32>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ 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<Editor> {
+ 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::<Vec<_>>();
+
+// // 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::<Vec<_>>(),
+// &[
+// 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::<String>();
+// 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::<Vec<_>>();
+
+// 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::<Vec<_>>();
+// 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::<Vec<_>>();
+// 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::<String>();
+// assert_eq!(
+// actual_text, expected_text,
+// "incorrect text starting from row {}",
+// start_row
+// );
+// assert_eq!(
+// blocks_snapshot
+// .buffer_rows(start_row as u32)
+// .collect::<Vec<_>>(),
+// &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::<Vec<_>>(),
+// 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<TransformBlock> 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)
+// }
+// }
+// }
@@ -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<T: ToOffset>(
+ &mut self,
+ ranges: impl IntoIterator<Item = Range<T>>,
+ ) -> (FoldSnapshot, Vec<FoldEdit>) {
+ 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::<Fold>();
+ 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<T: ToOffset>(
+ &mut self,
+ ranges: impl IntoIterator<Item = Range<T>>,
+ inclusive: bool,
+ ) -> (FoldSnapshot, Vec<FoldEdit>) {
+ 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::<usize>();
+ 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<Hsla>,
+}
+
+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<InlayEdit>,
+ ) -> (FoldSnapshot, Vec<FoldEdit>) {
+ let edits = self.sync(inlay_snapshot, edits);
+ self.check_invariants();
+ (self.snapshot.clone(), edits)
+ }
+
+ pub fn write(
+ &mut self,
+ inlay_snapshot: InlaySnapshot,
+ edits: Vec<InlayEdit>,
+ ) -> (FoldMapWriter, FoldSnapshot, Vec<FoldEdit>) {
+ 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<InlayEdit>,
+ ) -> Vec<FoldEdit> {
+ 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::<InlayOffset>();
+ 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::<Fold>();
+ 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<Transform>,
+ folds: SumTree<Fold>,
+ pub inlay_snapshot: InlaySnapshot,
+ pub version: usize,
+ pub ellipses_color: Option<Hsla>,
+}
+
+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<FoldPoint>) -> 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<T>(&self, range: Range<T>) -> impl Iterator<Item = &Range<Anchor>>
+ 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<T>(&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::<InlayOffset>();
+ 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::<InlayPoint>();
+ 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<FoldOffset>,
+ 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<Item = char> {
+ 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<Fold>,
+ range: Range<T>,
+ 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<InlayEdit>) {
+ 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<FoldEdit>) {
+ 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<Anchor>);
+
+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<u32>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ 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<Hsla>,
+}
+
+impl<'a> Iterator for FoldChunks<'a> {
+ type Item = Chunk<'a>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ 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<TypeId>,
+ style: HighlightStyle,
+}
+
+impl PartialOrd for HighlightEndpoint {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ 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<FoldOffset>;
+
+#[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::<Vec<_>>();
+ 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::<String>();
+ 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::<String>(),
+ text,
+ );
+ }
+
+ let mut fold_row = 0;
+ while fold_row < expected_buffer_rows.len() as u32 {
+ assert_eq!(
+ snapshot.buffer_rows(fold_row).collect::<Vec<_>>(),
+ 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::<HashSet<_>>();
+ 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::<Vec<_>>();
+
+ assert_eq!(
+ snapshot
+ .folds_in_range(start..end)
+ .cloned()
+ .collect::<Vec<_>>(),
+ 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::<Vec<_>>(),
+ [Some(0), Some(3), Some(5), Some(6)]
+ );
+ assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [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<Range<usize>> {
+ 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<FoldEdit>)> {
+ 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
+ }
+ }
+}
@@ -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<Inlay>,
+}
+
+#[derive(Clone)]
+pub struct InlaySnapshot {
+ pub buffer: MultiBufferSnapshot,
+ transforms: SumTree<Transform>,
+ 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<T: Into<Rope>>(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<InlayOffset>;
+
+#[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<TypeId>,
+ style: HighlightStyle,
+}
+
+impl PartialOrd for HighlightEndpoint {
+ fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
+ 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<Chunk<'a>>,
+ inlay_chunks: Option<text::Chunks<'a>>,
+ inlay_chunk: Option<&'a str>,
+ output_offset: InlayOffset,
+ max_output_offset: InlayOffset,
+ inlay_highlight_style: Option<HighlightStyle>,
+ suggestion_highlight_style: Option<HighlightStyle>,
+ highlight_endpoints: Peekable<vec::IntoIter<HighlightEndpoint>>,
+ active_highlights: BTreeMap<Option<TypeId>, 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<Self::Item> {
+ 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<u32>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ 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<text::Edit<usize>>,
+ ) -> (InlaySnapshot, Vec<InlayEdit>) {
+ 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<InlayId>,
+ to_insert: Vec<Inlay>,
+ ) -> (InlaySnapshot, Vec<InlayEdit>) {
+ 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<Item = &Inlay> {
+ self.inlays.iter()
+ }
+
+ #[cfg(test)]
+ pub(crate) fn randomly_mutate(
+ &mut self,
+ next_inlay_id: &mut usize,
+ rng: &mut rand::rngs::StdRng,
+ ) -> (InlaySnapshot, Vec<InlayEdit>) {
+ 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::<String>();
+
+ 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<InlayOffset>) -> 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::<TextSummary, _>(prefix_start..prefix_end);
+ }
+ Some(Transform::Inlay(inlay)) => {
+ let prefix_end = overshoot;
+ summary += inlay.text.cursor(0).summary::<TextSummary>(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<InlayOffset>,
+ 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<InlayOffset>,
+ text_highlights: &TreeMap<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>,
+ highlight_endpoints: &mut Vec<HighlightEndpoint>,
+ ) {
+ 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<Transform>, 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<_>>(),
+ 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::<String>();
+ 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::<Vec<_>>();
+ 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::<Vec<_>>();
+ 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::<Vec<_>>(),
+ &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::<Vec<_>>();
+ 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::<String>();
+ 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);
+ }
+}
@@ -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<FoldEdit>,
+ tab_size: NonZeroU32,
+ ) -> (TabSnapshot, Vec<TabEdit>) {
+ 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<TabPoint>) -> 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<TabPoint>,
+ 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<Item = char>, 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<Item = char>,
+ 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<Point> for TabPoint {
+ fn from(point: Point) -> Self {
+ Self(point)
+ }
+}
+
+pub type TabEdit = text::Edit<TabPoint>;
+
+#[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<Self::Item> {
+ 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::<String>(),
+ &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::<String>();
+ 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::<String>();
+ let expected_summary = TextSummary::from(expected_text.as_str());
+ assert_eq!(
+ tabs_snapshot
+ .chunks(start..end, false, Highlights::default())
+ .map(|c| c.text)
+ .collect::<String>(),
+ 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})"
+ );
+ }
+ }
+}
@@ -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<u32>;
+
+pub struct WrapMap {
+ snapshot: WrapSnapshot,
+ pending_edits: VecDeque<(TabSnapshot, Vec<TabEdit>)>,
+ interpolated_edits: Patch<u32>,
+ edits_since_sync: Patch<u32>,
+ wrap_width: Option<Pixels>,
+ background_task: Option<Task<()>>,
+ font_with_size: (Font, Pixels),
+}
+
+#[derive(Clone)]
+pub struct WrapSnapshot {
+ tab_snapshot: TabSnapshot,
+ transforms: SumTree<Transform>,
+ 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<u32>,
+ 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<Pixels>,
+ cx: &mut AppContext,
+ ) -> (Model<Self>, 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<TabEdit>,
+ cx: &mut ModelContext<Self>,
+ ) -> (WrapSnapshot, Patch<u32>) {
+ 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<Self>,
+ ) -> 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<Pixels>,
+ cx: &mut ModelContext<Self>,
+ ) -> 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>) {
+ 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<Self>) {
+ 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<u32> {
+ let mut new_transforms;
+ if tab_edits.is_empty() {
+ new_transforms = self.transforms.clone();
+ } else {
+ let mut old_cursor = self.transforms.cursor::<TabPoint>();
+
+ 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<u32> {
+ #[derive(Debug)]
+ struct RowEdit {
+ old_rows: Range<u32>,
+ new_rows: Range<u32>,
+ }
+
+ 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::<TabPoint>();
+
+ 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::<Transform>::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<u32> {
+ let mut wrap_edits = Vec::new();
+ let mut old_cursor = self.transforms.cursor::<TransformSummary>();
+ let mut new_cursor = new_snapshot.transforms.cursor::<TransformSummary>();
+ 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<u32>,
+ 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<u32> {
+ let mut cursor = self.transforms.cursor::<WrapPoint>();
+ 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::<WrapPoint>();
+ 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<u32> {
+ 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::<Vec<_>>(),
+ // &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<Self::Item> {
+ 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<u32>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ 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<Transform>, 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<Transform> {
+ 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<WrapEdit>) {
+ 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::<String>();
+// 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::<String>();
+
+// 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<f32>,
+// 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<Item = &str> {
+// 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::<String>();
+// 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::<Vec<_>>()
+// .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::<String>();
+// assert_eq!(
+// expected_text,
+// actual_text,
+// "chunks != highlighted_chunks for rows {:?}",
+// start_row..end_row
+// );
+// }
+// }
+// }
+// }
@@ -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<Tag: 'static>(
+// parsed: &language::ParsedMarkdown,
+// editor_style: &EditorStyle,
+// workspace: Option<WeakView<Workspace>>,
+// cx: &mut ViewContext<Editor>,
+// ) -> 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::<Vec<_>>(),
+// // )
+// // .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::<Editor, _>(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<usize>,
+}
+
+#[derive(Clone, Default, Deserialize, PartialEq)]
+pub struct ConfirmCodeAction {
+ #[serde(default)]
+ pub item_ix: Option<usize>,
+}
+
+#[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::<Editor>(cx);
+ workspace::register_followable_item::<Editor>(cx);
+ workspace::register_deserializable_item::<Editor>(cx);
+}
+
+trait InvalidationRegion {
+ fn ranges(&self) -> &[Range<Anchor>];
+}
+
+#[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<f32>,
+ },
+ End,
+}
+
+#[derive(Clone, Debug)]
+pub enum SelectMode {
+ Character,
+ Word(Range<Anchor>),
+ Line(Range<Anchor>),
+ 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<TextStyle>,
+ // 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<HighlightStyle>;
+
+type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Vec<Range<Anchor>>);
+type InlayBackgroundHighlight = (fn(&ThemeColors) -> Hsla, Vec<InlayHighlight>);
+
+pub struct Editor {
+ handle: WeakView<Self>,
+ focus_handle: FocusHandle,
+ buffer: Model<MultiBuffer>,
+ display_map: Model<DisplayMap>,
+ pub selections: SelectionsCollection,
+ pub scroll_manager: ScrollManager,
+ columnar_selection_tail: Option<Anchor>,
+ add_selections_state: Option<AddSelectionsState>,
+ select_next_state: Option<SelectNextState>,
+ select_prev_state: Option<SelectNextState>,
+ selection_history: SelectionHistory,
+ autoclose_regions: Vec<AutocloseRegion>,
+ snippet_stack: InvalidationStack<SnippetState>,
+ select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
+ ime_transaction: Option<TransactionId>,
+ active_diagnostics: Option<ActiveDiagnosticGroup>,
+ soft_wrap_mode_override: Option<language_settings::SoftWrap>,
+ // get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
+ // override_text_style: Option<Box<OverrideTextStyle>>,
+ project: Option<Model<Project>>,
+ collaboration_hub: Option<Box<dyn CollaborationHub>>,
+ focused: bool,
+ blink_manager: Model<BlinkManager>,
+ pub show_local_selections: bool,
+ mode: EditorMode,
+ show_gutter: bool,
+ show_wrap_guides: Option<bool>,
+ placeholder_text: Option<Arc<str>>,
+ highlighted_rows: Option<Range<u32>>,
+ background_highlights: BTreeMap<TypeId, BackgroundHighlight>,
+ inlay_background_highlights: TreeMap<Option<TypeId>, InlayBackgroundHighlight>,
+ nav_history: Option<ItemNavHistory>,
+ context_menu: RwLock<Option<ContextMenu>>,
+ // mouse_context_menu: View<context_menu::ContextMenu>,
+ completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
+ next_completion_id: CompletionId,
+ available_code_actions: Option<(Model<Buffer>, Arc<[CodeAction]>)>,
+ code_actions_task: Option<Task<()>>,
+ document_highlights_task: Option<Task<()>>,
+ pending_rename: Option<RenameState>,
+ searchable: bool,
+ cursor_shape: CursorShape,
+ collapse_matches: bool,
+ autoindent_mode: Option<AutoindentMode>,
+ workspace: Option<(WeakView<Workspace>, i64)>,
+ // keymap_context_layers: BTreeMap<TypeId, KeymapContext>,
+ input_enabled: bool,
+ read_only: bool,
+ leader_peer_id: Option<PeerId>,
+ remote_id: Option<ViewId>,
+ 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<Subscription>,
+ pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
+}
+
+pub struct EditorSnapshot {
+ pub mode: EditorMode,
+ pub show_gutter: bool,
+ pub display_snapshot: DisplaySnapshot,
+ pub placeholder_text: Option<Arc<str>>,
+ is_focused: bool,
+ scroll_anchor: ScrollAnchor,
+ ongoing_scroll: OngoingScroll,
+}
+
+pub struct RemoteSelection {
+ pub replica_id: ReplicaId,
+ pub selection: Selection<Anchor>,
+ pub cursor_shape: CursorShape,
+ pub peer_id: PeerId,
+ pub line_mode: bool,
+ pub participant_index: Option<ParticipantIndex>,
+}
+
+#[derive(Clone, Debug)]
+struct SelectionHistoryEntry {
+ selections: Arc<[Selection<Anchor>]>,
+ select_next_state: Option<SelectNextState>,
+ select_prev_state: Option<SelectNextState>,
+ add_selections_state: Option<AddSelectionsState>,
+}
+
+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<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
+ mode: SelectionHistoryMode,
+ undo_stack: VecDeque<SelectionHistoryEntry>,
+ redo_stack: VecDeque<SelectionHistoryEntry>,
+}
+
+impl SelectionHistory {
+ fn insert_transaction(
+ &mut self,
+ transaction_id: TransactionId,
+ selections: Arc<[Selection<Anchor>]>,
+ ) {
+ self.selections_by_transaction
+ .insert(transaction_id, (selections, None));
+ }
+
+ #[allow(clippy::type_complexity)]
+ fn transaction(
+ &self,
+ transaction_id: TransactionId,
+ ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
+ self.selections_by_transaction.get(&transaction_id)
+ }
+
+ #[allow(clippy::type_complexity)]
+ fn transaction_mut(
+ &mut self,
+ transaction_id: TransactionId,
+ ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
+ 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<usize>,
+}
+
+#[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::<Self>())
+ .field("wordwise", &self.wordwise)
+ .field("done", &self.done)
+ .finish()
+ }
+}
+
+#[derive(Debug)]
+struct AutocloseRegion {
+ selection_id: usize,
+ range: Range<Anchor>,
+ pair: BracketPair,
+}
+
+#[derive(Debug)]
+struct SnippetState {
+ ranges: Vec<Vec<Range<Anchor>>>,
+ active_index: usize,
+}
+
+pub struct RenameState {
+ pub range: Range<Anchor>,
+ pub old_name: Arc<str>,
+ pub editor: View<Editor>,
+ block_id: BlockId,
+}
+
+struct InvalidationStack<T>(Vec<T>);
+
+enum ContextMenu {
+ Completions(CompletionsMenu),
+ CodeActions(CodeActionsMenu),
+}
+
+impl ContextMenu {
+ fn select_first(
+ &mut self,
+ project: Option<&Model<Project>>,
+ cx: &mut ViewContext<Editor>,
+ ) -> 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<Project>>,
+ cx: &mut ViewContext<Editor>,
+ ) -> 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<Project>>,
+ cx: &mut ViewContext<Editor>,
+ ) -> 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<Project>>,
+ cx: &mut ViewContext<Editor>,
+ ) -> 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<WeakView<Workspace>>,
+ cx: &mut ViewContext<Editor>,
+ ) -> (DisplayPoint, AnyElement<Editor>) {
+ 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<Buffer>,
+ completions: Arc<RwLock<Box<[Completion]>>>,
+ 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<Project>>, cx: &mut ViewContext<Editor>) {
+ 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<Project>>, cx: &mut ViewContext<Editor>) {
+ 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<Project>>, cx: &mut ViewContext<Editor>) {
+ 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<Project>>, cx: &mut ViewContext<Editor>) {
+ 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<Model<Project>>,
+ cx: &mut ViewContext<Editor>,
+ ) {
+ 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<Project>>,
+ cx: &mut ViewContext<Editor>,
+ ) {
+ 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<RwLock<Box<[Completion]>>>,
+ completion_index: usize,
+ completion: lsp::CompletionItem,
+ client: Arc<Client>,
+ language_registry: Arc<LanguageRegistry>,
+ ) {
+ 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<lsp::LanguageServer>,
+ completions: Arc<RwLock<Box<[Completion]>>>,
+ completion_index: usize,
+ completion: lsp::CompletionItem,
+ language_registry: Arc<LanguageRegistry>,
+ ) {
+ 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::<lsp::request::ResolveCompletionItem>(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<WeakView<Workspace>>,
+ cx: &mut ViewContext<Editor>,
+ ) {
+ todo!("old implementation below")
+ }
+ // ) -> AnyElement<Editor> {
+ // 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::<CompletionTag, _>(
+ // 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::<MultiLineDocumentation>(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::<MultiLineDocumentation>(0, None, cx)
+ // .with_child(render_parsed_markdown::<MultiLineDocumentation>(
+ // 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<Buffer>,
+ selected_item: usize,
+ list: UniformListState,
+ deployed_from_indicator: bool,
+}
+
+impl CodeActionsMenu {
+ fn select_first(&mut self, cx: &mut ViewContext<Editor>) {
+ self.selected_item = 0;
+ self.list.scroll_to(ScrollTarget::Show(self.selected_item));
+ cx.notify()
+ }
+
+ fn select_prev(&mut self, cx: &mut ViewContext<Editor>) {
+ 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<Editor>) {
+ 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<Editor>) {
+ 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<Editor>,
+ ) -> (DisplayPoint, AnyElement<Editor>) {
+ 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::<ActionTag, _>(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<ExcerptId>,
+ pending_refresh: Task<Option<()>>,
+ pending_cycling_refresh: Task<Option<()>>,
+ cycled: bool,
+ completions: Vec<copilot::Completion>,
+ active_completion_index: usize,
+ suggestion: Option<Inlay>,
+}
+
+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<T1: Iterator<Item = char>, T2: Iterator<Item = char>>(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<Anchor>,
+ primary_message: String,
+ blocks: HashMap<BlockId, Diagnostic>,
+ 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<Editor>);
+
+enum GotoDefinitionKind {
+ Symbol,
+ Type,
+}
+
+#[derive(Debug, Clone)]
+enum InlayHintRefreshReason {
+ Toggle(bool),
+ SettingsChange(InlayHintSettings),
+ NewLinesShown,
+ BufferEdited(HashSet<Arc<Language>>),
+ RefreshRequested,
+ ExcerptsRemoved(Vec<ExcerptId>),
+}
+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<Arc<GetFieldEditorTheme>>,
+ // cx: &mut ViewContext<Self>,
+ // ) -> 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<Arc<GetFieldEditorTheme>>,
+ // cx: &mut ViewContext<Self>,
+ // ) -> 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<Arc<GetFieldEditorTheme>>,
+ // cx: &mut ViewContext<Self>,
+ // ) -> 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<Buffer>,
+ project: Option<Model<Project>>,
+ cx: &mut ViewContext<Self>,
+ ) -> Self {
+ let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+ Self::new(EditorMode::Full, buffer, project, cx)
+ }
+
+ pub fn for_multibuffer(
+ buffer: Model<MultiBuffer>,
+ project: Option<Model<Project>>,
+ cx: &mut ViewContext<Self>,
+ ) -> Self {
+ Self::new(EditorMode::Full, buffer, project, cx)
+ }
+
+ pub fn clone(&self, cx: &mut ViewContext<Self>) -> 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<MultiBuffer>,
+ project: Option<Model<Project>>,
+ // todo!()
+ // get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
+ cx: &mut ViewContext<Self>,
+ ) -> 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::<ThemeSettings>(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::<SettingsStore>(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<Workspace>,
+ // ) {
+ // 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<Workspace>,
+ // ) {
+ // 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<PeerId> {
+ // self.leader_peer_id
+ // }
+
+ pub fn buffer(&self) -> &Model<MultiBuffer> {
+ &self.buffer
+ }
+
+ fn workspace(&self) -> Option<View<Workspace>> {
+ 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<Arc<Language>> {
+ // self.buffer.read(cx).language_at(point, cx)
+ // }
+
+ // pub fn file_at<'a, T: ToOffset>(&self, point: T, cx: &'a AppContext) -> Option<Arc<dyn File>> {
+ // self.buffer.read(cx).read(cx).file_at(point).cloned()
+ // }
+
+ // pub fn active_excerpt(
+ // &self,
+ // cx: &AppContext,
+ // ) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
+ // self.buffer
+ // .read(cx)
+ // .excerpt_containing(self.selections.newest_anchor().head(), cx)
+ // }
+
+ // pub fn style(&self, cx: &AppContext) -> EditorStyle {
+ // build_style(
+ // settings::get::<ThemeSettings>(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<dyn CollaborationHub>) {
+ // self.collaboration_hub = Some(hub);
+ // }
+
+ // pub fn set_placeholder_text(
+ // &mut self,
+ // placeholder_text: impl Into<Arc<str>>,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // self.placeholder_text = Some(placeholder_text.into());
+ // cx.notify();
+ // }
+
+ // pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
+ // self.cursor_shape = cursor_shape;
+ // cx.notify();
+ // }
+
+ // pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
+ // self.collapse_matches = collapse_matches;
+ // }
+
+ pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
+ 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<Self>) {
+ // 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<Tag: 'static>(
+ // &mut self,
+ // context: KeymapContext,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // self.keymap_context_layers
+ // .insert(TypeId::of::<Tag>(), context);
+ // cx.notify();
+ // }
+
+ // pub fn remove_keymap_context_layer<Tag: 'static>(&mut self, cx: &mut ViewContext<Self>) {
+ // self.keymap_context_layers.remove(&TypeId::of::<Tag>());
+ // 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<Arc<GetFieldEditorTheme>>,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // self.get_field_editor_theme = style;
+ // cx.notify();
+ // }
+
+ fn selections_did_change(
+ &mut self,
+ local: bool,
+ old_cursor_position: &Anchor,
+ cx: &mut ViewContext<Self>,
+ ) {
+ 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<R>(
+ &mut self,
+ autoscroll: Option<Autoscroll>,
+ cx: &mut ViewContext<Self>,
+ 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<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
+ where
+ I: IntoIterator<Item = (Range<S>, T)>,
+ S: ToOffset,
+ T: Into<Arc<str>>,
+ {
+ if self.read_only {
+ return;
+ }
+
+ self.buffer
+ .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
+ }
+
+ // pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
+ // where
+ // I: IntoIterator<Item = (Range<S>, T)>,
+ // S: ToOffset,
+ // T: Into<Arc<str>>,
+ // {
+ // 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<I, S, T>(
+ // &mut self,
+ // edits: I,
+ // original_indent_columns: Vec<u32>,
+ // cx: &mut ViewContext<Self>,
+ // ) where
+ // I: IntoIterator<Item = (Range<S>, T)>,
+ // S: ToOffset,
+ // T: Into<Arc<str>>,
+ // {
+ // 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>) {
+ 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<Self>,
+ ) {
+ let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+ let tail = self.selections.newest::<usize>(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<Self>,
+ ) {
+ 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<Self>,
+ ) {
+ 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::<Point>(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<f32>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ 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>) {
+ self.columnar_selection_tail.take();
+ if self.selections.pending_anchor().is_some() {
+ let selections = self.selections.all::<usize>(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<Self>,
+ ) {
+ 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::<Vec<_>>();
+
+ 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<Self>) {
+ // 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<Self>) {
+ // let text: Arc<str> = 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::<usize, _>(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::<Vec<_>>();
+
+ // 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>) {
+ // self.transact(cx, |this, cx| {
+ // let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
+ // let selections = this.selections.all::<usize>(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::<usize>();
+
+ // let trailing_whitespace_len = buffer
+ // .chars_at(end)
+ // .take_while(|c| c.is_whitespace() && *c != '\n')
+ // .map(|c| c.len_utf8())
+ // .sum::<usize>();
+
+ // 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<Self>) {
+ // 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<Self>) {
+ // 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>) {
+ // 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<AutoindentMode>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ if self.read_only {
+ return;
+ }
+
+ let text: Arc<str> = 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::<Vec<_>>()
+ };
+ 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<Self>) {
+ // 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<Self>) {
+ // let selections = self.selections.all::<usize>(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<Item = Selection<D>>,
+ // buffer: &'a MultiBufferSnapshot,
+ // ) -> impl Iterator<Item = (Selection<D>, 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<Anchor>],
+ 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<String> {
+ 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::<String>(),
+ )
+ } else {
+ None
+ }
+ }
+
+ // pub fn toggle_inlay_hints(&mut self, _: &ToggleInlayHints, cx: &mut ViewContext<Self>) {
+ // 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<Self>) {
+ 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<Inlay> {
+ 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<Arc<Language>>>,
+ cx: &mut ViewContext<Editor>,
+ ) -> HashMap<ExcerptId, (Model<Buffer>, clock::Global, Range<usize>)> {
+ 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<InlayId>,
+ to_insert: Vec<Inlay>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ 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<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // 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<Self>) {
+ 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<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // 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::<usize>(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::<String>();
+
+ // 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<Range<isize>> = 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<Self>) {
+ // 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<Workspace>,
+ // ) -> Option<Task<Result<()>>> {
+ // let editor = workspace.active_item(cx)?.act_as::<Editor>(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<Editor
+ // workspace: WeakViewHandle<Workspace
+ // transaction: ProjectTransaction,
+ // title: String,
+ // mut cx: AsyncAppContext,
+ // ) -> Result<()> {
+ // let replica_id = this.read_with(&cx, |this, cx| this.replica_id(cx))?;
+
+ // let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
+ // 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::<usize>(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::<usize>(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::<Self>(
+ // ranges_to_highlight,
+ // |theme| theme.editor.highlighted_line_background,
+ // cx,
+ // );
+ // });
+ // })?;
+
+ // Ok(())
+ // }
+
+ fn refresh_code_actions(&mut self, cx: &mut ViewContext<Self>) -> 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<Self>) -> 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::<DocumentHighlightRead>(
+ read_ranges,
+ |theme| todo!("theme.editor.document_highlight_read_background"),
+ cx,
+ );
+ this.highlight_background::<DocumentHighlightWrite>(
+ 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<Self>,
+ ) -> 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<Self>,
+ ) -> 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<Self>) {
+ 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<Self>) {
+ 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<Self>,
+ ) {
+ 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<Self>) -> 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<Self>) -> 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<Self>,
+ ) -> 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<Self>) -> Option<Inlay> {
+ 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<Self>) {
+ 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>) {
+ 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<Self>,
+ // ) -> Option<AnyElement<Self>> {
+ // if self.available_code_actions.is_some() {
+ // enum CodeActions {}
+ // Some(
+ // MouseEventHandler::new::<CodeActions, _>(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<Option<(FoldStatus, u32, bool)>>,
+ // style: &EditorStyle,
+ // gutter_hovered: bool,
+ // line_height: f32,
+ // gutter_margin: f32,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Vec<Option<AnyElement<Self>>> {
+ // 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::<FoldIndicators, _>(
+ // 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<Editor>,
+ // ) -> Option<(DisplayPoint, AnyElement<Editor>)> {
+ // 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<Self>) -> Option<ContextMenu> {
+ 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<usize>],
+ // snippet: Snippet,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Result<()> {
+ // let tabstops = self.buffer.update(cx, |buffer, cx| {
+ // let snippet_text: Arc<str> = 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::<Vec<_>>();
+ // tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
+ // tabstop_ranges
+ // })
+ // .collect::<Vec<_>>()
+ // });
+
+ // 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<Self>) -> bool {
+ // self.move_to_snippet_tabstop(Bias::Right, cx)
+ // }
+
+ // pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) -> bool {
+ // self.move_to_snippet_tabstop(Bias::Left, cx)
+ // }
+
+ // pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext<Self>) -> 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>) {
+ // self.transact(cx, |this, cx| {
+ // this.select_all(&SelectAll, cx);
+ // this.insert("", cx);
+ // });
+ // }
+
+ // pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
+ // self.transact(cx, |this, cx| {
+ // this.select_autoclose_pair(cx);
+ // let mut selections = this.selections.all::<Point>(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>) {
+ // 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<Self>) {
+ // if self.move_to_prev_snippet_tabstop(cx) {
+ // return;
+ // }
+
+ // self.outdent(&Outdent, cx);
+ // }
+
+ // pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
+ // 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::<String>()));
+ // 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<Self>) {
+ // let mut selections = self.selections.all::<Point>(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<Point>,
+ // edits: &mut Vec<(Range<Point>, 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::<String>(),
+ // ));
+
+ // // 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<Self>) {
+ // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+ // let selections = self.selections.all::<Point>(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<str> = "".into();
+ // buffer.edit(
+ // deletion_ranges
+ // .into_iter()
+ // .map(|range| (range, empty_str.clone())),
+ // None,
+ // cx,
+ // );
+ // });
+ // let selections = this.selections.all::<usize>(cx);
+ // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
+ // });
+ // }
+
+ // pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext<Self>) {
+ // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+ // let selections = self.selections.all::<Point>(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<str> = "".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<Self>) {
+ // let mut row_ranges = Vec::<Range<u32>>::new();
+ // for selection in self.selections.all::<Point>(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>,
+ // ) {
+ // self.manipulate_lines(cx, |lines| lines.sort())
+ // }
+
+ // pub fn sort_lines_case_insensitive(
+ // &mut self,
+ // _: &SortLinesCaseInsensitive,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // self.manipulate_lines(cx, |lines| lines.sort_by_key(|line| line.to_lowercase()))
+ // }
+
+ // pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext<Self>) {
+ // self.manipulate_lines(cx, |lines| lines.reverse())
+ // }
+
+ // pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext<Self>) {
+ // self.manipulate_lines(cx, |lines| lines.shuffle(&mut thread_rng()))
+ // }
+
+ // fn manipulate_lines<Fn>(&mut self, cx: &mut ViewContext<Self>, 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::<Point>(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::<String>();
+ // 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>) {
+ // self.manipulate_text(cx, |text| text.to_uppercase())
+ // }
+
+ // pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext<Self>) {
+ // self.manipulate_text(cx, |text| text.to_lowercase())
+ // }
+
+ // pub fn convert_to_title_case(&mut self, _: &ConvertToTitleCase, cx: &mut ViewContext<Self>) {
+ // 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>) {
+ // self.manipulate_text(cx, |text| text.to_case(Case::Snake))
+ // }
+
+ // pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext<Self>) {
+ // self.manipulate_text(cx, |text| text.to_case(Case::Kebab))
+ // }
+
+ // pub fn convert_to_upper_camel_case(
+ // &mut self,
+ // _: &ConvertToUpperCamelCase,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // 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>,
+ // ) {
+ // self.manipulate_text(cx, |text| text.to_case(Case::Camel))
+ // }
+
+ // fn manipulate_text<Fn>(&mut self, cx: &mut ViewContext<Self>, 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::<usize>(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::<String>();
+ // 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<Self>) {
+ // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+ // let buffer = &display_map.buffer_snapshot;
+ // let selections = self.selections.all::<Point>(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::<String>();
+ // 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<Self>) {
+ // 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::<Point>(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::<String>();
+
+ // 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<Self>) {
+ // 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::<Point>(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<Self>) {
+ // 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<usize>, 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::<usize>(cx);
+ // this.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ // s.select(selections);
+ // });
+ // });
+ // }
+
+ // pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
+ // let mut text = String::new();
+ // let buffer = self.buffer.read(cx).snapshot(cx);
+ // let mut selections = self.selections.all::<Point>(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<Self>) {
+ // let selections = self.selections.all::<Point>(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>) {
+ // 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::<Vec<ClipboardSelection>>() {
+ // let old_selections = this.selections.all::<usize>(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::<usize>(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<Self>) {
+ // 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<Self>) {
+ // 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>) {
+ // self.buffer
+ // .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
+ // }
+
+ // pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext<Self>) {
+ // 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>) {
+ // 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>) {
+ // 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>) {
+ // 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<Self>) {
+ // 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<Self>) {
+ // 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<Self>) {
+ // 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>) {
+ // 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<Self>) {
+ // 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<Self>) {
+ // 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<Self>) {
+ // 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<Self>) {
+ // 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<Self>) {
+ // 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<Self>) {
+ // 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>,
+ // ) {
+ // 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>,
+ // ) {
+ // 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>,
+ // ) {
+ // 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>,
+ // ) {
+ // 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>,
+ // ) {
+ // 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>,
+ // ) {
+ // 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>) {
+ // 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>,
+ // ) {
+ // 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>) {
+ // 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>,
+ // ) {
+ // 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>) {
+ // 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>,
+ // ) {
+ // 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>,
+ // ) {
+ // 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>,
+ // ) {
+ // 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>,
+ // ) {
+ // 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>) {
+ // 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>,
+ // ) {
+ // 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>) {
+ // 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>) {
+ // 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<Self>,
+ // ) {
+ // 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<Self>,
+ // ) {
+ // 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<Self>,
+ // ) {
+ // 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<Self>,
+ // ) {
+ // 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<Self>) {
+ // 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<Self>) {
+ // let mut selection = self.selections.last::<Point>(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<Self>) {
+ // 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<ItemNavHistory>) {
+ // 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<Point>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ 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<Self>) {
+ // let buffer = self.buffer.read(cx).snapshot(cx);
+ // let mut selection = self.selections.first::<usize>(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<Self>) {
+ // 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<Self>) {
+ // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+ // let mut selections = self.selections.all::<Point>(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<Self>,
+ // ) {
+ // let mut to_unfold = Vec::new();
+ // let mut new_selection_ranges = Vec::new();
+ // {
+ // let selections = self.selections.all::<Point>(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>) {
+ // self.add_selection(true, cx);
+ // }
+
+ // pub fn add_selection_below(&mut self, _: &AddSelectionBelow, cx: &mut ViewContext<Self>) {
+ // self.add_selection(false, cx);
+ // }
+
+ // fn add_selection(&mut self, above: bool, cx: &mut ViewContext<Self>) {
+ // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+ // let mut selections = self.selections.all::<Point>(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<Autoscroll>,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Result<()> {
+ // fn select_next_match_ranges(
+ // this: &mut Editor,
+ // range: Range<usize>,
+ // replace_newest: bool,
+ // auto_scroll: Option<Autoscroll>,
+ // cx: &mut ViewContext<Editor>,
+ // ) {
+ // 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::<usize>(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::<String>();
+
+ // 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::<String>();
+ // 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<Self>,
+ // ) -> 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<Self>) -> 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<Self>,
+ // ) -> 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::<usize>(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::<String>();
+ // let query = query.chars().rev().collect::<String>();
+ // 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::<String>();
+ // let query = query.chars().rev().collect::<String>();
+ // 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<Self>) {
+ // let text_layout_details = &self.text_layout_details(cx);
+ // self.transact(cx, |this, cx| {
+ // let mut selections = this.selections.all::<Point>(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<str> = "".into();
+ // let mut suffixes_inserted = Vec::new();
+
+ // fn comment_prefix_range(
+ // snapshot: &MultiBufferSnapshot,
+ // row: u32,
+ // comment_prefix: &str,
+ // comment_prefix_whitespace: &str,
+ // ) -> Range<Point> {
+ // 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<Point> {
+ // 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::<Point>(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::<Point>(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<Self>,
+ // ) {
+ // 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::<usize>(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::<Vec<_>>();
+
+ // 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<Self>,
+ // ) {
+ // 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>,
+ // ) {
+ // 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>) {
+ // 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>) {
+ // 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>) {
+ // self.go_to_diagnostic_impl(Direction::Next, cx)
+ // }
+
+ // fn go_to_prev_diagnostic(&mut self, _: &GoToPrevDiagnostic, cx: &mut ViewContext<Self>) {
+ // self.go_to_diagnostic_impl(Direction::Prev, cx)
+ // }
+
+ // pub fn go_to_diagnostic_impl(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
+ // let buffer = self.buffer.read(cx).snapshot(cx);
+ // let selection = self.selections.newest::<usize>(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<Self>) {
+ // let snapshot = self
+ // .display_map
+ // .update(cx, |display_map, cx| display_map.snapshot(cx));
+ // let selection = self.selections.newest::<Point>(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<Self>) {
+ // let snapshot = self
+ // .display_map
+ // .update(cx, |display_map, cx| display_map.snapshot(cx));
+ // let selection = self.selections.newest::<Point>(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<Item = DiffHunk<u32>>,
+ // cx: &mut ViewContext<Editor>,
+ // ) -> 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>) {
+ self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx);
+ }
+
+ pub fn go_to_type_definition(&mut self, _: &GoToTypeDefinition, cx: &mut ViewContext<Self>) {
+ self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx);
+ }
+
+ pub fn go_to_definition_split(&mut self, _: &GoToDefinitionSplit, cx: &mut ViewContext<Self>) {
+ self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, cx);
+ }
+
+ pub fn go_to_type_definition_split(
+ &mut self,
+ _: &GoToTypeDefinitionSplit,
+ cx: &mut ViewContext<Self>,
+ ) {
+ 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<Self>,
+ ) {
+ let Some(workspace) = self.workspace() else {
+ return;
+ };
+ let buffer = self.buffer.read(cx);
+ let head = self.selections.newest::<usize>(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<GoToDefinitionLink>,
+ split: bool,
+ cx: &mut ViewContext<Editor>,
+ ) {
+ 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<Self> =
+ 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::<String>()
+ )
+ })
+ }
+ 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::<Vec<_>>();
+ (title, location_tasks)
+ })
+ .context("location tasks preparation")?;
+
+ let locations = futures::future::join_all(location_tasks)
+ .await
+ .into_iter()
+ .filter_map(|location| location.transpose())
+ .collect::<Result<_>>()
+ .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<Editor>,
+ ) -> Task<anyhow::Result<Option<Location>>> {
+ 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<Workspace>,
+ // ) -> Option<Task<Result<()>>> {
+ // let active_item = workspace.active_item(cx)?;
+ // let editor_handle = active_item.act_as::<Self>(cx)?;
+
+ // let editor = editor_handle.read(cx);
+ // let buffer = editor.buffer.read(cx);
+ // let head = editor.selections.newest::<usize>(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::<String>()
+ // )
+ // })
+ // .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<Location>,
+ replica_id: ReplicaId,
+ title: String,
+ split: bool,
+ cx: &mut ViewContext<Workspace>,
+ ) {
+ // 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::<Self>(
+ 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<Self>) -> Option<Task<Result<()>>> {
+ // 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<str> = 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::<String>()
+ // .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::<DocumentHighlightWrite>(cx)
+ // .into_iter()
+ // .flat_map(|(_, ranges)| ranges.into_iter())
+ // .chain(
+ // this.clear_background_highlights::<DocumentHighlightRead>(cx)
+ // .into_iter()
+ // .flat_map(|(_, ranges)| ranges.into_iter()),
+ // )
+ // .collect();
+
+ // this.highlight_text::<Rename>(
+ // 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<Workspace>,
+ // ) -> Option<Task<Result<()>>> {
+ // let editor = workspace.active_item(cx)?.act_as::<Editor>(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<Self>,
+ ) -> Option<RenameState> {
+ let rename = self.pending_rename.take()?;
+ self.remove_blocks(
+ [rename.block_id].into_iter().collect(),
+ Some(Autoscroll::fit()),
+ cx,
+ );
+ self.clear_highlights::<Rename>(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::<usize>(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<Self>) -> Option<Task<Result<()>>> {
+ // 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<Project>,
+ trigger: FormatTrigger,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Result<()>> {
+ 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<Self>) {
+ // 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<Self>) {
+ // cx.show_character_palette();
+ // }
+
+ fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext<Editor>) {
+ 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<Self>) -> 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::<Point>(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::<Vec<_>>();
+ // 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<Self>) {
+ // 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<Selection<Anchor>>,
+ // pending_selection: Option<Selection<Anchor>>,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // 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<Self>,
+ update: impl FnOnce(&mut Self, &mut ViewContext<Self>),
+ ) -> Option<TransactionId> {
+ 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<Self>) {
+ 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<Self>,
+ ) -> Option<TransactionId> {
+ 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<Self>) {
+ // 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<Self>) {
+ // 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::<Point>(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<Self>) {
+ // let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+ // let buffer = &display_map.buffer_snapshot;
+ // let selections = self.selections.all::<Point>(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::<Vec<_>>();
+
+ // self.unfold_ranges(ranges, true, true, cx);
+ // }
+
+ // pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext<Self>) {
+ // 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::<Point>(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<Self>) {
+ // let selections = self.selections.all::<Point>(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<T: ToOffset + Clone>(
+ &mut self,
+ ranges: impl IntoIterator<Item = Range<T>>,
+ auto_scroll: bool,
+ cx: &mut ViewContext<Self>,
+ ) {
+ 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<T: ToOffset + Clone>(
+ &mut self,
+ ranges: impl IntoIterator<Item = Range<T>>,
+ inclusive: bool,
+ auto_scroll: bool,
+ cx: &mut ViewContext<Self>,
+ ) {
+ 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>,
+ // ) {
+ // self.gutter_hovered = *hovered;
+ // cx.notify();
+ // }
+
+ // pub fn insert_blocks(
+ // &mut self,
+ // blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
+ // autoscroll: Option<Autoscroll>,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Vec<BlockId> {
+ // 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<BlockId, RenderBlock>,
+ // autoscroll: Option<Autoscroll>,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // 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<BlockId>,
+ autoscroll: Option<Autoscroll>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ 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<Arc<str>>, cx: &mut ViewContext<Self>) {
+ // 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>,
+ // ) {
+ // self.soft_wrap_mode_override = Some(mode);
+ // cx.notify();
+ // }
+
+ // pub fn set_wrap_width(&self, width: Option<f32>, 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<Self>) {
+ // 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>) {
+ // self.show_gutter = show_gutter;
+ // cx.notify();
+ // }
+
+ // pub fn set_show_wrap_guides(&mut self, show_gutter: bool, cx: &mut ViewContext<Self>) {
+ // self.show_wrap_guides = Some(show_gutter);
+ // cx.notify();
+ // }
+
+ // pub fn reveal_in_finder(&mut self, _: &RevealInFinder, cx: &mut ViewContext<Self>) {
+ // 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<Self>) {
+ // 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<Self>) {
+ // 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<Range<u32>>) {
+ // self.highlighted_rows = rows;
+ // }
+
+ // pub fn highlighted_rows(&self) -> Option<Range<u32>> {
+ // self.highlighted_rows.clone()
+ // }
+
+ pub fn highlight_background<T: 'static>(
+ &mut self,
+ ranges: Vec<Range<Anchor>>,
+ color_fetcher: fn(&ThemeColors) -> Hsla,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.background_highlights
+ .insert(TypeId::of::<T>(), (color_fetcher, ranges));
+ cx.notify();
+ }
+
+ // pub fn highlight_inlay_background<T: 'static>(
+ // &mut self,
+ // ranges: Vec<InlayHighlight>,
+ // color_fetcher: fn(&Theme) -> Color,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // // TODO: no actual highlights happen for inlays currently, find a way to do that
+ // self.inlay_background_highlights
+ // .insert(Some(TypeId::of::<T>()), (color_fetcher, ranges));
+ // cx.notify();
+ // }
+
+ // pub fn clear_background_highlights<T: 'static>(
+ // &mut self,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<BackgroundHighlight> {
+ // let text_highlights = self.background_highlights.remove(&TypeId::of::<T>());
+ // let inlay_highlights = self
+ // .inlay_background_highlights
+ // .remove(&Some(TypeId::of::<T>()));
+ // 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<Self>,
+ // ) -> Vec<(Range<DisplayPoint>, 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<Item = &Range<Anchor>> {
+ // let read_highlights = self
+ // .background_highlights
+ // .get(&TypeId::of::<DocumentHighlightRead>())
+ // .map(|h| &h.1);
+ // let write_highlights = self
+ // .background_highlights
+ // .get(&TypeId::of::<DocumentHighlightWrite>())
+ // .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<Anchor>,
+ // display_snapshot: &DisplaySnapshot,
+ // theme: &Theme,
+ // ) -> Vec<(Range<DisplayPoint>, 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<T: 'static>(
+ // &self,
+ // search_range: Range<Anchor>,
+ // display_snapshot: &DisplaySnapshot,
+ // count: usize,
+ // ) -> Vec<RangeInclusive<DisplayPoint>> {
+ // let mut results = Vec::new();
+ // let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) 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<Point>, end: Option<Point>| {
+ // 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<Point> = None;
+ // let mut end_row: Option<Point> = 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<T: 'static>(
+ // &mut self,
+ // ranges: Vec<Range<Anchor>>,
+ // style: HighlightStyle,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // self.display_map.update(cx, |map, _| {
+ // map.highlight_text(TypeId::of::<T>(), ranges, style)
+ // });
+ // cx.notify();
+ // }
+
+ // pub fn highlight_inlays<T: 'static>(
+ // &mut self,
+ // highlights: Vec<InlayHighlight>,
+ // style: HighlightStyle,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // self.display_map.update(cx, |map, _| {
+ // map.highlight_inlays(TypeId::of::<T>(), highlights, style)
+ // });
+ // cx.notify();
+ // }
+
+ // pub fn text_highlights<'a, T: 'static>(
+ // &'a self,
+ // cx: &'a AppContext,
+ // ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
+ // self.display_map.read(cx).text_highlights(TypeId::of::<T>())
+ // }
+
+ pub fn clear_highlights<T: 'static>(&mut self, cx: &mut ViewContext<Self>) {
+ let cleared = self
+ .display_map
+ .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
+ 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<MultiBuffer>, cx: &mut ViewContext<Self>) {
+ cx.notify();
+ }
+
+ fn on_buffer_event(
+ &mut self,
+ multibuffer: Model<MultiBuffer>,
+ event: &multi_buffer::Event,
+ cx: &mut ViewContext<Self>,
+ ) {
+ 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::<HashSet<_>>();
+ 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<DisplayMap>, cx: &mut ViewContext<Self>) {
+ cx.notify();
+ }
+
+ fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
+ 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<Workspace>) {
+ // let active_item = workspace.active_item(cx);
+ // let editor_handle = if let Some(editor) = active_item
+ // .as_ref()
+ // .and_then(|item| item.act_as::<Self>(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::<usize>(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::<Self>(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<Workspace>,
+ // ) {
+ // let editor = workspace.open_path(path, None, true, cx);
+ // cx.spawn(|_, mut cx| async move {
+ // let editor = editor
+ // .await?
+ // .downcast::<Editor>()
+ // .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<Vec<Range<OffsetUtf16>>> {
+ // let snapshot = self.buffer.read(cx).read(cx);
+ // let (_, ranges) = self.text_highlights::<InputComposition>(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<OffsetUtf16>,
+ // cx: &AppContext,
+ // ) -> Vec<Range<OffsetUtf16>> {
+ // let selections = self.selections.all::<OffsetUtf16>(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<String>,
+ 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<String>,
+ _cx: &AppContext,
+ ) {
+ }
+
+ #[cfg(not(any(test, feature = "test-support")))]
+ fn report_editor_event(
+ &self,
+ operation: &'static str,
+ file_extension: Option<String>,
+ 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::<SettingsStore>()
+ .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<Self>) {
+ // 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<Chunk> = 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<Range<isize>>,
+ // cx: &mut ViewContext<Self>,
+ // ) {
+ // 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::<OffsetUtf16>(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<PeerId, Collaborator>;
+ fn user_participant_indices<'a>(
+ &self,
+ cx: &'a AppContext,
+ ) -> &'a HashMap<u64, ParticipantIndex>;
+}
+
+impl CollaborationHub for Model<Project> {
+ fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
+ self.read(cx).collaborators()
+ }
+
+ fn user_participant_indices<'a>(
+ &self,
+ cx: &'a AppContext,
+ ) -> &'a HashMap<u64, ParticipantIndex> {
+ 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<Point>>,
+ selection: &Selection<Point>,
+ display_map: &DisplaySnapshot,
+ selections: &mut std::iter::Peekable<std::slice::Iter<Selection<Point>>>,
+) -> (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<Point>, 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<Anchor>,
+ collaboration_hub: &dyn CollaborationHub,
+ cx: &'a AppContext,
+ ) -> impl 'a + Iterator<Item = RemoteSelection> {
+ 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::<HashMap<_, _>>();
+ 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<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
+ self.display_snapshot.buffer_snapshot.language_at(position)
+ }
+
+ pub fn is_focused(&self) -> bool {
+ self.is_focused
+ }
+
+ pub fn placeholder_text(&self) -> Option<&Arc<str>> {
+ self.placeholder_text.as_ref()
+ }
+
+ pub fn scroll_position(&self) -> gpui::Point<f32> {
+ 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<str>,
+ },
+ InputHandled {
+ utf16_range_to_replace: Option<Range<isize>>,
+ text: Arc<str>,
+ },
+ ExcerptsAdded {
+ buffer: Model<Buffer>,
+ predecessor: ExcerptId,
+ excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
+ },
+ ExcerptsRemoved {
+ ids: Vec<ExcerptId>,
+ },
+ BufferEdited,
+ Edited,
+ Reparsed,
+ Focused,
+ Blurred,
+ DirtyChanged,
+ Saved,
+ TitleChanged,
+ DiffBaseChanged,
+ SelectionsChanged {
+ local: bool,
+ },
+ ScrollPositionChanged {
+ local: bool,
+ autoscroll: bool,
+ },
+ Closed,
+}
+
+pub struct EditorFocused(pub View<Editor>);
+pub struct EditorBlurred(pub View<Editor>);
+pub struct EditorReleased(pub WeakView<Editor>);
+
+// 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<Self>;
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+ // todo!()
+ div()
+ }
+}
+
+// impl View for Editor {
+// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+// 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<Editor>| {
+// 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<Self>) {
+// 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::<Editor>() {
+// 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<Self>) {
+// 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<Self>,
+// ) -> 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::<LinkGoToDefinitionState>(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<usize>, cx: &AppContext) -> Option<String> {
+// 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<Range<usize>> {
+// // 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::<OffsetUtf16>(cx).range();
+// Some(range.start.0..range.end.0)
+// }
+
+// fn marked_text_range(&self, cx: &AppContext) -> Option<Range<usize>> {
+// let snapshot = self.buffer.read(cx).read(cx);
+// let range = self.text_highlights::<InputComposition>(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>) {
+// self.clear_highlights::<InputComposition>(cx);
+// self.ime_transaction.take();
+// }
+
+// fn replace_text_in_range(
+// &mut self,
+// range_utf16: Option<Range<usize>>,
+// text: &str,
+// cx: &mut ViewContext<Self>,
+// ) {
+// 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::<OffsetUtf16>(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<Range<usize>>,
+// text: &str,
+// new_selected_range_utf16: Option<Range<usize>>,
+// cx: &mut ViewContext<Self>,
+// ) {
+// 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::<OffsetUtf16>(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::<Vec<_>>()
+// };
+
+// if text.is_empty() {
+// this.unmark_text(cx);
+// } else {
+// this.highlight_text::<InputComposition>(
+// 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::<Vec<_>>();
+
+// 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::<InputComposition>(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<usize>;
+ fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range<Point>;
+ fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
+ fn spanned_rows(&self, include_end_if_at_line_start: bool, map: &DisplaySnapshot)
+ -> Range<u32>;
+}
+
+impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
+ fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range<Point> {
+ 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<usize> {
+ 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<DisplayPoint> {
+ 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<u32> {
+ 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<T: InvalidationRegion> InvalidationStack<T> {
+ fn invalidate<S>(&mut self, selections: &[Selection<S>], 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<T> Default for InvalidationStack<T> {
+ fn default() -> Self {
+ Self(Default::default())
+ }
+}
+
+impl<T> Deref for InvalidationStack<T> {
+ type Target = Vec<T>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl<T> DerefMut for InvalidationStack<T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+impl InvalidationRegion for SnippetState {
+ fn ranges(&self) -> &[Range<Anchor>] {
+ &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::<BlockContext, _>(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::<BlockContextToolip>(
+ // cx.block_id,
+ // "Copy diagnostic message",
+ // None,
+ // tooltip_style,
+ // cx,
+ // )
+ // .into_any()
+ })
+}
+
+pub fn highlight_diagnostic_message(
+ initial_highlights: Vec<usize>,
+ message: &str,
+) -> (String, Vec<usize>) {
+ 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<Item = (Range<usize>, HighlightStyle)>,
+// match_indices: &[usize],
+// ) -> Vec<(Range<usize>, 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<Item = (Range<usize>, 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<usize>, 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<Item = &'a str> + '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<Anchor>;
+}
+
+impl<T: ToOffset> RangeToAnchorExt for Range<T> {
+ fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
+ snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
+ }
+}
@@ -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<bool>,
+ pub hover_popover_enabled: Option<bool>,
+ pub show_completions_on_input: Option<bool>,
+ pub show_completion_documentation: Option<bool>,
+ pub use_on_type_format: Option<bool>,
+ pub scrollbar: Option<ScrollbarContent>,
+ pub relative_line_numbers: Option<bool>,
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+pub struct ScrollbarContent {
+ pub show: Option<ShowScrollbar>,
+ pub git_diff: Option<bool>,
+ pub selections: Option<bool>,
+}
+
+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> {
+ Self::load_via_json_merge(default_value, user_values)
+ }
+}
@@ -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<Pixels>::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<Pixels>::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<Pixels>::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<Pixels>::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<Pixels>::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<Pixels>::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::<Vec<_>>(),
+// snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
+// );
+// assert_set_eq!(
+// cloned_editor.read_with(cx, |editor, cx| editor.selections.ranges::<Point>(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::<Workspace>::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<NavigationEntry> {
+// 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<Pixels>::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<Pixels>::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<Pixels>::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<Pixels>::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::<Point>(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::<Point>(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::<Point>(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::<Point>(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::<Point>(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::<Point>(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::<Point>(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::<Point>(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#"
+// <body>ˇ
+// <script>
+// var x = 1;ˇ
+// </script>
+// </body>ˇ
+// "#
+// .unindent(),
+// );
+
+// // Precondition: different languages are active at different locations.
+// cx.update_editor(|editor, cx| {
+// let snapshot = editor.snapshot(cx);
+// let cursors = editor.selections.ranges::<usize>(cx);
+// let languages = cursors
+// .iter()
+// .map(|c| snapshot.language_at(c.start).unwrap().name())
+// .collect::<Vec<_>>();
+// 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#"
+// <body><aˇ>
+// <script>
+// var x = 1;<aˇ
+// </script>
+// </body><aˇ>
+// "#
+// .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#"
+// <body><a b={c(ˇ)}>
+// <script>
+// var x = 1;<a b={c(ˇ)}
+// </script>
+// </body><a b={c(ˇ)}>
+// "#
+// .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#"
+// <body><a b={c()d}ˇ>
+// <script>
+// var x = 1;<a b={c()d}ˇ
+// </script>
+// </body><a b={c()d}ˇ>
+// "#
+// .unindent(),
+// );
+// cx.update_editor(|editor, cx| {
+// editor.handle_input(">", cx);
+// });
+// cx.assert_editor_state(
+// &r#"
+// <body><a b={c()d}>ˇ
+// <script>
+// var x = 1;<a b={c()d}>ˇ
+// </script>
+// </body><a b={c()d}>ˇ
+// "#
+// .unindent(),
+// );
+
+// // Reset
+// cx.set_state(
+// &r#"
+// <body>ˇ
+// <script>
+// var x = 1;ˇ
+// </script>
+// </body>ˇ
+// "#
+// .unindent(),
+// );
+
+// cx.update_editor(|editor, cx| {
+// editor.handle_input("<", cx);
+// });
+// cx.assert_editor_state(
+// &r#"
+// <body><ˇ>
+// <script>
+// var x = 1;<ˇ
+// </script>
+// </body><ˇ>
+// "#
+// .unindent(),
+// );
+
+// // When backspacing, the closing angle brackets are removed.
+// cx.update_editor(|editor, cx| {
+// editor.backspace(&Backspace, cx);
+// });
+// cx.assert_editor_state(
+// &r#"
+// <body>ˇ
+// <script>
+// var x = 1;ˇ
+// </script>
+// </body>ˇ
+// "#
+// .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#"
+// <body>/*ˇ
+// <script>
+// var x = 1;/*ˇ */
+// </script>
+// </body>/*ˇ
+// "#
+// .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::<Point>(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::<Point>(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::<Point>(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<Editor>, 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::<usize>(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::<lsp::request::Formatting, _, _>(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::<lsp::request::Formatting, _, _>(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::<lsp::request::Formatting, _, _>(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::<lsp::request::RangeFormatting, _, _>(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::<lsp::request::RangeFormatting, _, _>(
+// 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::<lsp::request::RangeFormatting, _, _>(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::<lsp::request::Formatting, _, _>(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::<lsp::request::Formatting, _, _>(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::<lsp::request::Formatting, _, _>(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::<lsp::notification::DidChangeTextDocument, _>({
+// 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::<lsp::request::Formatting, _, _>({
+// 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 <s|>
+// 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 <si|>
+// 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::<SettingsStore, _, _>(|settings, cx| {
+// settings.update_user_settings::<EditorSettings>(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.<clo|>", 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(), " -->".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#"
+// <p>A</p>ˇ
+// <p>B</p>ˇ
+// <p>C</p>ˇ
+// "#
+// .unindent(),
+// );
+// cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
+// cx.assert_editor_state(
+// &r#"
+// <!-- <p>A</p>ˇ -->
+// <!-- <p>B</p>ˇ -->
+// <!-- <p>C</p>ˇ -->
+// "#
+// .unindent(),
+// );
+// cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
+// cx.assert_editor_state(
+// &r#"
+// <p>A</p>ˇ
+// <p>B</p>ˇ
+// <p>C</p>ˇ
+// "#
+// .unindent(),
+// );
+
+// // Toggle comments for mixture of empty and non-empty selections, where
+// // multiple selections occupy a given line.
+// cx.set_state(
+// &r#"
+// <p>A«</p>
+// <p>ˇ»B</p>ˇ
+// <p>C«</p>
+// <p>ˇ»D</p>ˇ
+// "#
+// .unindent(),
+// );
+
+// cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
+// cx.assert_editor_state(
+// &r#"
+// <!-- <p>A«</p>
+// <p>ˇ»B</p>ˇ -->
+// <!-- <p>C«</p>
+// <p>ˇ»D</p>ˇ -->
+// "#
+// .unindent(),
+// );
+// cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
+// cx.assert_editor_state(
+// &r#"
+// <p>A«</p>
+// <p>ˇ»B</p>ˇ
+// <p>C«</p>
+// <p>ˇ»D</p>ˇ
+// "#
+// .unindent(),
+// );
+
+// // Toggle comments when different languages are active for different
+// // selections.
+// cx.set_state(
+// &r#"
+// ˇ<script>
+// ˇvar x = new Y();
+// ˇ</script>
+// "#
+// .unindent(),
+// );
+// cx.foreground().run_until_parked();
+// cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
+// cx.assert_editor_state(
+// &r#"
+// <!-- ˇ<script> -->
+// // ˇvar x = new Y();
+// <!-- ˇ</script> -->
+// "#
+// .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<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
+
+// editor.highlight_background::<Type1>(
+// 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::<Type2>(
+// 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<Deterministic>,
+// 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<Deterministic>, 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<Deterministic>, 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<Deterministic>,
+// 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<Deterministic>,
+// 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<Deterministic>,
+// 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::<copilot::request::GetCompletions, _, _>(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::<Editor>()
+// .unwrap();
+
+// fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|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<str> = "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::<lsp::request::Shutdown, _, _>(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::<lsp::request::Completion, _, _>(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::<lsp::request::ResolveCompletionItem, _, _>(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::<lsp::request::Completion, _, _>(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#"<p class="bgˇ" />"#);
+
+// // 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::<Vec<_>>(),
+// &["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::<Vec<_>>(),
+// &["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#"<p class="yelˇ" />"#);
+// 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::<Vec<_>>(),
+// &["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<DisplayPoint> {
+// 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<Editor>) {
+// 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<Output = ()> {
+// 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::<lsp::request::Completion, _, _>(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<Vec<(&'static str, &'static str)>>,
+// ) -> impl Future<Output = ()> {
+// 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::<Vec<_>>()
+// });
+
+// let mut request =
+// cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(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<copilot::request::Completion>,
+// completions_cycling: Vec<copilot::request::Completion>,
+// ) {
+// lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
+// let completions = completions.clone();
+// async move {
+// Ok(copilot::request::GetCompletionsResult {
+// completions: completions.clone(),
+// })
+// }
+// });
+// lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(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::<SettingsStore, _, _>(|store, cx| {
+// store.update_user_settings::<AllLanguageSettings>(cx, f);
+// });
+// });
+// }
+
+// pub(crate) fn update_test_project_settings(
+// cx: &mut TestAppContext,
+// f: impl Fn(&mut ProjectSettings),
+// ) {
+// cx.update(|cx| {
+// cx.update_global::<SettingsStore, _, _>(|store, cx| {
+// store.update_user_settings::<ProjectSettings>(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);
+// }
@@ -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<DisplayPoint>,
+ active_rows: Range<u32>,
+}
+
+impl SelectionLayout {
+ fn new<T: ToPoint + ToDisplayPoint + Clone>(
+ selection: Selection<T>,
+ 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<EditorStyle>,
+}
+
+impl EditorElement {
+ pub fn new(style: EditorStyle) -> Self {
+ Self {
+ style: Arc::new(style),
+ }
+ }
+
+ // fn attach_mouse_handlers(
+ // position_map: &Arc<PositionMap>,
+ // has_popovers: bool,
+ // visible_bounds: Bounds<Pixels>,
+ // text_bounds: Bounds<Pixels>,
+ // gutter_bounds: Bounds<Pixels>,
+ // bounds: Bounds<Pixels>,
+ // cx: &mut ViewContext<Editor>,
+ // ) {
+ // enum EditorElementMouseHandlers {}
+ // let view_id = cx.view_id();
+ // cx.scene().push_mouse_region(
+ // MouseRegion::new::<EditorElementMouseHandlers>(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::<GutterHandlers>(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<Pixels>,
+ // gutter_bounds: Bounds<Pixels>,
+ // cx: &mut EventContext<Editor>,
+ // ) -> 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<Pixels>,
+ // position_map: &PositionMap,
+ // text_bounds: Bounds<Pixels>,
+ // cx: &mut EventContext<Editor>,
+ // ) -> 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<Pixels>,
+ // cmd: bool,
+ // shift: bool,
+ // alt: bool,
+ // position_map: &PositionMap,
+ // text_bounds: Bounds<Pixels>,
+ // cx: &mut EventContext<Editor>,
+ // ) -> 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<Pixels>,
+ // cx: &mut EventContext<Editor>,
+ // ) -> 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<Pixels>::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<Pixels>::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<Pixels>,
+ // cx: &mut ViewContext<Editor>,
+ // ) -> 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<Pixels>,
+ // mut delta: gpui::Point<Pixels>,
+ // precise: bool,
+ // position_map: &PositionMap,
+ // bounds: Bounds<Pixels>,
+ // cx: &mut ViewContext<Editor>,
+ // ) -> 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<Pixels>::zero(), position_map.scroll_max);
+ // editor.scroll(scroll_position, axis, cx);
+
+ // true
+ // }
+
+ // fn paint_background(
+ // &self,
+ // gutter_bounds: Bounds<Pixels>,
+ // text_bounds: Bounds<Pixels>,
+ // layout: &LayoutState,
+ // cx: &mut ViewContext<Editor>,
+ // ) {
+ // 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<Pixels>::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<Pixels>::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<Pixels>::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<Pixels>,
+ // visible_bounds: Bounds<Pixels>,
+ // layout: &mut LayoutState,
+ // editor: &mut Editor,
+ // cx: &mut ViewContext<Editor>,
+ // ) {
+ // 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::<ProjectSettings>(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<Pixels>, layout: &mut LayoutState, cx: &mut ViewContext<Editor>) {
+ // 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<Pixels>::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<Pixels>::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<Pixels>::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<Pixels>,
+ // visible_bounds: Bounds<Pixels>,
+ // layout: &mut LayoutState,
+ // editor: &mut Editor,
+ // cx: &mut ViewContext<Editor>,
+ // ) {
+ // 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::<FoldMarkers>(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<DisplayPoint>; 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<Pixels>::from_points(gpui::Point<Pixels>::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<Pixels>::from_points(gpui::Point<Pixels>::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<Pixels>::from_points(gpui::Point<Pixels>::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<Pixels>) -> f32 {
+ // bounds.max_x() - self.style.theme.scrollbar.width
+ // }
+
+ // fn paint_scrollbar(
+ // &mut self,
+ // bounds: Bounds<Pixels>,
+ // layout: &mut LayoutState,
+ // editor: &Editor,
+ // cx: &mut ViewContext<Editor>,
+ // ) {
+ // 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<Pixels>::from_points(vec2f(left, top), vec2f(right, bottom));
+ // let thumb_bounds = Bounds<Pixels>::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::<EditorSettings>(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<Pixels>::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::<crate::items::BufferSearchHighlights>(
+ // 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<Pixels>::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::<ScrollbarMouseHandlers>(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<DisplayPoint>,
+ // color: Color,
+ // corner_radius: f32,
+ // line_end_overshoot: f32,
+ // layout: &LayoutState,
+ // content_origin: gpui::Point<Pixels>,
+ // scroll_top: f32,
+ // scroll_left: f32,
+ // bounds: Bounds<Pixels>,
+ // cx: &mut ViewContext<Editor>,
+ // ) {
+ // 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<Pixels>,
+ // visible_bounds: Bounds<Pixels>,
+ // layout: &mut LayoutState,
+ // editor: &mut Editor,
+ // cx: &mut ViewContext<Editor>,
+ // ) {
+ // 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<Editor>) -> 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<Editor>) -> 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<u32>,
+ // snapshot: &EditorSnapshot,
+ // ) -> Vec<DisplayDiffHunk> {
+ // 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<u32>,
+ // relative_to: Option<u32>,
+ // ) -> HashMap<u32, u32> {
+ // let mut relative_rows: HashMap<u32, u32> = 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::<Vec<_>>();
+
+ // 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<u32>,
+ // active_rows: &BTreeMap<u32, bool>,
+ // newest_selection_head: DisplayPoint,
+ // is_singleton: bool,
+ // snapshot: &EditorSnapshot,
+ // cx: &ViewContext<Editor>,
+ // ) -> (
+ // Vec<Option<text_layout::Line>>,
+ // Vec<Option<(FoldStatus, BufferRow, bool)>>,
+ // ) {
+ // 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::<EditorSettings>(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<u32>,
+ // line_number_layouts: &[Option<Line>],
+ // snapshot: &EditorSnapshot,
+ // cx: &ViewContext<Editor>,
+ // ) -> Vec<LineWithInvisibles> {
+ // 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<u32>,
+ // 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<Editor>,
+ // ) -> (f32, Vec<BlockLayout>) {
+ // 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::<Vec<_>, _>(|(_, 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::<JumpIcon, _>((*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::<JumpIcon>(
+ // (*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<Pixels>::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<Invisible>,
+}
+
+// impl LineWithInvisibles {
+// fn from_chunks<'a>(
+// chunks: impl Iterator<Item = HighlightedChunk<'a>>,
+// text_style: &TextStyle,
+// text_layout_cache: &TextLayoutCache,
+// font_cache: &Arc<FontCache>,
+// max_line_len: usize,
+// max_line_count: usize,
+// line_number_layouts: &[Option<Line>],
+// editor_mode: EditorMode,
+// ) -> Vec<Self> {
+// 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<Pixels>,
+// scroll_left: f32,
+// visible_text_bounds: Bounds<Pixels>,
+// whitespace_setting: ShowWhitespaceSetting,
+// selection_ranges: &[Range<DisplayPoint>],
+// visible_bounds: Bounds<Pixels>,
+// cx: &mut ViewContext<Editor>,
+// ) {
+// 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<DisplayPoint>],
+// layout: &LayoutState,
+// content_origin: gpui::Point<Pixels>,
+// scroll_left: f32,
+// line_y: f32,
+// row: u32,
+// visible_bounds: Bounds<Pixels>,
+// line_height: f32,
+// whitespace_setting: ShowWhitespaceSetting,
+// cx: &mut ViewContext<Editor>,
+// ) {
+// 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<Editor> for EditorElement {
+ type ElementState = ();
+
+ fn id(&self) -> Option<gpui::ElementId> {
+ None
+ }
+
+ fn initialize(
+ &mut self,
+ view_state: &mut Editor,
+ element_state: Option<Self::ElementState>,
+ cx: &mut gpui::ViewContext<Editor>,
+ ) -> Self::ElementState {
+ ()
+ }
+
+ fn layout(
+ &mut self,
+ view_state: &mut Editor,
+ element_state: &mut Self::ElementState,
+ cx: &mut gpui::ViewContext<Editor>,
+ ) -> 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<gpui::Pixels>,
+ view_state: &mut Editor,
+ element_state: &mut Self::ElementState,
+ cx: &mut gpui::ViewContext<Editor>,
+ ) {
+ 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<Editor>,
+// ) -> (gpui::Point<Pixels>, 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<SelectionLayout>)> = 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<Selection<Point>> = 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::<EditorSettings>(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<DisplayPoint>, Color)> = fold_ranges
+// .into_iter()
+// .map(|(id, fold)| {
+// let color = self
+// .style
+// .folds
+// .ellipses
+// .background
+// .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
+// .color;
+
+// (id, fold, color)
+// })
+// .collect();
+
+// let head_for_relative = newest_selection_head.unwrap_or_else(|| {
+// let newest = editor.selections.newest::<Point>(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<Pixels>::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<Pixels>::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<Pixels>,
+// visible_bounds: Bounds<Pixels>,
+// layout: &mut Self::LayoutState,
+// editor: &mut Editor,
+// cx: &mut ViewContext<Editor>,
+// ) -> Self::PaintState {
+// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
+// cx.scene().push_layer(Some(visible_bounds));
+
+// let gutter_bounds = Bounds<Pixels>::new(bounds.origin(), layout.gutter_size);
+// let text_bounds = Bounds<Pixels>::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<usize>,
+// bounds: Bounds<Pixels>,
+// _: Bounds<Pixels>,
+// layout: &Self::LayoutState,
+// _: &Self::PaintState,
+// _: &Editor,
+// _: &ViewContext<Editor>,
+// ) -> Option<Bounds<Pixels>> {
+// let text_bounds = Bounds<Pixels>::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<Pixels>::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<Pixels>,
+// _: &Self::LayoutState,
+// _: &Self::PaintState,
+// _: &Editor,
+// _: &ViewContext<Editor>,
+// ) -> json::Value {
+// json!({
+// "type": "BufferElement",
+// "bounds": bounds.to_json()
+// })
+// }
+// }
+
+type BufferRow = u32;
+
+// pub struct LayoutState {
+// position_map: Arc<PositionMap>,
+// gutter_size: gpui::Point<Pixels>,
+// gutter_padding: f32,
+// gutter_margin: f32,
+// text_size: gpui::Point<Pixels>,
+// mode: EditorMode,
+// wrap_guides: SmallVec<[(f32, bool); 2]>,
+// visible_display_row_range: Range<u32>,
+// active_rows: BTreeMap<u32, bool>,
+// highlighted_rows: Option<Range<u32>>,
+// line_number_layouts: Vec<Option<text_layout::Line>>,
+// display_hunks: Vec<DisplayDiffHunk>,
+// blocks: Vec<BlockLayout>,
+// highlighted_ranges: Vec<(Range<DisplayPoint>, Color)>,
+// fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Color)>,
+// selections: Vec<(SelectionStyle, Vec<SelectionLayout>)>,
+// scrollbar_row_range: Range<f32>,
+// show_scrollbars: bool,
+// is_singleton: bool,
+// max_row: u32,
+// context_menu: Option<(DisplayPoint, AnyElement<Editor>)>,
+// code_actions_indicator: Option<(u32, AnyElement<Editor>)>,
+// hover_popovers: Option<(DisplayPoint, Vec<AnyElement<Editor>>)>,
+// fold_indicators: Vec<Option<AnyElement<Editor>>>,
+// tab_invisible: Line,
+// space_invisible: Line,
+// }
+
+struct PositionMap {
+ size: Size<Pixels>,
+ line_height: Pixels,
+ scroll_max: Size<Pixels>,
+ em_width: Pixels,
+ em_advance: Pixels,
+ line_layouts: Vec<LineWithInvisibles>,
+ 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<DisplayPoint> {
+ 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<Pixels>,
+ position: gpui::Point<Pixels>,
+ ) -> 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<Editor>,
+ style: BlockStyle,
+}
+
+fn layout_line(
+ row: u32,
+ snapshot: &EditorSnapshot,
+ style: &EditorStyle,
+ rem_size: Pixels,
+ text_system: &TextSystem,
+) -> Result<SmallVec<[Line; 1]>> {
+ 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<Pixels>,
+ block_width: Pixels,
+ line_height: Pixels,
+ color: Hsla,
+ shape: CursorShape,
+ block_text: Option<Line>,
+}
+
+impl Cursor {
+ // pub fn new(
+ // origin: gpui::Point<Pixels>,
+ // block_width: f32,
+ // line_height: f32,
+ // color: Color,
+ // shape: CursorShape,
+ // block_text: Option<Line>,
+ // ) -> Cursor {
+ // Cursor {
+ // origin,
+ // block_width,
+ // line_height,
+ // color,
+ // shape,
+ // block_text,
+ // }
+ // }
+
+ // pub fn bounding_rect(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
+ // Bounds<Pixels>::new(
+ // self.origin + origin,
+ // vec2f(self.block_width, self.line_height),
+ // )
+ // }
+
+ // pub fn paint(&self, origin: gpui::Point<Pixels>, cx: &mut WindowContext) {
+ // let bounds = match self.shape {
+ // CursorShape::Bar => Bounds<Pixels>::new(self.origin + origin, vec2f(2.0, self.line_height)),
+ // CursorShape::Block | CursorShape::Hollow => Bounds<Pixels>::new(
+ // self.origin + origin,
+ // vec2f(self.block_width, self.line_height),
+ // ),
+ // CursorShape::Underscore => Bounds<Pixels>::new(
+ // self.origin + origin + gpui::Point<Pixels>::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<HighlightedRangeLine>,
+ 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<Pixels>, 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<Pixels>,
+ // 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<DisplayPoint>,
+// content_origin: gpui::Point<Pixels>,
+// scroll_left: f32,
+// scroll_top: f32,
+// visible_row_range: &Range<u32>,
+// line_end_overshoot: f32,
+// position_map: &PositionMap,
+// ) -> impl Iterator<Item = Bounds<Pixels>> {
+// let mut bounds: SmallVec<[Bounds<Pixels>; 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<Pixels>::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<u32>>(),
+// 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::<Vec<_>>(),
+// &[false, false, false, true]
+// );
+
+// // Don't panic.
+// let bounds = Bounds<Pixels>::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::<Vec<_>>();
+// 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<Invisible> {
+// 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()
+// }
+// }
@@ -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<u32>,
+ 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<u32>, 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::<Vec<_>>(),
+// &expected,
+// );
+
+// assert_eq!(
+// snapshot
+// .git_diff_hunks_in_range_rev(0..12)
+// .map(|hunk| (hunk.status(), hunk.buffer_range))
+// .collect::<Vec<_>>(),
+// expected
+// .iter()
+// .rev()
+// .cloned()
+// .collect::<Vec<_>>()
+// .as_slice(),
+// );
+// }
+// }
@@ -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>) {
+ // editor.clear_background_highlights::<MatchingBracketHighlight>(cx);
+
+ let newest_selection = editor.selections.newest::<usize>(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::<MatchingBracketHighlight>(
+ 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::<MatchingBracketHighlight>(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::<MatchingBracketHighlight>(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::<MatchingBracketHighlight>(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::<MatchingBracketHighlight>(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::<MatchingBracketHighlight>(indoc! {r#"
+// pub fn test("Test argument") {
+// another_test(1, 2, 3);
+// }
+// "#});
+// }
+// }
@@ -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<Editor>) {
+// 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<DisplayPoint>, cx: &mut ViewContext<Editor>) {
+ 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<InlayHintLabelPart>,
+ hint_start: InlayOffset,
+ hovered_offset: InlayOffset,
+) -> Option<(InlayHintLabelPart, Range<InlayOffset>)> {
+ 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<Editor>) {
+ 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::<HoverState>(
+ // 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<Editor>) -> 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::<HoverState>(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<Editor>,
+) {
+ 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::<usize>(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::<HoverState>(
+ // vec![symbol_range],
+ // |theme| theme.editor.hover_popover.highlight,
+ // cx,
+ // );
+ // } else {
+ // this.clear_background_highlights::<HoverState>(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<LanguageRegistry>,
+ language: Option<Arc<Language>>,
+) -> 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<InfoPopover>,
+ pub diagnostic_popover: Option<DiagnosticPopover>,
+ pub triggered_from: Option<Anchor>,
+ pub info_task: Option<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<u32>,
+ workspace: Option<WeakView<Workspace>>,
+ cx: &mut ViewContext<Editor>,
+ ) -> Option<(DisplayPoint, Vec<AnyElement<Editor>>)> {
+ 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<Project>,
+ symbol_range: RangeInEditor,
+ pub blocks: Vec<HoverBlock>,
+ parsed_content: ParsedMarkdown,
+}
+
+// impl InfoPopover {
+// pub fn render(
+// &mut self,
+// style: &EditorStyle,
+// workspace: Option<WeakView<Workspace>>,
+// cx: &mut ViewContext<Editor>,
+// ) -> AnyElement<Editor> {
+// MouseEventHandler::new::<InfoPopover, _>(0, cx, |_, cx| {
+// Flex::column()
+// .scrollable::<HoverBlock>(0, None, cx)
+// .with_child(crate::render_parsed_markdown::<HoverBlock>(
+// &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<Anchor>,
+ primary_diagnostic: Option<DiagnosticEntry<Anchor>>,
+}
+
+impl DiagnosticPopover {
+ pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext<Editor>) -> AnyElement<Editor> {
+ 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::<DiagnosticPopover, _>(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::<PrimaryDiagnostic>(
+ // 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::<lsp::request::HoverRequest, _, _>(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::<lsp::request::HoverRequest, _, _>(|_, _| 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::<lsp::request::HoverRequest, _, _>(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::<lsp::request::HoverRequest, _, _>(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::<lsp::request::HoverRequest, _, _>(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::<lsp::request::HoverRequest, _, _>(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<HoverBlock>,
+// expected_marked_text: String,
+// expected_styles: Vec<HighlightStyle>,
+// }
+
+// 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 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
+// - 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::<Vec<_>>();
+// 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>(T);
+
+// fn main() {
+// let variableˇ = TestNewType(TestStruct);
+// }
+// "});
+
+// let hint_start_offset = cx.ranges(indoc! {"
+// struct TestStruct;
+
+// // ==================
+
+// struct TestNewType<T>(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>(T);
+
+// fn main() {
+// let variable = TestNewType(TestStruct);
+// }
+// "});
+// let struct_target_range = cx.lsp_range(indoc! {"
+// struct «TestStruct»;
+
+// // ==================
+
+// struct TestNewType<T>(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<TestStruct>";
+// let closure_uri = uri.clone();
+// cx.lsp
+// .handle_request::<lsp::request::InlayHintRequest, _, _>(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>(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::<lsp::request::InlayHintResolveRequest, _, _>(
+// 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<TestStruct>`
+// 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"
+// );
+// });
+// }
+// }
@@ -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<ExcerptId, Arc<RwLock<CachedExcerptHints>>>,
+ allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
+ version: usize,
+ pub(super) enabled: bool,
+ update_tasks: HashMap<ExcerptId, TasksForRanges>,
+ lsp_request_limiter: Arc<Semaphore>,
+}
+
+#[derive(Debug)]
+struct TasksForRanges {
+ tasks: Vec<Task<()>>,
+ sorted_ranges: Vec<Range<language::Anchor>>,
+}
+
+#[derive(Debug)]
+pub struct CachedExcerptHints {
+ version: usize,
+ buffer_version: Global,
+ buffer_id: u64,
+ ordered_hints: Vec<InlayId>,
+ hints_by_id: HashMap<InlayId, InlayHint>,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum InvalidationStrategy {
+ RefreshRequested,
+ BufferEdited,
+ None,
+}
+
+#[derive(Debug, Default)]
+pub struct InlaySplice {
+ pub to_remove: Vec<InlayId>,
+ pub to_insert: Vec<Inlay>,
+}
+
+#[derive(Debug)]
+struct ExcerptHintsUpdate {
+ excerpt_id: ExcerptId,
+ remove_from_visible: Vec<InlayId>,
+ remove_from_cache: HashSet<InlayId>,
+ add_to_cache: Vec<InlayHint>,
+}
+
+#[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<language::Anchor>,
+ ) -> Vec<Range<language::Anchor>> {
+ let mut ranges_to_query = Vec::new();
+ let mut latest_cached_range = None::<&mut Range<language::Anchor>>;
+ 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<language::Anchor>) {
+ 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<MultiBuffer>,
+ new_hint_settings: InlayHintSettings,
+ visible_hints: Vec<Inlay>,
+ cx: &mut ViewContext<Editor>,
+ ) -> ControlFlow<Option<InlaySplice>> {
+ 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<ExcerptId, (Model<Buffer>, Global, Range<usize>)>,
+ invalidate: InvalidationStrategy,
+ cx: &mut ViewContext<Editor>,
+ ) -> Option<InlaySplice> {
+ 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<MultiBuffer>,
+ visible_hints: &[Inlay],
+ new_kinds: &HashSet<Option<InlayHintKind>>,
+ cx: &mut ViewContext<Editor>,
+ ) -> Option<InlaySplice> {
+ 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::<ExcerptId, Vec<(Anchor, InlayId)>>::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<ExcerptId>) -> Option<InlaySplice> {
+ 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<InlayHint> {
+ self.hints
+ .get(&excerpt_id)?
+ .read()
+ .hints_by_id
+ .get(&hint_id)
+ .cloned()
+ }
+
+ pub fn hints(&self) -> Vec<InlayHint> {
+ 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<ExcerptId, (Model<Buffer>, Global, Range<usize>)>,
+ 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<Range<language::Anchor>>,
+ visible: Vec<Range<language::Anchor>>,
+ after_visible: Vec<Range<language::Anchor>>,
+}
+
+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<Buffer>,
+ excerpt_visible_range: Range<usize>,
+ cx: &mut ModelContext<'_, MultiBuffer>,
+) -> Option<QueryRanges> {
+ 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<Vec<Inlay>>,
+ cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
+ lsp_request_limiter: Arc<Semaphore>,
+ 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<language::Anchor>, 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<Editor>,
+// multi_buffer_snapshot: MultiBufferSnapshot,
+// buffer_snapshot: BufferSnapshot,
+// visible_hints: Arc<Vec<Inlay>>,
+// cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
+// query: ExcerptQuery,
+// invalidate: bool,
+// fetch_range: Range<language::Anchor>,
+// lsp_request_limiter: Arc<Semaphore>,
+// 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<language::Anchor>,
+ new_excerpt_hints: Vec<InlayHint>,
+ buffer_snapshot: &BufferSnapshot,
+ cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
+ visible_hints: &[Inlay],
+) -> Option<ExcerptHintsUpdate> {
+ let mut add_to_cache = Vec::<InlayHint>::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<language::Anchor>,
+ 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::<lsp::request::InlayHintRequest, _, _>(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::<lsp::request::InlayHintRefreshRequest>(())
+// .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::<lsp::request::InlayHintRequest, _, _>(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::request::WorkDoneProgressCreate>(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::notification::Progress>(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::notification::Progress>(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::<Editor>()
+// .unwrap();
+// let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
+// rs_fake_server
+// .handle_request::<lsp::request::InlayHintRequest, _, _>(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::<Editor>()
+// .unwrap();
+// let md_lsp_request_count = Arc::new(AtomicU32::new(0));
+// md_fake_server
+// .handle_request::<lsp::request::InlayHintRequest, _, _>(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::<lsp::request::InlayHintRequest, _, _>(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::<lsp::request::InlayHintRefreshRequest>(())
+// .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::<lsp::request::InlayHintRefreshRequest>(())
+// .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::<lsp::request::InlayHintRefreshRequest>(())
+// .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::<lsp::request::InlayHintRequest, _, _>(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::<Editor>()
+// .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::<lsp::request::InlayHintRequest, _, _>(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<Editor>,
+// cx: &mut gpui::TestAppContext,
+// ) -> Range<Point> {
+// 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::<Vec<_>>();
+// 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::<Vec<_>>();
+// 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::<Vec<_>>();
+// 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::<Vec<_>>();
+// 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<Deterministic>,
+// 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::<Vec<_>>().join("")),
+// "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().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::<lsp::request::InlayHintRequest, _, _>(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<Deterministic>,
+// 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::<Vec<_>>().join("")),
+// "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().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::<lsp::request::InlayHintRequest, _, _>(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::<Editor>()
+// .unwrap();
+// let lsp_request_count = Arc::new(AtomicU32::new(0));
+// let closure_lsp_request_count = Arc::clone(&lsp_request_count);
+// fake_server
+// .handle_request::<lsp::request::InlayHintRequest, _, _>(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::<lsp::request::InlayHintRequest, _, _>(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<Editor>, 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::<Editor>()
+// .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<String> {
+// 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<String> {
+// let mut hints = editor
+// .visible_inlay_hints(cx)
+// .into_iter()
+// .map(|hint| hint.text.to_string())
+// .collect::<Vec<_>>();
+// hints.sort();
+// hints
+// }
+// }
@@ -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<ViewId> {
+ self.remote_id
+ }
+
+ fn from_state_proto(
+ pane: View<workspace::Pane>,
+ workspace: View<Workspace>,
+ remote_id: ViewId,
+ state: &mut Option<proto::view::Variant>,
+ cx: &mut AppContext,
+ ) -> Option<Task<Result<View<Self>>>> {
+ 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::<HashSet<_>>();
+ // let buffers = project.update(cx, |project, cx| {
+ // buffer_ids
+ // .iter()
+ // .map(|id| project.open_buffer_by_id(*id, cx))
+ // .collect::<Vec<_>>()
+ // });
+
+ // 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::<Self>();
+ // 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<PeerId>, cx: &mut ViewContext<Self>) {
+ 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<proto::view::Variant> {
+ 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<proto::update_view::Variant>,
+ 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<Project>,
+ message: update_view::Variant,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Result<()>> {
+ 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<Editor>,
+ project: Model<Project>,
+ 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::<HashSet<_>>();
+// 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::<Vec<_>>()
+// })?;
+// 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::<Vec<_>>();
+// 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::<Vec<_>>();
+// 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<language::Anchor>,
+) -> Option<proto::Excerpt> {
+ 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<Anchor>) -> 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<ExcerptRange<language::Anchor>> {
+ 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<Selection<Anchor>> {
+ 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<Anchor> {
+ 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<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
+ todo!();
+ // if let Ok(data) = data.downcast::<NavigationData>() {
+ // let newest_selection = self.selections.newest::<Point>(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<SharedString> {
+ 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<SharedString> {
+ let path = path_for_buffer(&self.buffer, detail, true, cx)?;
+ Some(path.to_string_lossy().to_string().into())
+ }
+
+ fn tab_content<T: 'static>(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<T> {
+ 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<Self>,
+ ) -> Option<View<Editor>>
+ where
+ Self: Sized,
+ {
+ Some(cx.build_view(|cx| self.clone(cx)))
+ }
+
+ fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
+ self.nav_history = Some(history);
+ }
+
+ fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
+ let selection = self.selections.newest_anchor();
+ todo!()
+ // self.push_to_nav_history(selection.head(), None, cx);
+ }
+
+ fn workspace_deactivated(&mut self, cx: &mut ViewContext<Self>) {
+ 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<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
+ 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<Project>,
+ abs_path: PathBuf,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Result<()>> {
+ 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<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
+ 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<Self>) -> Option<Box<dyn SearchableItemHandle>> {
+ Some(Box::new(handle.clone()))
+ }
+
+ fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<gpui::Point<Pixels>> {
+ self.pixel_position_of_newest_cursor
+ }
+
+ fn breadcrumb_location(&self) -> ToolbarItemLocation {
+ ToolbarItemLocation::PrimaryLeft { flex: None }
+ }
+
+ fn breadcrumbs(&self, variant: &ThemeVariant, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
+ 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<Self>) {
+ 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<Buffer>,
+ 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<Project>,
+ _workspace: WeakView<Workspace>,
+ workspace_id: workspace::WorkspaceId,
+ item_id: ItemId,
+ cx: &mut ViewContext<Pane>,
+ ) -> Task<Result<View<Self>>> {
+ 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::<Buffer>()
+ .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<Project>,
+ buffer: Model<Buffer>,
+ cx: &mut ViewContext<Self>,
+ ) -> Self {
+ Self::for_buffer(buffer, Some(project), cx)
+ }
+}
+
+pub(crate) enum BufferSearchHighlights {}
+impl SearchableItem for Editor {
+ type Match = Range<Anchor>;
+
+ fn to_search_event(
+ &mut self,
+ event: &Self::Event,
+ _: &mut ViewContext<Self>,
+ ) -> Option<SearchEvent> {
+ 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<Self>) {
+ todo!()
+ // self.clear_background_highlights::<BufferSearchHighlights>(cx);
+ }
+
+ fn update_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) {
+ todo!()
+ // self.highlight_background::<BufferSearchHighlights>(
+ // matches,
+ // |theme| theme.search.match_background,
+ // cx,
+ // );
+ }
+
+ fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
+ let display_map = self.snapshot(cx).display_snapshot;
+ let selection = self.selections.newest::<usize>(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<Range<Anchor>>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ 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<Self::Match>, cx: &mut ViewContext<Self>) {
+ 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<Self>,
+ ) {
+ let text = self.buffer.read(cx);
+ let text = text.snapshot(cx);
+ let text = text.text_for_range(identifier.clone()).collect::<Vec<_>>();
+ 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<Range<Anchor>>,
+ current_index: usize,
+ direction: Direction,
+ count: usize,
+ cx: &mut ViewContext<Self>,
+ ) -> 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<project::search::SearchQuery>,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Vec<Range<Anchor>>> {
+ 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<Range<Anchor>>,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<usize> {
+ active_match_index(
+ &matches,
+ &self.selections.newest_anchor().head(),
+ &self.buffer().read(cx).snapshot(cx),
+ )
+ }
+}
+
+pub fn active_match_index(
+ ranges: &[Range<Anchor>],
+ cursor: &Anchor,
+ buffer: &MultiBufferSnapshot,
+) -> Option<usize> {
+ 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<Point>,
+ selected_count: usize,
+ _observe_active_editor: Option<Subscription>,
+}
+
+// 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<Editor>, cx: &mut ViewContext<Self>) {
+// let editor = editor.read(cx);
+// let buffer = editor.buffer().read(cx).snapshot(cx);
+
+// self.selected_count = 0;
+// let mut last_selection: Option<Selection<usize>> = None;
+// for selection in editor.selections.all::<usize>(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<Self>) -> AnyElement<Self> {
+// 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<Self>,
+// ) {
+// if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(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<MultiBuffer>,
+ height: usize,
+ include_filename: bool,
+ cx: &'a AppContext,
+) -> Option<Cow<'a, Path>> {
+ 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<Cow<'a, Path>> {
+ // 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<Path>` 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<Path>,
+ full_path: PathBuf,
+ }
+
+ impl language::File for TestFile {
+ fn path(&self) -> &Arc<Path> {
+ &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!()
+ }
+ }
+}
@@ -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<TriggerPoint>,
+ pub symbol_range: Option<RangeInEditor>,
+ pub kind: Option<LinkDefinitionKind>,
+ pub definitions: Vec<GoToDefinitionLink>,
+ pub task: Option<Task<Option<()>>>,
+}
+
+#[derive(Debug, Eq, PartialEq, Clone)]
+pub enum RangeInEditor {
+ Text(Range<Anchor>),
+ Inlay(InlayHighlight),
+}
+
+impl RangeInEditor {
+ pub fn as_text_range(&self) -> Option<Range<Anchor>> {
+ 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<usize>,
+}
+
+#[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<GoToDefinitionTrigger>,
+ cmd_held: bool,
+ shift_held: bool,
+ cx: &mut ViewContext<Editor>,
+) {
+ 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<Editor>,
+) {
+ 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::<LinkGoToDefinitionState>(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::<LinkGoToDefinitionState>(
+ // vec![text_range],
+ // style,
+ // cx,
+ // ),
+ // RangeInEditor::Inlay(highlight) => this
+ // .highlight_inlays::<LinkGoToDefinitionState>(
+ // 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<Editor>) {
+ 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::<LinkGoToDefinitionState>(cx);
+}
+
+pub fn go_to_fetched_definition(
+ editor: &mut Editor,
+ point: PointForPosition,
+ split: bool,
+ cx: &mut ViewContext<Editor>,
+) {
+ 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<Editor>,
+) {
+ 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<Editor>,
+) {
+ 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::<GotoTypeDefinition, _, _>(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::<LinkGoToDefinitionState>(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::<LinkGoToDefinitionState>(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::<GotoTypeDefinition, _, _>(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::<GotoDefinition, _, _>(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::<LinkGoToDefinitionState>(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::<LinkGoToDefinitionState>(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::<GotoDefinition, _, _>(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::<LinkGoToDefinitionState>(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::<GotoDefinition, _, _>(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::<LinkGoToDefinitionState>(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::<LinkGoToDefinitionState>(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::<GotoDefinition, _, _>(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::<LinkGoToDefinitionState>(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::<LinkGoToDefinitionState>(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::<LinkGoToDefinitionState>(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::<LinkGoToDefinitionState>(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::<GotoDefinition, _, _>(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::<LinkGoToDefinitionState>(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::<GotoDefinition, _, _>(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::<GotoDefinition, _, _>(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::<LinkGoToDefinitionState>(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::<lsp::request::InlayHintRequest, _, _>(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::<LinkGoToDefinitionState>()
+// .into_iter()
+// .flat_map(|highlights| highlights.values().map(|(_, highlight)| highlight))
+// .collect::<Vec<_>>();
+
+// 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::<LinkGoToDefinitionState>()
+// .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;
+// }
+// "});
+// }
+// }
@@ -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<Pixels>,
+ point: DisplayPoint,
+ cx: &mut ViewContext<Editor>,
+) {
+ 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()));
+// }
+// }
@@ -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<Item = (char, Range<usize>)> + '_ {
+ 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<Item = (char, Range<usize>)> + '_ {
+ 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<DisplayPoint> {
+ 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<DisplayPoint>,
+) -> Vec<Range<DisplayPoint>> {
+ 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);
+// }
+// }
@@ -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<WorkspaceDb> =
+ &[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<Option<PathBuf>> {
+ 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<Option<(u32, f32, f32)>> {
+ 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
+ }
+ }
+}
@@ -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<f32>,
+ 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<f32> {
+ 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<Axis>,
+}
+
+impl OngoingScroll {
+ fn new() -> Self {
+ Self {
+ last_event: Instant::now() - SCROLL_EVENT_SEPARATION,
+ axis: None,
+ }
+ }
+
+ pub fn filter(&self, delta: &mut gpui::Point<Pixels>) -> Option<Axis> {
+ 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, f32, AutoscrollStrategy)>,
+ show_scrollbars: bool,
+ hide_scrollbar_task: Option<Task<()>>,
+ visible_line_count: Option<f32>,
+}
+
+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<Axis>) {
+ self.ongoing.last_event = Instant::now();
+ self.ongoing.axis = axis;
+ }
+
+ pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<f32> {
+ self.anchor.scroll_position(snapshot)
+ }
+
+ fn set_scroll_position(
+ &mut self,
+ scroll_position: gpui::Point<f32>,
+ map: &DisplaySnapshot,
+ local: bool,
+ autoscroll: bool,
+ workspace_id: Option<i64>,
+ cx: &mut ViewContext<Editor>,
+ ) {
+ 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<i64>,
+ cx: &mut ViewContext<Editor>,
+ ) {
+ 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<Editor>) {
+ if !self.show_scrollbars {
+ self.show_scrollbars = true;
+ cx.notify();
+ }
+
+ if cx.default_global::<ScrollbarAutoHide>().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>) {
+ // self.scroll_manager.vertical_scroll_margin = margin_rows as f32;
+ // cx.notify();
+ // }
+
+ pub fn visible_line_count(&self) -> Option<f32> {
+ self.scroll_manager.visible_line_count
+ }
+
+ // pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext<Self>) {
+ // 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<f32>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.set_scroll_position_internal(scroll_position, true, false, cx);
+ }
+
+ pub(crate) fn set_scroll_position_internal(
+ &mut self,
+ scroll_position: gpui::Point<f32>,
+ local: bool,
+ autoscroll: bool,
+ cx: &mut ViewContext<Self>,
+ ) {
+ 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<Self>) -> gpui::Point<Pixels> {
+ // 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<Self>) {
+ 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<Self>,
+ ) {
+ 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<Self>) {
+ // 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<Editor>,
+ ) {
+ 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);
+ }
+ }
+}
@@ -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<Editor>) -> 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<Axis>,
+// cx: &mut ViewContext<Self>,
+// ) {
+// 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<Editor>) {
+// 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<Editor>,
+// ) {
+// 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<Editor>,
+// ) {
+// 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,
+// )
+// }
+// }
@@ -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<Editor>,
+ ) -> 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::<Point>(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<Self>,
+ ) -> bool {
+ let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+ let selections = self.selections.all::<Point>(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>) {
+ self.scroll_manager.autoscroll_request = Some((autoscroll, true));
+ cx.notify();
+ }
+
+ pub(crate) fn request_autoscroll_remotely(
+ &mut self,
+ autoscroll: Autoscroll,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.scroll_manager.autoscroll_request = Some((autoscroll, false));
+ cx.notify();
+ }
+}
@@ -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.),
+ // }
+ }
+}
@@ -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<Anchor>,
+ pub mode: SelectMode,
+}
+
+#[derive(Debug, Clone)]
+pub struct SelectionsCollection {
+ display_map: Model<DisplayMap>,
+ buffer: Model<MultiBuffer>,
+ pub next_selection_id: usize,
+ pub line_mode: bool,
+ disjoint: Arc<[Selection<Anchor>]>,
+ pending: Option<PendingSelection>,
+}
+
+impl SelectionsCollection {
+ pub fn new(display_map: Model<DisplayMap>, buffer: Model<MultiBuffer>) -> 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<Anchor>]> {
+ self.disjoint.clone()
+ }
+
+ pub fn pending_anchor(&self) -> Option<Selection<Anchor>> {
+ self.pending
+ .as_ref()
+ .map(|pending| pending.selection.clone())
+ }
+
+ pub fn pending<D: TextDimension + Ord + Sub<D, Output = D>>(
+ &self,
+ cx: &AppContext,
+ ) -> Option<Selection<D>> {
+ self.pending_anchor()
+ .as_ref()
+ .map(|pending| pending.map(|p| p.summary::<D>(&self.buffer(cx))))
+ }
+
+ pub fn pending_mode(&self) -> Option<SelectMode> {
+ self.pending.as_ref().map(|pending| pending.mode.clone())
+ }
+
+ pub fn all<'a, D>(&self, cx: &AppContext) -> Vec<Selection<D>>
+ where
+ D: 'a + TextDimension + Ord + Sub<D, Output = D> + std::fmt::Debug,
+ {
+ let disjoint_anchors = &self.disjoint;
+ let mut disjoint =
+ resolve_multiple::<D, _>(disjoint_anchors.iter(), &self.buffer(cx)).peekable();
+
+ let mut pending_opt = self.pending::<D>(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<Selection<Point>> {
+ let mut selections = self.all::<Point>(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<Selection<DisplayPoint>>) {
+ if self.line_mode {
+ let selections = self.all::<Point>(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<Anchor>,
+ cx: &AppContext,
+ ) -> Vec<Selection<D>>
+ where
+ D: 'a + TextDimension + Ord + Sub<D, Output = D> + 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<Selection<DisplayPoint>>) {
+ let display_map = self.display_map(cx);
+ let selections = self
+ .all::<Point>(cx)
+ .into_iter()
+ .map(|selection| selection.map(|point| point.to_display_point(&display_map)))
+ .collect();
+ (display_map, selections)
+ }
+
+ pub fn newest_anchor(&self) -> &Selection<Anchor> {
+ self.pending
+ .as_ref()
+ .map(|s| &s.selection)
+ .or_else(|| self.disjoint.iter().max_by_key(|s| s.id))
+ .unwrap()
+ }
+
+ pub fn newest<D: TextDimension + Ord + Sub<D, Output = D>>(
+ &self,
+ cx: &AppContext,
+ ) -> Selection<D> {
+ resolve(self.newest_anchor(), &self.buffer(cx))
+ }
+
+ pub fn newest_display(&self, cx: &mut AppContext) -> Selection<DisplayPoint> {
+ 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<Anchor> {
+ self.disjoint
+ .iter()
+ .min_by_key(|s| s.id)
+ .or_else(|| self.pending.as_ref().map(|p| &p.selection))
+ .unwrap()
+ }
+
+ pub fn oldest<D: TextDimension + Ord + Sub<D, Output = D>>(
+ &self,
+ cx: &AppContext,
+ ) -> Selection<D> {
+ resolve(self.oldest_anchor(), &self.buffer(cx))
+ }
+
+ pub fn first_anchor(&self) -> Selection<Anchor> {
+ self.disjoint[0].clone()
+ }
+
+ pub fn first<D: TextDimension + Ord + Sub<D, Output = D>>(
+ &self,
+ cx: &AppContext,
+ ) -> Selection<D> {
+ self.all(cx).first().unwrap().clone()
+ }
+
+ pub fn last<D: TextDimension + Ord + Sub<D, Output = D>>(
+ &self,
+ cx: &AppContext,
+ ) -> Selection<D> {
+ self.all(cx).last().unwrap().clone()
+ }
+
+ #[cfg(any(test, feature = "test-support"))]
+ pub fn ranges<D: TextDimension + Ord + Sub<D, Output = D> + std::fmt::Debug>(
+ &self,
+ cx: &AppContext,
+ ) -> Vec<Range<D>> {
+ self.all::<D>(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<Range<DisplayPoint>> {
+ 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<Pixels>,
+ // reversed: bool,
+ // text_layout_details: &TextLayoutDetails,
+ // ) -> Option<Selection<Point>> {
+ // 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<R>(
+ &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<MultiBufferSnapshot> {
+ 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<Anchor>, 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<DisplayPoint>, 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<Anchor>, 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<T>(&mut self, range: Range<T>)
+ where
+ T: 'a + ToOffset + ToPoint + TextDimension + Ord + Sub<T, Output = T> + 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<T>(&mut self, mut selections: Vec<Selection<T>>)
+ 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<Selection<Anchor>>) {
+ let buffer = self.buffer.read(self.cx).snapshot(self.cx);
+ let resolved_selections =
+ resolve_multiple::<usize, _>(&selections, &buffer).collect::<Vec<_>>();
+ self.select(resolved_selections);
+ }
+
+ pub fn select_ranges<I, T>(&mut self, ranges: I)
+ where
+ I: IntoIterator<Item = Range<T>>,
+ 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<I>(&mut self, ranges: I)
+ where
+ I: IntoIterator<Item = Range<usize>>,
+ {
+ 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::<Vec<_>>();
+
+ self.select(selections)
+ }
+
+ pub fn select_anchor_ranges<I: IntoIterator<Item = Range<Anchor>>>(&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::<Vec<_>>();
+
+ // self.select_anchors(selections)
+ }
+
+ pub fn new_selection_id(&mut self) -> usize {
+ post_inc(&mut self.next_selection_id)
+ }
+
+ pub fn select_display_ranges<T>(&mut self, ranges: T)
+ where
+ T: IntoIterator<Item = Range<DisplayPoint>>,
+ {
+ 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<DisplayPoint>),
+ ) {
+ let mut changed = false;
+ let display_map = self.display_map();
+ let selections = self
+ .all::<Point>(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<usize>),
+ ) {
+ let mut changed = false;
+ let snapshot = self.buffer().clone();
+ let selections = self
+ .all::<usize>(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<DisplayPoint>,
+ ) {
+ 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<usize, ExcerptId> {
+ 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::<usize>(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<Item = Selection<D>>
+where
+ D: TextDimension + Ord + Sub<D, Output = D> + std::fmt::Debug,
+ I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
+{
+ let (to_summarize, selections) = selections.into_iter().tee();
+ let mut summaries = snapshot
+ .summaries_for_anchors::<D, _>(
+ to_summarize
+ .flat_map(|s| [&s.start, &s.end])
+ .collect::<Vec<_>>(),
+ )
+ .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<D: TextDimension + Ord + Sub<D, Output = D>>(
+ selection: &Selection<Anchor>,
+ buffer: &MultiBufferSnapshot,
+) -> Selection<D> {
+ selection.map(|p| p.summary::<D>(buffer))
+}
@@ -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<DisplayPoint>) {
+// 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<Editor>) {
+// 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<Editor>,
+// ) {
+// 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<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {
+// Editor::new(EditorMode::Full, buffer, None, None, cx)
+// }
+
+// pub(crate) fn build_editor_with_project(
+// project: Model<Project>,
+// buffer: Model<MultiBuffer>,
+// cx: &mut ViewContext<Editor>,
+// ) -> Editor {
+// Editor::new(EditorMode::Full, buffer, Some(project), None, cx)
+// }
@@ -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<Workspace>,
+// 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::<Editor>(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<char> = 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<usize>) -> 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<F, T>(&mut self, update: F) -> T
+// where
+// F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
+// {
+// self.workspace.update(self.cx.cx, update)
+// }
+
+// pub fn handle_request<T, F, Fut>(
+// &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<Output = Result<T::Result>>,
+// {
+// let url = self.buffer_lsp_url.clone();
+// self.lsp.handle_request::<T, _, _>(move |params, cx| {
+// let url = url.clone();
+// handler(url, params, cx)
+// })
+// }
+
+// pub fn notify<T: notification::Notification>(&self, params: T::Params) {
+// self.lsp.notify::<T>(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
+// }
+// }
@@ -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<Editor>,
+// }
+
+// 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<Output = ()> {
+// self.editor.condition(self.cx, predicate)
+// }
+
+// pub fn editor<F, T>(&self, read: F) -> T
+// where
+// F: FnOnce(&Editor, &ViewContext<Editor>) -> T,
+// {
+// self.editor.update(self.cx, read)
+// }
+
+// pub fn update_editor<F, T>(&mut self, update: F) -> T
+// where
+// F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
+// {
+// self.editor.update(self.cx, update)
+// }
+
+// pub fn multibuffer<F, T>(&self, read: F) -> T
+// where
+// F: FnOnce(&MultiBuffer, &AppContext) -> T,
+// {
+// self.editor(|editor, cx| read(editor.buffer().read(cx), cx))
+// }
+
+// pub fn update_multibuffer<F, T>(&mut self, update: F) -> T
+// where
+// F: FnOnce(&mut MultiBuffer, &mut ModelContext<MultiBuffer>) -> 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<F, T>(&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<F, T>(&mut self, update: F) -> T
+// where
+// F: FnOnce(&mut Buffer, &mut ModelContext<Buffer>) -> 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<const COUNT: usize>(
+// &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<Range<usize>> {
+// 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<language::Anchor> {
+// 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<Tag: 'static>(&mut self, marked_text: &str) {
+// let expected_ranges = self.ranges(marked_text);
+// let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
+// let snapshot = editor.snapshot(cx);
+// editor
+// .background_highlights
+// .get(&TypeId::of::<Tag>())
+// .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<Tag: ?Sized + 'static>(&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<Range<usize>> = snapshot
+// .text_highlight_ranges::<Tag>()
+// .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<Range<usize>>) {
+// 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<Range<usize>> {
+// self.editor
+// .read_with(self.cx, |editor, cx| editor.selections.all::<usize>(cx))
+// .into_iter()
+// .map(|s| {
+// if s.reversed {
+// s.end..s.start
+// } else {
+// s.start..s.end
+// }
+// })
+// .collect::<Vec<_>>()
+// }
+
+// #[track_caller]
+// fn assert_selections(
+// &mut self,
+// expected_selections: Vec<Range<usize>>,
+// 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
+// }
+// }
@@ -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" }
@@ -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 } => {
@@ -29,6 +29,7 @@ pub struct ForegroundExecutor {
}
#[must_use]
+#[derive(Debug)]
pub enum Task<T> {
Ready(Option<T>),
Spawned(async_task::Task<T>),
@@ -49,11 +50,11 @@ impl<T> Task<T> {
impl<E, T> Task<Result<T, E>>
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();
}
}
@@ -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<Pixels> for Pixels {
@@ -815,6 +819,18 @@ impl From<Pixels> for f64 {
}
}
+impl From<Pixels> for u32 {
+ fn from(pixels: Pixels) -> Self {
+ pixels.0 as u32
+ }
+}
+
+impl From<Pixels> 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,
)]
@@ -411,6 +411,7 @@ impl MacTextSystemState {
descent: typographic_bounds.descent.into(),
runs,
font_size,
+ len: text.len(),
}
}
@@ -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,
@@ -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<Pixels>,
@@ -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<UnderlineStyle>,
}
-#[derive(Clone, Default, Debug)]
+#[derive(Clone, Default, Debug, Deref, DerefMut)]
pub struct Line {
+ #[deref]
+ #[deref_mut]
pub(crate) layout: Arc<WrappedLineLayout>,
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()
}
@@ -16,6 +16,7 @@ pub struct LineLayout {
pub ascent: Pixels,
pub descent: Pixels,
pub runs: Vec<ShapedRun>,
+ 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 {
@@ -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<V: Render> From<View<V>> for AnyView {
fn from(value: View<V>) -> Self {
AnyView {
model: value.model.into_any(),
- initialize: |view, cx| {
- cx.with_element_id(view.model.entity_id, |_, cx| {
- let view = view.clone().downcast::<V>().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::<V>().unwrap();
- let element = element.downcast_mut::<AnyElement<V>>().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::<V>().unwrap();
- let element = element.downcast_mut::<AnyElement<V>>().unwrap();
- view.update(cx, |view, cx| element.paint(view, cx))
- })
- },
+ initialize: any_view::initialize::<V>,
+ layout: any_view::layout::<V>,
+ paint: any_view::paint::<V>,
}
}
}
@@ -280,6 +258,17 @@ impl AnyWeakView {
}
}
+impl<V: Render> From<WeakView<V>> for AnyWeakView {
+ fn from(view: WeakView<V>) -> Self {
+ Self {
+ model: view.model.into(),
+ initialize: any_view::initialize::<V>,
+ layout: any_view::layout::<V>,
+ paint: any_view::paint::<V>,
+ }
+ }
+}
+
impl<T, E> 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<V: Render>(view: &AnyView, cx: &mut WindowContext) -> Box<dyn Any> {
+ cx.with_element_id(view.model.entity_id, |_, cx| {
+ let view = view.clone().downcast::<V>().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<V: Render>(
+ view: &AnyView,
+ element: &mut Box<dyn Any>,
+ cx: &mut WindowContext,
+ ) -> LayoutId {
+ cx.with_element_id(view.model.entity_id, |_, cx| {
+ let view = view.clone().downcast::<V>().unwrap();
+ let element = element.downcast_mut::<AnyElement<V>>().unwrap();
+ view.update(cx, |view, cx| element.layout(view, cx))
+ })
+ }
+
+ pub(crate) fn paint<V: Render>(
+ view: &AnyView,
+ element: &mut Box<dyn Any>,
+ cx: &mut WindowContext,
+ ) {
+ cx.with_element_id(view.model.entity_id, |_, cx| {
+ let view = view.clone().downcast::<V>().unwrap();
+ let element = element.downcast_mut::<AnyElement<V>>().unwrap();
+ view.update(cx, |view, cx| element.paint(view, cx))
+ })
+ }
+}
@@ -1655,6 +1655,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
}
}
+ // todo!("change this to return a reference");
pub fn view(&self) -> View<V> {
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<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
self.window.z_index_stack.push(order);
let result = f(self);
@@ -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"] }
@@ -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::{
@@ -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;
@@ -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<usize>, MarkdownHighlight)>,
+ pub region_ranges: Vec<Range<usize>>,
+ pub regions: Vec<ParsedRegion>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum MarkdownHighlight {
+ Style(MarkdownHighlightStyle),
+ Code(HighlightId),
+}
+
+impl MarkdownHighlight {
+ pub fn to_highlight_style(&self, theme: &theme::SyntaxTheme) -> Option<HighlightStyle> {
+ 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<Link>,
+}
+
+#[derive(Debug, Clone)]
+pub enum Link {
+ Web { url: String },
+ Path { path: PathBuf },
+}
+
+impl Link {
+ fn identify(text: String) -> Option<Link> {
+ 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<LanguageRegistry>,
+ language: Option<Arc<Language>>,
+) -> 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<LanguageRegistry>,
+ language: Option<Arc<Language>>,
+ text: &mut String,
+ highlights: &mut Vec<(Range<usize>, MarkdownHighlight)>,
+ region_ranges: &mut Vec<Range<usize>>,
+ regions: &mut Vec<ParsedRegion>,
+) {
+ 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<usize>, MarkdownHighlight)>,
+ content: &str,
+ language: &Arc<Language>,
+) {
+ 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<u64>, 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(" ");
+ }
+}
@@ -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)),
}
@@ -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<Vec<(Range<usize>, HighlightStyle)>>,
}
-pub trait Item: Render + EventEmitter + Send {
+pub trait Item: Render + EventEmitter {
+ fn focus_handle(&self) -> FocusHandle;
fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
@@ -104,7 +106,11 @@ pub trait Item: Render + EventEmitter + Send {
}
fn tab_content<V: 'static>(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<V>;
- 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<usize>, cx: &AppContext) -> AnyElement<Workspace>;
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
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<dyn ItemHandle>;
fn clone_on_split(
@@ -253,7 +264,7 @@ pub trait ItemHandle: 'static + Send {
fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyView>;
fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
fn on_release(
- &mut self,
+ &self,
cx: &mut AppContext,
callback: Box<dyn FnOnce(&mut AppContext) + Send>,
) -> gpui::Subscription;
@@ -282,6 +293,10 @@ impl dyn ItemHandle {
}
impl<T: Item> ItemHandle for View<T> {
+ 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<T: Item> ItemHandle for View<T> {
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<T: Item> ItemHandle for View<T> {
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<T: Item> ItemHandle for View<T> {
.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,
- // );
- // }
- // });
- }
- }
-
- 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;
+ 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);
}
- 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,
+ );
+ }
});
}
+ }
+
+ 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::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)
+ 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<T: Item> ItemHandle for View<T> {
// })
// .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<T: Item> ItemHandle for View<T> {
}
fn on_release(
- &mut self,
+ &self,
cx: &mut AppContext,
callback: Box<dyn FnOnce(&mut AppContext) + Send>,
) -> gpui::Subscription {
@@ -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<Box<dyn ItemHandle>>,
activation_history: Vec<EntityId>,
zoomed: bool,
@@ -183,7 +184,6 @@ pub struct Pane {
// tab_context_menu: ViewHandle<ContextMenu>,
workspace: WeakView<Workspace>,
project: Model<Project>,
- has_focus: bool,
// can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
// can_split: bool,
// render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>>,
@@ -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<Box<dyn ItemHandle>> {
- // 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<Box<dyn ItemHandle>> {
+ 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<usize> {
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<Pane>) {
+ cx.focus(&self.focus_handle);
+ }
+
pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
- 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<Self>) {
@@ -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<Result<Box<dyn ItemHandle>>>,
>;
pub fn register_deserializable_item<I: 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::<ItemDeserializers>();
+ 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<Self>,
+ focus_handle: FocusHandle,
// modal: Option<ActiveModal>,
zoomed: Option<AnyWeakView>,
zoomed_position: Option<DockPosition>,
@@ -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<DockPosition>,
- // cx: &mut ViewContext<Self>,
- // ) {
- // // 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<DockPosition>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ // 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<Self>) -> View<Pane> {
let pane = cx.build_view(|cx| {
@@ -1997,22 +1999,22 @@ impl Workspace {
// }
// }
- // pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
- // self.active_pane
- // .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
- // }
+ pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
+ 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<dyn ItemHandle>,
- // cx: &mut ViewContext<Self>,
- // ) {
- // 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<dyn ItemHandle>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ 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<T>(
- // &mut self,
- // project_item: ModelHandle<T::Item>,
- // cx: &mut ViewContext<Self>,
- // ) -> View<T>
- // where
- // T: ProjectItem,
- // {
- // use project::Item as _;
+ pub fn open_project_item<T>(
+ &mut self,
+ project_item: Model<T::Item>,
+ cx: &mut ViewContext<Self>,
+ ) -> View<T>
+ 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<T>(
- // &mut self,
- // project_item: ModelHandle<T::Item>,
- // cx: &mut ViewContext<Self>,
- // ) -> View<T>
- // where
- // T: ProjectItem,
- // {
- // use project::Item as _;
+ pub fn split_project_item<T>(
+ &mut self,
+ project_item: Model<T::Item>,
+ cx: &mut ViewContext<Self>,
+ ) -> View<T>
+ 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<Self>) {
// 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<Self>) -> 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<Self>) -> 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<Self>) {
// 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<Pane>, cx: &mut ViewContext<Self>) {
- // 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<Pane>, cx: &mut ViewContext<Self>) {
+ 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<Pane>,
- _event: &pane::Event,
- _cx: &mut ViewContext<Self>,
+ pane: View<Pane>,
+ event: &pane::Event,
+ cx: &mut ViewContext<Self>,
) {
- 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<Pane>,
- // split_direction: SplitDirection,
- // cx: &mut ViewContext<Self>,
- // ) -> View<Pane> {
- // 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<Pane>,
+ split_direction: SplitDirection,
+ cx: &mut ViewContext<Self>,
+ ) -> View<Pane> {
+ 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<Pane>,
- // direction: SplitDirection,
- // cx: &mut ViewContext<Self>,
- // ) -> Option<View<Pane>> {
- // 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<Pane>,
+ direction: SplitDirection,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<View<Pane>> {
+ 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<Pane>,
- // split_direction: SplitDirection,
- // from: WeakView<Pane>,
- // item_id_to_move: usize,
- // cx: &mut ViewContext<Self>,
- // ) {
- // 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<Pane>,
+ split_direction: SplitDirection,
+ from: WeakView<Pane>,
+ item_id_to_move: EntityId,
+ cx: &mut ViewContext<Self>,
+ ) {
+ 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<Pane>,
- // split_direction: SplitDirection,
- // project_entry: ProjectEntryId,
- // cx: &mut ViewContext<Self>,
- // ) -> Option<Task<Result<()>>> {
- // 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();
-
- // 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(())
- // }))
- // }
+ pub fn split_pane_with_project_entry(
+ &mut self,
+ pane_to_split: WeakView<Pane>,
+ split_direction: SplitDirection,
+ project_entry: ProjectEntryId,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<Task<Result<()>>> {
+ 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_executor().spawn(async move {
+ task.await?;
+ Ok(())
+ }))
+ }
- // pub fn move_item(
- // &mut self,
- // source: View<Pane>,
- // destination: View<Pane>,
- // item_id_to_move: usize,
- // destination_index: usize,
- // cx: &mut ViewContext<Self>,
- // ) {
- // 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();
+ pub fn move_item(
+ &mut self,
+ source: View<Pane>,
+ destination: View<Pane>,
+ item_id_to_move: EntityId,
+ destination_index: usize,
+ cx: &mut ViewContext<Self>,
+ ) {
+ 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 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<Pane>, cx: &mut ViewContext<Self>) {
- // 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<Pane>, cx: &mut ViewContext<Self>) {
+ 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<Pane>] {
&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<Self>) {
- // 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<Self>) {
+ 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<Self>) {
let project = self.project().read(cx);
@@ -3010,7 +3013,7 @@ impl Workspace {
fn update_active_view_for_followers(&mut self, cx: &mut ViewContext<Self>) {
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<Self>) {
- fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &AppContext) -> SerializedPane {
+ fn serialize_pane_handle(pane_handle: &View<Pane>, 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::<Vec<_>>(),
- 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 {
@@ -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
@@ -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<AppState>,
+ 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<String> {
async fn restore_or_create_workspace(app_state: &Arc<AppState>, 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<AppState>, 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<dyn Fs>, _cx: &mut AppContext) {}
-fn connect_to_cli(
- server_name: &str,
-) -> Result<(mpsc::Receiver<CliRequest>, IpcSender<CliResponse>)> {
- let handshake_tx = cli::ipc::IpcSender::<IpcHandshake>::connect(server_name.to_string())
- .context("error connecting to cli")?;
- let (request_tx, request_rx) = ipc::channel::<CliRequest>()?;
- let (response_tx, response_rx) = ipc::channel::<CliResponse>()?;
-
- 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::<CliRequest>(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<CliRequest>, IpcSender<CliResponse>),
- _app_state: Arc<AppState>,
- 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::<Editor>() {
- // 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),
@@ -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::<u64>() {
- 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<CliRequest>, IpcSender<CliResponse>)> {
+ let handshake_tx = cli::ipc::IpcSender::<IpcHandshake>::connect(server_name.to_string())
+ .context("error connecting to cli")?;
+ let (request_tx, request_rx) = ipc::channel::<CliRequest>()?;
+ let (response_tx, response_rx) = ipc::channel::<CliResponse>()?;
+
+ 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::<CliRequest>(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<CliRequest>, IpcSender<CliResponse>),
+ app_state: Arc<AppState>,
+ 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::<Editor>() {
+ 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();
+ }
+ }
+ }
+}
@@ -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<CliRequest>, IpcSender<CliResponse>)> {
- let handshake_tx = cli::ipc::IpcSender::<IpcHandshake>::connect(server_name.to_string())
- .context("error connecting to cli")?;
- let (request_tx, request_rx) = ipc::channel::<CliRequest>()?;
- let (response_tx, response_rx) = ipc::channel::<CliResponse>()?;
-
- 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::<CliRequest>(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<CliRequest>, IpcSender<CliResponse>),
- app_state: Arc<AppState>,
- 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::<Vec<_>>()
- };
-
- 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::<Editor>() {
- // 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<WindowBounds>,
@@ -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!()