Merge branch 'main' into import-theme

Marshall Bowers created

Change summary

Cargo.lock                                         |    95 
Cargo.toml                                         |     1 
crates/channel2/Cargo.toml                         |    54 
crates/channel2/src/channel2.rs                    |    23 
crates/channel2/src/channel_buffer.rs              |   259 
crates/channel2/src/channel_chat.rs                |   647 +
crates/channel2/src/channel_store.rs               |  1021 +
crates/channel2/src/channel_store/channel_index.rs |   184 
crates/channel2/src/channel_store_tests.rs         |   380 
crates/client2/src/user.rs                         |    33 
crates/copilot2/Cargo.toml                         |     2 
crates/copilot2/src/copilot2.rs                    |     5 
crates/editor2/Cargo.toml                          |    93 
crates/editor2/src/blink_manager.rs                |   102 
crates/editor2/src/display_map.rs                  |  1900 +++
crates/editor2/src/display_map/block_map.rs        |  1667 ++
crates/editor2/src/display_map/fold_map.rs         |  1707 ++
crates/editor2/src/display_map/inlay_map.rs        |  1896 ++
crates/editor2/src/display_map/tab_map.rs          |   765 +
crates/editor2/src/display_map/wrap_map.rs         |  1362 ++
crates/editor2/src/editor.rs                       | 10120 ++++++++++++++++
crates/editor2/src/editor_settings.rs              |    62 
crates/editor2/src/editor_tests.rs                 |  8191 ++++++++++++
crates/editor2/src/element.rs                      |  3488 +++++
crates/editor2/src/git.rs                          |   282 
crates/editor2/src/highlight_matching_bracket.rs   |   138 
crates/editor2/src/hover_popover.rs                |  1331 ++
crates/editor2/src/inlay_hint_cache.rs             |  3355 +++++
crates/editor2/src/items.rs                        |  1339 ++
crates/editor2/src/link_go_to_definition.rs        |  1275 ++
crates/editor2/src/mouse_context_menu.rs           |    94 
crates/editor2/src/movement.rs                     |   933 +
crates/editor2/src/persistence.rs                  |    83 
crates/editor2/src/scroll.rs                       |   448 
crates/editor2/src/scroll/actions.rs               |   148 
crates/editor2/src/scroll/autoscroll.rs            |   253 
crates/editor2/src/scroll/scroll_amount.rs         |    29 
crates/editor2/src/selections_collection.rs        |   887 +
crates/editor2/src/test.rs                         |    81 
crates/editor2/src/test/editor_lsp_test_context.rs |   297 
crates/editor2/src/test/editor_test_context.rs     |   331 
crates/gpui2/Cargo.toml                            |     1 
crates/gpui2/src/app.rs                            |    12 
crates/gpui2/src/app/test_context.rs               |    19 
crates/gpui2/src/executor.rs                       |     7 
crates/gpui2/src/geometry.rs                       |    16 
crates/gpui2/src/platform/mac/text_system.rs       |     1 
crates/gpui2/src/style.rs                          |     9 
crates/gpui2/src/text_system.rs                    |     4 
crates/gpui2/src/text_system/line.rs               |     9 
crates/gpui2/src/text_system/line_layout.rs        |    23 
crates/gpui2/src/view.rs                           |    84 
crates/gpui2/src/window.rs                         |     6 
crates/gpui2_macros/src/test.rs                    |     1 
crates/language2/Cargo.toml                        |     1 
crates/language2/src/buffer.rs                     |     1 
crates/language2/src/language2.rs                  |     1 
crates/language2/src/markdown.rs                   |   301 
crates/rpc2/proto/zed.proto                        |   275 
crates/rpc2/src/proto.rs                           |   186 
crates/text2/src/selection.rs                      |     2 
crates/theme2/src/colors.rs                        |     4 
crates/workspace2/src/item.rs                      |   196 
crates/workspace2/src/pane.rs                      |    52 
crates/workspace2/src/workspace2.rs                |   665 
crates/zed2/Cargo.toml                             |     4 
crates/zed2/src/main.rs                            |   284 
crates/zed2/src/open_listener.rs                   |   216 
crates/zed2/src/zed2.rs                            |   211 
69 files changed, 46,810 insertions(+), 1,142 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1347,6 +1347,43 @@ dependencies = [
  "uuid 1.4.1",
 ]
 
+[[package]]
+name = "channel2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "client2",
+ "clock",
+ "collections",
+ "db2",
+ "feature_flags2",
+ "futures 0.3.28",
+ "gpui2",
+ "image",
+ "language2",
+ "lazy_static",
+ "log",
+ "parking_lot 0.11.2",
+ "postage",
+ "rand 0.8.5",
+ "rpc2",
+ "schemars",
+ "serde",
+ "serde_derive",
+ "settings2",
+ "smallvec",
+ "smol",
+ "sum_tree",
+ "tempfile",
+ "text",
+ "thiserror",
+ "time",
+ "tiny_http",
+ "url",
+ "util",
+ "uuid 1.4.1",
+]
+
 [[package]]
 name = "chrono"
 version = "0.4.31"
@@ -1866,7 +1903,6 @@ dependencies = [
  "async-tar",
  "clock",
  "collections",
- "context_menu",
  "fs",
  "futures 0.3.28",
  "gpui2",
@@ -2622,6 +2658,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 +3614,6 @@ dependencies = [
  "foreign-types",
  "futures 0.3.28",
  "gpui2_macros",
- "gpui_macros",
  "image",
  "itertools 0.10.5",
  "lazy_static",
@@ -4374,6 +4463,7 @@ dependencies = [
  "lsp2",
  "parking_lot 0.11.2",
  "postage",
+ "pulldown-cmark",
  "rand 0.8.5",
  "regex",
  "rpc2",
@@ -11124,6 +11214,7 @@ dependencies = [
  "copilot2",
  "ctor",
  "db2",
+ "editor2",
  "env_logger 0.9.3",
  "feature_flags2",
  "fs2",

Cargo.toml 🔗

@@ -10,6 +10,7 @@ members = [
     "crates/call",
     "crates/call2",
     "crates/channel",
+    "crates/channel2",
     "crates/cli",
     "crates/client",
     "crates/client2",

crates/channel2/Cargo.toml 🔗

@@ -0,0 +1,54 @@
+[package]
+name = "channel2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/channel2.rs"
+doctest = false
+
+[features]
+test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
+
+[dependencies]
+client = { package = "client2", path = "../client2" }
+collections = { path = "../collections" }
+db = { package = "db2", path = "../db2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+util = { path = "../util" }
+rpc = { package = "rpc2", path = "../rpc2" }
+text = { path = "../text" }
+language = { package = "language2", path = "../language2" }
+settings = { package = "settings2", path = "../settings2" }
+feature_flags = { package = "feature_flags2", path = "../feature_flags2" }
+sum_tree = { path = "../sum_tree" }
+clock = { path = "../clock" }
+
+anyhow.workspace = true
+futures.workspace = true
+image = "0.23"
+lazy_static.workspace = true
+smallvec.workspace = true
+log.workspace = true
+parking_lot.workspace = true
+postage.workspace = true
+rand.workspace = true
+schemars.workspace = true
+smol.workspace = true
+thiserror.workspace = true
+time.workspace = true
+tiny_http = "0.8"
+uuid.workspace = true
+url = "2.2"
+serde.workspace = true
+serde_derive.workspace = true
+tempfile = "3"
+
+[dev-dependencies]
+collections = { path = "../collections", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
+client = { package = "client2", path = "../client2", features = ["test-support"] }
+settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
+util = { path = "../util", features = ["test-support"] }

crates/channel2/src/channel2.rs 🔗

@@ -0,0 +1,23 @@
+mod channel_buffer;
+mod channel_chat;
+mod channel_store;
+
+use client::{Client, UserStore};
+use gpui::{AppContext, Model};
+use std::sync::Arc;
+
+pub use channel_buffer::{ChannelBuffer, ChannelBufferEvent, ACKNOWLEDGE_DEBOUNCE_INTERVAL};
+pub use channel_chat::{
+    mentions_to_proto, ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId,
+    MessageParams,
+};
+pub use channel_store::{Channel, ChannelEvent, ChannelId, ChannelMembership, ChannelStore};
+
+#[cfg(test)]
+mod channel_store_tests;
+
+pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
+    channel_store::init(client, user_store, cx);
+    channel_buffer::init(client);
+    channel_chat::init(client);
+}

crates/channel2/src/channel_buffer.rs 🔗

@@ -0,0 +1,259 @@
+use crate::{Channel, ChannelId, ChannelStore};
+use anyhow::Result;
+use client::{Client, Collaborator, UserStore};
+use collections::HashMap;
+use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
+use language::proto::serialize_version;
+use rpc::{
+    proto::{self, PeerId},
+    TypedEnvelope,
+};
+use std::{sync::Arc, time::Duration};
+use util::ResultExt;
+
+pub const ACKNOWLEDGE_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(250);
+
+pub(crate) fn init(client: &Arc<Client>) {
+    client.add_model_message_handler(ChannelBuffer::handle_update_channel_buffer);
+    client.add_model_message_handler(ChannelBuffer::handle_update_channel_buffer_collaborators);
+}
+
+pub struct ChannelBuffer {
+    pub channel_id: ChannelId,
+    connected: bool,
+    collaborators: HashMap<PeerId, Collaborator>,
+    user_store: Model<UserStore>,
+    channel_store: Model<ChannelStore>,
+    buffer: Model<language::Buffer>,
+    buffer_epoch: u64,
+    client: Arc<Client>,
+    subscription: Option<client::Subscription>,
+    acknowledge_task: Option<Task<Result<()>>>,
+}
+
+pub enum ChannelBufferEvent {
+    CollaboratorsChanged,
+    Disconnected,
+    BufferEdited,
+    ChannelChanged,
+}
+
+impl EventEmitter for ChannelBuffer {
+    type Event = ChannelBufferEvent;
+}
+
+impl ChannelBuffer {
+    pub(crate) async fn new(
+        channel: Arc<Channel>,
+        client: Arc<Client>,
+        user_store: Model<UserStore>,
+        channel_store: Model<ChannelStore>,
+        mut cx: AsyncAppContext,
+    ) -> Result<Model<Self>> {
+        let response = client
+            .request(proto::JoinChannelBuffer {
+                channel_id: channel.id,
+            })
+            .await?;
+
+        let base_text = response.base_text;
+        let operations = response
+            .operations
+            .into_iter()
+            .map(language::proto::deserialize_operation)
+            .collect::<Result<Vec<_>, _>>()?;
+
+        let buffer = cx.build_model(|_| {
+            language::Buffer::remote(response.buffer_id, response.replica_id as u16, base_text)
+        })?;
+        buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??;
+
+        let subscription = client.subscribe_to_entity(channel.id)?;
+
+        anyhow::Ok(cx.build_model(|cx| {
+            cx.subscribe(&buffer, Self::on_buffer_update).detach();
+            cx.on_release(Self::release).detach();
+            let mut this = Self {
+                buffer,
+                buffer_epoch: response.epoch,
+                client,
+                connected: true,
+                collaborators: Default::default(),
+                acknowledge_task: None,
+                channel_id: channel.id,
+                subscription: Some(subscription.set_model(&cx.handle(), &mut cx.to_async())),
+                user_store,
+                channel_store,
+            };
+            this.replace_collaborators(response.collaborators, cx);
+            this
+        })?)
+    }
+
+    fn release(&mut self, _: &mut AppContext) {
+        if self.connected {
+            if let Some(task) = self.acknowledge_task.take() {
+                task.detach();
+            }
+            self.client
+                .send(proto::LeaveChannelBuffer {
+                    channel_id: self.channel_id,
+                })
+                .log_err();
+        }
+    }
+
+    pub fn remote_id(&self, cx: &AppContext) -> u64 {
+        self.buffer.read(cx).remote_id()
+    }
+
+    pub fn user_store(&self) -> &Model<UserStore> {
+        &self.user_store
+    }
+
+    pub(crate) fn replace_collaborators(
+        &mut self,
+        collaborators: Vec<proto::Collaborator>,
+        cx: &mut ModelContext<Self>,
+    ) {
+        let mut new_collaborators = HashMap::default();
+        for collaborator in collaborators {
+            if let Ok(collaborator) = Collaborator::from_proto(collaborator) {
+                new_collaborators.insert(collaborator.peer_id, collaborator);
+            }
+        }
+
+        for (_, old_collaborator) in &self.collaborators {
+            if !new_collaborators.contains_key(&old_collaborator.peer_id) {
+                self.buffer.update(cx, |buffer, cx| {
+                    buffer.remove_peer(old_collaborator.replica_id as u16, cx)
+                });
+            }
+        }
+        self.collaborators = new_collaborators;
+        cx.emit(ChannelBufferEvent::CollaboratorsChanged);
+        cx.notify();
+    }
+
+    async fn handle_update_channel_buffer(
+        this: Model<Self>,
+        update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>,
+        _: Arc<Client>,
+        mut cx: AsyncAppContext,
+    ) -> Result<()> {
+        let ops = update_channel_buffer
+            .payload
+            .operations
+            .into_iter()
+            .map(language::proto::deserialize_operation)
+            .collect::<Result<Vec<_>, _>>()?;
+
+        this.update(&mut cx, |this, cx| {
+            cx.notify();
+            this.buffer
+                .update(cx, |buffer, cx| buffer.apply_ops(ops, cx))
+        })??;
+
+        Ok(())
+    }
+
+    async fn handle_update_channel_buffer_collaborators(
+        this: Model<Self>,
+        message: TypedEnvelope<proto::UpdateChannelBufferCollaborators>,
+        _: Arc<Client>,
+        mut cx: AsyncAppContext,
+    ) -> Result<()> {
+        this.update(&mut cx, |this, cx| {
+            this.replace_collaborators(message.payload.collaborators, cx);
+            cx.emit(ChannelBufferEvent::CollaboratorsChanged);
+            cx.notify();
+        })
+    }
+
+    fn on_buffer_update(
+        &mut self,
+        _: Model<language::Buffer>,
+        event: &language::Event,
+        cx: &mut ModelContext<Self>,
+    ) {
+        match event {
+            language::Event::Operation(operation) => {
+                let operation = language::proto::serialize_operation(operation);
+                self.client
+                    .send(proto::UpdateChannelBuffer {
+                        channel_id: self.channel_id,
+                        operations: vec![operation],
+                    })
+                    .log_err();
+            }
+            language::Event::Edited => {
+                cx.emit(ChannelBufferEvent::BufferEdited);
+            }
+            _ => {}
+        }
+    }
+
+    pub fn acknowledge_buffer_version(&mut self, cx: &mut ModelContext<'_, ChannelBuffer>) {
+        let buffer = self.buffer.read(cx);
+        let version = buffer.version();
+        let buffer_id = buffer.remote_id();
+        let client = self.client.clone();
+        let epoch = self.epoch();
+
+        self.acknowledge_task = Some(cx.spawn(move |_, cx| async move {
+            cx.background_executor()
+                .timer(ACKNOWLEDGE_DEBOUNCE_INTERVAL)
+                .await;
+            client
+                .send(proto::AckBufferOperation {
+                    buffer_id,
+                    epoch,
+                    version: serialize_version(&version),
+                })
+                .ok();
+            Ok(())
+        }));
+    }
+
+    pub fn epoch(&self) -> u64 {
+        self.buffer_epoch
+    }
+
+    pub fn buffer(&self) -> Model<language::Buffer> {
+        self.buffer.clone()
+    }
+
+    pub fn collaborators(&self) -> &HashMap<PeerId, Collaborator> {
+        &self.collaborators
+    }
+
+    pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> {
+        self.channel_store
+            .read(cx)
+            .channel_for_id(self.channel_id)
+            .cloned()
+    }
+
+    pub(crate) fn disconnect(&mut self, cx: &mut ModelContext<Self>) {
+        log::info!("channel buffer {} disconnected", self.channel_id);
+        if self.connected {
+            self.connected = false;
+            self.subscription.take();
+            cx.emit(ChannelBufferEvent::Disconnected);
+            cx.notify()
+        }
+    }
+
+    pub(crate) fn channel_changed(&mut self, cx: &mut ModelContext<Self>) {
+        cx.emit(ChannelBufferEvent::ChannelChanged);
+        cx.notify()
+    }
+
+    pub fn is_connected(&self) -> bool {
+        self.connected
+    }
+
+    pub fn replica_id(&self, cx: &AppContext) -> u16 {
+        self.buffer.read(cx).replica_id()
+    }
+}

crates/channel2/src/channel_chat.rs 🔗

@@ -0,0 +1,647 @@
+use crate::{Channel, ChannelId, ChannelStore};
+use anyhow::{anyhow, Result};
+use client::{
+    proto,
+    user::{User, UserStore},
+    Client, Subscription, TypedEnvelope, UserId,
+};
+use futures::lock::Mutex;
+use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
+use rand::prelude::*;
+use std::{
+    collections::HashSet,
+    mem,
+    ops::{ControlFlow, Range},
+    sync::Arc,
+};
+use sum_tree::{Bias, SumTree};
+use time::OffsetDateTime;
+use util::{post_inc, ResultExt as _, TryFutureExt};
+
+pub struct ChannelChat {
+    pub channel_id: ChannelId,
+    messages: SumTree<ChannelMessage>,
+    acknowledged_message_ids: HashSet<u64>,
+    channel_store: Model<ChannelStore>,
+    loaded_all_messages: bool,
+    last_acknowledged_id: Option<u64>,
+    next_pending_message_id: usize,
+    user_store: Model<UserStore>,
+    rpc: Arc<Client>,
+    outgoing_messages_lock: Arc<Mutex<()>>,
+    rng: StdRng,
+    _subscription: Subscription,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct MessageParams {
+    pub text: String,
+    pub mentions: Vec<(Range<usize>, UserId)>,
+}
+
+#[derive(Clone, Debug)]
+pub struct ChannelMessage {
+    pub id: ChannelMessageId,
+    pub body: String,
+    pub timestamp: OffsetDateTime,
+    pub sender: Arc<User>,
+    pub nonce: u128,
+    pub mentions: Vec<(Range<usize>, UserId)>,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum ChannelMessageId {
+    Saved(u64),
+    Pending(usize),
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct ChannelMessageSummary {
+    max_id: ChannelMessageId,
+    count: usize,
+}
+
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+struct Count(usize);
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum ChannelChatEvent {
+    MessagesUpdated {
+        old_range: Range<usize>,
+        new_count: usize,
+    },
+    NewMessage {
+        channel_id: ChannelId,
+        message_id: u64,
+    },
+}
+
+impl EventEmitter for ChannelChat {
+    type Event = ChannelChatEvent;
+}
+pub fn init(client: &Arc<Client>) {
+    client.add_model_message_handler(ChannelChat::handle_message_sent);
+    client.add_model_message_handler(ChannelChat::handle_message_removed);
+}
+
+impl ChannelChat {
+    pub async fn new(
+        channel: Arc<Channel>,
+        channel_store: Model<ChannelStore>,
+        user_store: Model<UserStore>,
+        client: Arc<Client>,
+        mut cx: AsyncAppContext,
+    ) -> Result<Model<Self>> {
+        let channel_id = channel.id;
+        let subscription = client.subscribe_to_entity(channel_id).unwrap();
+
+        let response = client
+            .request(proto::JoinChannelChat { channel_id })
+            .await?;
+        let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
+        let loaded_all_messages = response.done;
+
+        Ok(cx.build_model(|cx| {
+            cx.on_release(Self::release).detach();
+            let mut this = Self {
+                channel_id: channel.id,
+                user_store,
+                channel_store,
+                rpc: client,
+                outgoing_messages_lock: Default::default(),
+                messages: Default::default(),
+                acknowledged_message_ids: Default::default(),
+                loaded_all_messages,
+                next_pending_message_id: 0,
+                last_acknowledged_id: None,
+                rng: StdRng::from_entropy(),
+                _subscription: subscription.set_model(&cx.handle(), &mut cx.to_async()),
+            };
+            this.insert_messages(messages, cx);
+            this
+        })?)
+    }
+
+    fn release(&mut self, _: &mut AppContext) {
+        self.rpc
+            .send(proto::LeaveChannelChat {
+                channel_id: self.channel_id,
+            })
+            .log_err();
+    }
+
+    pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> {
+        self.channel_store
+            .read(cx)
+            .channel_for_id(self.channel_id)
+            .cloned()
+    }
+
+    pub fn client(&self) -> &Arc<Client> {
+        &self.rpc
+    }
+
+    pub fn send_message(
+        &mut self,
+        message: MessageParams,
+        cx: &mut ModelContext<Self>,
+    ) -> Result<Task<Result<u64>>> {
+        if message.text.is_empty() {
+            Err(anyhow!("message body can't be empty"))?;
+        }
+
+        let current_user = self
+            .user_store
+            .read(cx)
+            .current_user()
+            .ok_or_else(|| anyhow!("current_user is not present"))?;
+
+        let channel_id = self.channel_id;
+        let pending_id = ChannelMessageId::Pending(post_inc(&mut self.next_pending_message_id));
+        let nonce = self.rng.gen();
+        self.insert_messages(
+            SumTree::from_item(
+                ChannelMessage {
+                    id: pending_id,
+                    body: message.text.clone(),
+                    sender: current_user,
+                    timestamp: OffsetDateTime::now_utc(),
+                    mentions: message.mentions.clone(),
+                    nonce,
+                },
+                &(),
+            ),
+            cx,
+        );
+        let user_store = self.user_store.clone();
+        let rpc = self.rpc.clone();
+        let outgoing_messages_lock = self.outgoing_messages_lock.clone();
+        Ok(cx.spawn(move |this, mut cx| async move {
+            let outgoing_message_guard = outgoing_messages_lock.lock().await;
+            let request = rpc.request(proto::SendChannelMessage {
+                channel_id,
+                body: message.text,
+                nonce: Some(nonce.into()),
+                mentions: mentions_to_proto(&message.mentions),
+            });
+            let response = request.await?;
+            drop(outgoing_message_guard);
+            let response = response.message.ok_or_else(|| anyhow!("invalid message"))?;
+            let id = response.id;
+            let message = ChannelMessage::from_proto(response, &user_store, &mut cx).await?;
+            this.update(&mut cx, |this, cx| {
+                this.insert_messages(SumTree::from_item(message, &()), cx);
+            })?;
+            Ok(id)
+        }))
+    }
+
+    pub fn remove_message(&mut self, id: u64, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
+        let response = self.rpc.request(proto::RemoveChannelMessage {
+            channel_id: self.channel_id,
+            message_id: id,
+        });
+        cx.spawn(move |this, mut cx| async move {
+            response.await?;
+            this.update(&mut cx, |this, cx| {
+                this.message_removed(id, cx);
+            })?;
+            Ok(())
+        })
+    }
+
+    pub fn load_more_messages(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<Option<()>>> {
+        if self.loaded_all_messages {
+            return None;
+        }
+
+        let rpc = self.rpc.clone();
+        let user_store = self.user_store.clone();
+        let channel_id = self.channel_id;
+        let before_message_id = self.first_loaded_message_id()?;
+        Some(cx.spawn(move |this, mut cx| {
+            async move {
+                let response = rpc
+                    .request(proto::GetChannelMessages {
+                        channel_id,
+                        before_message_id,
+                    })
+                    .await?;
+                let loaded_all_messages = response.done;
+                let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
+                this.update(&mut cx, |this, cx| {
+                    this.loaded_all_messages = loaded_all_messages;
+                    this.insert_messages(messages, cx);
+                })?;
+                anyhow::Ok(())
+            }
+            .log_err()
+        }))
+    }
+
+    pub fn first_loaded_message_id(&mut self) -> Option<u64> {
+        self.messages.first().and_then(|message| match message.id {
+            ChannelMessageId::Saved(id) => Some(id),
+            ChannelMessageId::Pending(_) => None,
+        })
+    }
+
+    /// Load all of the chat messages since a certain message id.
+    ///
+    /// For now, we always maintain a suffix of the channel's messages.
+    pub async fn load_history_since_message(
+        chat: Model<Self>,
+        message_id: u64,
+        mut cx: AsyncAppContext,
+    ) -> Option<usize> {
+        loop {
+            let step = chat
+                .update(&mut cx, |chat, cx| {
+                    if let Some(first_id) = chat.first_loaded_message_id() {
+                        if first_id <= message_id {
+                            let mut cursor = chat.messages.cursor::<(ChannelMessageId, Count)>();
+                            let message_id = ChannelMessageId::Saved(message_id);
+                            cursor.seek(&message_id, Bias::Left, &());
+                            return ControlFlow::Break(
+                                if cursor
+                                    .item()
+                                    .map_or(false, |message| message.id == message_id)
+                                {
+                                    Some(cursor.start().1 .0)
+                                } else {
+                                    None
+                                },
+                            );
+                        }
+                    }
+                    ControlFlow::Continue(chat.load_more_messages(cx))
+                })
+                .log_err()?;
+            match step {
+                ControlFlow::Break(ix) => return ix,
+                ControlFlow::Continue(task) => task?.await?,
+            }
+        }
+    }
+
+    pub fn acknowledge_last_message(&mut self, cx: &mut ModelContext<Self>) {
+        if let ChannelMessageId::Saved(latest_message_id) = self.messages.summary().max_id {
+            if self
+                .last_acknowledged_id
+                .map_or(true, |acknowledged_id| acknowledged_id < latest_message_id)
+            {
+                self.rpc
+                    .send(proto::AckChannelMessage {
+                        channel_id: self.channel_id,
+                        message_id: latest_message_id,
+                    })
+                    .ok();
+                self.last_acknowledged_id = Some(latest_message_id);
+                self.channel_store.update(cx, |store, cx| {
+                    store.acknowledge_message_id(self.channel_id, latest_message_id, cx);
+                });
+            }
+        }
+    }
+
+    pub fn rejoin(&mut self, cx: &mut ModelContext<Self>) {
+        let user_store = self.user_store.clone();
+        let rpc = self.rpc.clone();
+        let channel_id = self.channel_id;
+        cx.spawn(move |this, mut cx| {
+            async move {
+                let response = rpc.request(proto::JoinChannelChat { channel_id }).await?;
+                let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
+                let loaded_all_messages = response.done;
+
+                let pending_messages = this.update(&mut cx, |this, cx| {
+                    if let Some((first_new_message, last_old_message)) =
+                        messages.first().zip(this.messages.last())
+                    {
+                        if first_new_message.id > last_old_message.id {
+                            let old_messages = mem::take(&mut this.messages);
+                            cx.emit(ChannelChatEvent::MessagesUpdated {
+                                old_range: 0..old_messages.summary().count,
+                                new_count: 0,
+                            });
+                            this.loaded_all_messages = loaded_all_messages;
+                        }
+                    }
+
+                    this.insert_messages(messages, cx);
+                    if loaded_all_messages {
+                        this.loaded_all_messages = loaded_all_messages;
+                    }
+
+                    this.pending_messages().cloned().collect::<Vec<_>>()
+                })?;
+
+                for pending_message in pending_messages {
+                    let request = rpc.request(proto::SendChannelMessage {
+                        channel_id,
+                        body: pending_message.body,
+                        mentions: mentions_to_proto(&pending_message.mentions),
+                        nonce: Some(pending_message.nonce.into()),
+                    });
+                    let response = request.await?;
+                    let message = ChannelMessage::from_proto(
+                        response.message.ok_or_else(|| anyhow!("invalid message"))?,
+                        &user_store,
+                        &mut cx,
+                    )
+                    .await?;
+                    this.update(&mut cx, |this, cx| {
+                        this.insert_messages(SumTree::from_item(message, &()), cx);
+                    })?;
+                }
+
+                anyhow::Ok(())
+            }
+            .log_err()
+        })
+        .detach();
+    }
+
+    pub fn message_count(&self) -> usize {
+        self.messages.summary().count
+    }
+
+    pub fn messages(&self) -> &SumTree<ChannelMessage> {
+        &self.messages
+    }
+
+    pub fn message(&self, ix: usize) -> &ChannelMessage {
+        let mut cursor = self.messages.cursor::<Count>();
+        cursor.seek(&Count(ix), Bias::Right, &());
+        cursor.item().unwrap()
+    }
+
+    pub fn acknowledge_message(&mut self, id: u64) {
+        if self.acknowledged_message_ids.insert(id) {
+            self.rpc
+                .send(proto::AckChannelMessage {
+                    channel_id: self.channel_id,
+                    message_id: id,
+                })
+                .ok();
+        }
+    }
+
+    pub fn messages_in_range(&self, range: Range<usize>) -> impl Iterator<Item = &ChannelMessage> {
+        let mut cursor = self.messages.cursor::<Count>();
+        cursor.seek(&Count(range.start), Bias::Right, &());
+        cursor.take(range.len())
+    }
+
+    pub fn pending_messages(&self) -> impl Iterator<Item = &ChannelMessage> {
+        let mut cursor = self.messages.cursor::<ChannelMessageId>();
+        cursor.seek(&ChannelMessageId::Pending(0), Bias::Left, &());
+        cursor
+    }
+
+    async fn handle_message_sent(
+        this: Model<Self>,
+        message: TypedEnvelope<proto::ChannelMessageSent>,
+        _: Arc<Client>,
+        mut cx: AsyncAppContext,
+    ) -> Result<()> {
+        let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
+        let message = message
+            .payload
+            .message
+            .ok_or_else(|| anyhow!("empty message"))?;
+        let message_id = message.id;
+
+        let message = ChannelMessage::from_proto(message, &user_store, &mut cx).await?;
+        this.update(&mut cx, |this, cx| {
+            this.insert_messages(SumTree::from_item(message, &()), cx);
+            cx.emit(ChannelChatEvent::NewMessage {
+                channel_id: this.channel_id,
+                message_id,
+            })
+        })?;
+
+        Ok(())
+    }
+
+    async fn handle_message_removed(
+        this: Model<Self>,
+        message: TypedEnvelope<proto::RemoveChannelMessage>,
+        _: Arc<Client>,
+        mut cx: AsyncAppContext,
+    ) -> Result<()> {
+        this.update(&mut cx, |this, cx| {
+            this.message_removed(message.payload.message_id, cx)
+        })?;
+        Ok(())
+    }
+
+    fn insert_messages(&mut self, messages: SumTree<ChannelMessage>, cx: &mut ModelContext<Self>) {
+        if let Some((first_message, last_message)) = messages.first().zip(messages.last()) {
+            let nonces = messages
+                .cursor::<()>()
+                .map(|m| m.nonce)
+                .collect::<HashSet<_>>();
+
+            let mut old_cursor = self.messages.cursor::<(ChannelMessageId, Count)>();
+            let mut new_messages = old_cursor.slice(&first_message.id, Bias::Left, &());
+            let start_ix = old_cursor.start().1 .0;
+            let removed_messages = old_cursor.slice(&last_message.id, Bias::Right, &());
+            let removed_count = removed_messages.summary().count;
+            let new_count = messages.summary().count;
+            let end_ix = start_ix + removed_count;
+
+            new_messages.append(messages, &());
+
+            let mut ranges = Vec::<Range<usize>>::new();
+            if new_messages.last().unwrap().is_pending() {
+                new_messages.append(old_cursor.suffix(&()), &());
+            } else {
+                new_messages.append(
+                    old_cursor.slice(&ChannelMessageId::Pending(0), Bias::Left, &()),
+                    &(),
+                );
+
+                while let Some(message) = old_cursor.item() {
+                    let message_ix = old_cursor.start().1 .0;
+                    if nonces.contains(&message.nonce) {
+                        if ranges.last().map_or(false, |r| r.end == message_ix) {
+                            ranges.last_mut().unwrap().end += 1;
+                        } else {
+                            ranges.push(message_ix..message_ix + 1);
+                        }
+                    } else {
+                        new_messages.push(message.clone(), &());
+                    }
+                    old_cursor.next(&());
+                }
+            }
+
+            drop(old_cursor);
+            self.messages = new_messages;
+
+            for range in ranges.into_iter().rev() {
+                cx.emit(ChannelChatEvent::MessagesUpdated {
+                    old_range: range,
+                    new_count: 0,
+                });
+            }
+            cx.emit(ChannelChatEvent::MessagesUpdated {
+                old_range: start_ix..end_ix,
+                new_count,
+            });
+
+            cx.notify();
+        }
+    }
+
+    fn message_removed(&mut self, id: u64, cx: &mut ModelContext<Self>) {
+        let mut cursor = self.messages.cursor::<ChannelMessageId>();
+        let mut messages = cursor.slice(&ChannelMessageId::Saved(id), Bias::Left, &());
+        if let Some(item) = cursor.item() {
+            if item.id == ChannelMessageId::Saved(id) {
+                let ix = messages.summary().count;
+                cursor.next(&());
+                messages.append(cursor.suffix(&()), &());
+                drop(cursor);
+                self.messages = messages;
+                cx.emit(ChannelChatEvent::MessagesUpdated {
+                    old_range: ix..ix + 1,
+                    new_count: 0,
+                });
+            }
+        }
+    }
+}
+
+async fn messages_from_proto(
+    proto_messages: Vec<proto::ChannelMessage>,
+    user_store: &Model<UserStore>,
+    cx: &mut AsyncAppContext,
+) -> Result<SumTree<ChannelMessage>> {
+    let messages = ChannelMessage::from_proto_vec(proto_messages, user_store, cx).await?;
+    let mut result = SumTree::new();
+    result.extend(messages, &());
+    Ok(result)
+}
+
+impl ChannelMessage {
+    pub async fn from_proto(
+        message: proto::ChannelMessage,
+        user_store: &Model<UserStore>,
+        cx: &mut AsyncAppContext,
+    ) -> Result<Self> {
+        let sender = user_store
+            .update(cx, |user_store, cx| {
+                user_store.get_user(message.sender_id, cx)
+            })?
+            .await?;
+        Ok(ChannelMessage {
+            id: ChannelMessageId::Saved(message.id),
+            body: message.body,
+            mentions: message
+                .mentions
+                .into_iter()
+                .filter_map(|mention| {
+                    let range = mention.range?;
+                    Some((range.start as usize..range.end as usize, mention.user_id))
+                })
+                .collect(),
+            timestamp: OffsetDateTime::from_unix_timestamp(message.timestamp as i64)?,
+            sender,
+            nonce: message
+                .nonce
+                .ok_or_else(|| anyhow!("nonce is required"))?
+                .into(),
+        })
+    }
+
+    pub fn is_pending(&self) -> bool {
+        matches!(self.id, ChannelMessageId::Pending(_))
+    }
+
+    pub async fn from_proto_vec(
+        proto_messages: Vec<proto::ChannelMessage>,
+        user_store: &Model<UserStore>,
+        cx: &mut AsyncAppContext,
+    ) -> Result<Vec<Self>> {
+        let unique_user_ids = proto_messages
+            .iter()
+            .map(|m| m.sender_id)
+            .collect::<HashSet<_>>()
+            .into_iter()
+            .collect();
+        user_store
+            .update(cx, |user_store, cx| {
+                user_store.get_users(unique_user_ids, cx)
+            })?
+            .await?;
+
+        let mut messages = Vec::with_capacity(proto_messages.len());
+        for message in proto_messages {
+            messages.push(ChannelMessage::from_proto(message, user_store, cx).await?);
+        }
+        Ok(messages)
+    }
+}
+
+pub fn mentions_to_proto(mentions: &[(Range<usize>, UserId)]) -> Vec<proto::ChatMention> {
+    mentions
+        .iter()
+        .map(|(range, user_id)| proto::ChatMention {
+            range: Some(proto::Range {
+                start: range.start as u64,
+                end: range.end as u64,
+            }),
+            user_id: *user_id as u64,
+        })
+        .collect()
+}
+
+impl sum_tree::Item for ChannelMessage {
+    type Summary = ChannelMessageSummary;
+
+    fn summary(&self) -> Self::Summary {
+        ChannelMessageSummary {
+            max_id: self.id,
+            count: 1,
+        }
+    }
+}
+
+impl Default for ChannelMessageId {
+    fn default() -> Self {
+        Self::Saved(0)
+    }
+}
+
+impl sum_tree::Summary for ChannelMessageSummary {
+    type Context = ();
+
+    fn add_summary(&mut self, summary: &Self, _: &()) {
+        self.max_id = summary.max_id;
+        self.count += summary.count;
+    }
+}
+
+impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for ChannelMessageId {
+    fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
+        debug_assert!(summary.max_id > *self);
+        *self = summary.max_id;
+    }
+}
+
+impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for Count {
+    fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
+        self.0 += summary.count;
+    }
+}
+
+impl<'a> From<&'a str> for MessageParams {
+    fn from(value: &'a str) -> Self {
+        Self {
+            text: value.into(),
+            mentions: Vec::new(),
+        }
+    }
+}

crates/channel2/src/channel_store.rs 🔗

@@ -0,0 +1,1021 @@
+mod channel_index;
+
+use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat, ChannelMessage};
+use anyhow::{anyhow, Result};
+use channel_index::ChannelIndex;
+use client::{Client, Subscription, User, UserId, UserStore};
+use collections::{hash_map, HashMap, HashSet};
+use db::RELEASE_CHANNEL;
+use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
+use gpui::{
+    AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
+};
+use rpc::{
+    proto::{self, ChannelVisibility},
+    TypedEnvelope,
+};
+use std::{mem, sync::Arc, time::Duration};
+use util::{async_maybe, ResultExt};
+
+pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
+    let channel_store =
+        cx.build_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
+    cx.set_global(channel_store);
+}
+
+pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
+
+pub type ChannelId = u64;
+
+pub struct ChannelStore {
+    pub channel_index: ChannelIndex,
+    channel_invitations: Vec<Arc<Channel>>,
+    channel_participants: HashMap<ChannelId, Vec<Arc<User>>>,
+    outgoing_invites: HashSet<(ChannelId, UserId)>,
+    update_channels_tx: mpsc::UnboundedSender<proto::UpdateChannels>,
+    opened_buffers: HashMap<ChannelId, OpenedModelHandle<ChannelBuffer>>,
+    opened_chats: HashMap<ChannelId, OpenedModelHandle<ChannelChat>>,
+    client: Arc<Client>,
+    user_store: Model<UserStore>,
+    _rpc_subscription: Subscription,
+    _watch_connection_status: Task<Option<()>>,
+    disconnect_channel_buffers_task: Option<Task<()>>,
+    _update_channels: Task<()>,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct Channel {
+    pub id: ChannelId,
+    pub name: String,
+    pub visibility: proto::ChannelVisibility,
+    pub role: proto::ChannelRole,
+    pub unseen_note_version: Option<(u64, clock::Global)>,
+    pub unseen_message_id: Option<u64>,
+    pub parent_path: Vec<u64>,
+}
+
+impl Channel {
+    pub fn link(&self) -> String {
+        RELEASE_CHANNEL.link_prefix().to_owned()
+            + "channel/"
+            + &self.slug()
+            + "-"
+            + &self.id.to_string()
+    }
+
+    pub fn slug(&self) -> String {
+        let slug: String = self
+            .name
+            .chars()
+            .map(|c| if c.is_alphanumeric() { c } else { '-' })
+            .collect();
+
+        slug.trim_matches(|c| c == '-').to_string()
+    }
+
+    pub fn can_edit_notes(&self) -> bool {
+        self.role == proto::ChannelRole::Member || self.role == proto::ChannelRole::Admin
+    }
+}
+
+pub struct ChannelMembership {
+    pub user: Arc<User>,
+    pub kind: proto::channel_member::Kind,
+    pub role: proto::ChannelRole,
+}
+impl ChannelMembership {
+    pub fn sort_key(&self) -> MembershipSortKey {
+        MembershipSortKey {
+            role_order: match self.role {
+                proto::ChannelRole::Admin => 0,
+                proto::ChannelRole::Member => 1,
+                proto::ChannelRole::Banned => 2,
+                proto::ChannelRole::Guest => 3,
+            },
+            kind_order: match self.kind {
+                proto::channel_member::Kind::Member => 0,
+                proto::channel_member::Kind::AncestorMember => 1,
+                proto::channel_member::Kind::Invitee => 2,
+            },
+            username_order: self.user.github_login.as_str(),
+        }
+    }
+}
+
+#[derive(PartialOrd, Ord, PartialEq, Eq)]
+pub struct MembershipSortKey<'a> {
+    role_order: u8,
+    kind_order: u8,
+    username_order: &'a str,
+}
+
+pub enum ChannelEvent {
+    ChannelCreated(ChannelId),
+    ChannelRenamed(ChannelId),
+}
+
+impl EventEmitter for ChannelStore {
+    type Event = ChannelEvent;
+}
+
+enum OpenedModelHandle<E> {
+    Open(WeakModel<E>),
+    Loading(Shared<Task<Result<Model<E>, Arc<anyhow::Error>>>>),
+}
+
+impl ChannelStore {
+    pub fn global(cx: &AppContext) -> Model<Self> {
+        cx.global::<Model<Self>>().clone()
+    }
+
+    pub fn new(
+        client: Arc<Client>,
+        user_store: Model<UserStore>,
+        cx: &mut ModelContext<Self>,
+    ) -> Self {
+        let rpc_subscription =
+            client.add_message_handler(cx.weak_model(), Self::handle_update_channels);
+
+        let mut connection_status = client.status();
+        let (update_channels_tx, mut update_channels_rx) = mpsc::unbounded();
+        let watch_connection_status = cx.spawn(|this, mut cx| async move {
+            while let Some(status) = connection_status.next().await {
+                let this = this.upgrade()?;
+                match status {
+                    client::Status::Connected { .. } => {
+                        this.update(&mut cx, |this, cx| this.handle_connect(cx))
+                            .ok()?
+                            .await
+                            .log_err()?;
+                    }
+                    client::Status::SignedOut | client::Status::UpgradeRequired => {
+                        this.update(&mut cx, |this, cx| this.handle_disconnect(false, cx))
+                            .ok();
+                    }
+                    _ => {
+                        this.update(&mut cx, |this, cx| this.handle_disconnect(true, cx))
+                            .ok();
+                    }
+                }
+            }
+            Some(())
+        });
+
+        Self {
+            channel_invitations: Vec::default(),
+            channel_index: ChannelIndex::default(),
+            channel_participants: Default::default(),
+            outgoing_invites: Default::default(),
+            opened_buffers: Default::default(),
+            opened_chats: Default::default(),
+            update_channels_tx,
+            client,
+            user_store,
+            _rpc_subscription: rpc_subscription,
+            _watch_connection_status: watch_connection_status,
+            disconnect_channel_buffers_task: None,
+            _update_channels: cx.spawn(|this, mut cx| async move {
+                async_maybe!({
+                    while let Some(update_channels) = update_channels_rx.next().await {
+                        if let Some(this) = this.upgrade() {
+                            let update_task = this.update(&mut cx, |this, cx| {
+                                this.update_channels(update_channels, cx)
+                            })?;
+                            if let Some(update_task) = update_task {
+                                update_task.await.log_err();
+                            }
+                        }
+                    }
+                    anyhow::Ok(())
+                })
+                .await
+                .log_err();
+            }),
+        }
+    }
+
+    pub fn client(&self) -> Arc<Client> {
+        self.client.clone()
+    }
+
+    /// Returns the number of unique channels in the store
+    pub fn channel_count(&self) -> usize {
+        self.channel_index.by_id().len()
+    }
+
+    /// Returns the index of a channel ID in the list of unique channels
+    pub fn index_of_channel(&self, channel_id: ChannelId) -> Option<usize> {
+        self.channel_index
+            .by_id()
+            .keys()
+            .position(|id| *id == channel_id)
+    }
+
+    /// Returns an iterator over all unique channels
+    pub fn channels(&self) -> impl '_ + Iterator<Item = &Arc<Channel>> {
+        self.channel_index.by_id().values()
+    }
+
+    /// Iterate over all entries in the channel DAG
+    pub fn ordered_channels(&self) -> impl '_ + Iterator<Item = (usize, &Arc<Channel>)> {
+        self.channel_index
+            .ordered_channels()
+            .iter()
+            .filter_map(move |id| {
+                let channel = self.channel_index.by_id().get(id)?;
+                Some((channel.parent_path.len(), channel))
+            })
+    }
+
+    pub fn channel_at_index(&self, ix: usize) -> Option<&Arc<Channel>> {
+        let channel_id = self.channel_index.ordered_channels().get(ix)?;
+        self.channel_index.by_id().get(channel_id)
+    }
+
+    pub fn channel_at(&self, ix: usize) -> Option<&Arc<Channel>> {
+        self.channel_index.by_id().values().nth(ix)
+    }
+
+    pub fn has_channel_invitation(&self, channel_id: ChannelId) -> bool {
+        self.channel_invitations
+            .iter()
+            .any(|channel| channel.id == channel_id)
+    }
+
+    pub fn channel_invitations(&self) -> &[Arc<Channel>] {
+        &self.channel_invitations
+    }
+
+    pub fn channel_for_id(&self, channel_id: ChannelId) -> Option<&Arc<Channel>> {
+        self.channel_index.by_id().get(&channel_id)
+    }
+
+    pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &AppContext) -> bool {
+        if let Some(buffer) = self.opened_buffers.get(&channel_id) {
+            if let OpenedModelHandle::Open(buffer) = buffer {
+                return buffer.upgrade().is_some();
+            }
+        }
+        false
+    }
+
+    pub fn open_channel_buffer(
+        &mut self,
+        channel_id: ChannelId,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<Model<ChannelBuffer>>> {
+        let client = self.client.clone();
+        let user_store = self.user_store.clone();
+        let channel_store = cx.handle();
+        self.open_channel_resource(
+            channel_id,
+            |this| &mut this.opened_buffers,
+            |channel, cx| ChannelBuffer::new(channel, client, user_store, channel_store, cx),
+            cx,
+        )
+    }
+
+    pub fn fetch_channel_messages(
+        &self,
+        message_ids: Vec<u64>,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<Vec<ChannelMessage>>> {
+        let request = if message_ids.is_empty() {
+            None
+        } else {
+            Some(
+                self.client
+                    .request(proto::GetChannelMessagesById { message_ids }),
+            )
+        };
+        cx.spawn(|this, mut cx| async move {
+            if let Some(request) = request {
+                let response = request.await?;
+                let this = this
+                    .upgrade()
+                    .ok_or_else(|| anyhow!("channel store dropped"))?;
+                let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
+                ChannelMessage::from_proto_vec(response.messages, &user_store, &mut cx).await
+            } else {
+                Ok(Vec::new())
+            }
+        })
+    }
+
+    pub fn has_channel_buffer_changed(&self, channel_id: ChannelId) -> Option<bool> {
+        self.channel_index
+            .by_id()
+            .get(&channel_id)
+            .map(|channel| channel.unseen_note_version.is_some())
+    }
+
+    pub fn has_new_messages(&self, channel_id: ChannelId) -> Option<bool> {
+        self.channel_index
+            .by_id()
+            .get(&channel_id)
+            .map(|channel| channel.unseen_message_id.is_some())
+    }
+
+    pub fn notes_changed(
+        &mut self,
+        channel_id: ChannelId,
+        epoch: u64,
+        version: &clock::Global,
+        cx: &mut ModelContext<Self>,
+    ) {
+        self.channel_index.note_changed(channel_id, epoch, version);
+        cx.notify();
+    }
+
+    pub fn new_message(
+        &mut self,
+        channel_id: ChannelId,
+        message_id: u64,
+        cx: &mut ModelContext<Self>,
+    ) {
+        self.channel_index.new_message(channel_id, message_id);
+        cx.notify();
+    }
+
+    pub fn acknowledge_message_id(
+        &mut self,
+        channel_id: ChannelId,
+        message_id: u64,
+        cx: &mut ModelContext<Self>,
+    ) {
+        self.channel_index
+            .acknowledge_message_id(channel_id, message_id);
+        cx.notify();
+    }
+
+    pub fn acknowledge_notes_version(
+        &mut self,
+        channel_id: ChannelId,
+        epoch: u64,
+        version: &clock::Global,
+        cx: &mut ModelContext<Self>,
+    ) {
+        self.channel_index
+            .acknowledge_note_version(channel_id, epoch, version);
+        cx.notify();
+    }
+
+    pub fn open_channel_chat(
+        &mut self,
+        channel_id: ChannelId,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<Model<ChannelChat>>> {
+        let client = self.client.clone();
+        let user_store = self.user_store.clone();
+        let this = cx.handle();
+        self.open_channel_resource(
+            channel_id,
+            |this| &mut this.opened_chats,
+            |channel, cx| ChannelChat::new(channel, this, user_store, client, cx),
+            cx,
+        )
+    }
+
+    /// Asynchronously open a given resource associated with a channel.
+    ///
+    /// Make sure that the resource is only opened once, even if this method
+    /// is called multiple times with the same channel id while the first task
+    /// is still running.
+    fn open_channel_resource<T, F, Fut>(
+        &mut self,
+        channel_id: ChannelId,
+        get_map: fn(&mut Self) -> &mut HashMap<ChannelId, OpenedModelHandle<T>>,
+        load: F,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<Model<T>>>
+    where
+        F: 'static + FnOnce(Arc<Channel>, AsyncAppContext) -> Fut,
+        Fut: Future<Output = Result<Model<T>>>,
+        T: 'static,
+    {
+        let task = loop {
+            match get_map(self).entry(channel_id) {
+                hash_map::Entry::Occupied(e) => match e.get() {
+                    OpenedModelHandle::Open(model) => {
+                        if let Some(model) = model.upgrade() {
+                            break Task::ready(Ok(model)).shared();
+                        } else {
+                            get_map(self).remove(&channel_id);
+                            continue;
+                        }
+                    }
+                    OpenedModelHandle::Loading(task) => {
+                        break task.clone();
+                    }
+                },
+                hash_map::Entry::Vacant(e) => {
+                    let task = cx
+                        .spawn(move |this, mut cx| async move {
+                            let channel = this.update(&mut cx, |this, _| {
+                                this.channel_for_id(channel_id).cloned().ok_or_else(|| {
+                                    Arc::new(anyhow!("no channel for id: {}", channel_id))
+                                })
+                            })??;
+
+                            load(channel, cx).await.map_err(Arc::new)
+                        })
+                        .shared();
+
+                    e.insert(OpenedModelHandle::Loading(task.clone()));
+                    cx.spawn({
+                        let task = task.clone();
+                        move |this, mut cx| async move {
+                            let result = task.await;
+                            this.update(&mut cx, |this, _| match result {
+                                Ok(model) => {
+                                    get_map(this).insert(
+                                        channel_id,
+                                        OpenedModelHandle::Open(model.downgrade()),
+                                    );
+                                }
+                                Err(_) => {
+                                    get_map(this).remove(&channel_id);
+                                }
+                            })
+                            .ok();
+                        }
+                    })
+                    .detach();
+                    break task;
+                }
+            }
+        };
+        cx.background_executor()
+            .spawn(async move { task.await.map_err(|error| anyhow!("{}", error)) })
+    }
+
+    pub fn is_channel_admin(&self, channel_id: ChannelId) -> bool {
+        let Some(channel) = self.channel_for_id(channel_id) else {
+            return false;
+        };
+        channel.role == proto::ChannelRole::Admin
+    }
+
+    pub fn channel_participants(&self, channel_id: ChannelId) -> &[Arc<User>] {
+        self.channel_participants
+            .get(&channel_id)
+            .map_or(&[], |v| v.as_slice())
+    }
+
+    pub fn create_channel(
+        &self,
+        name: &str,
+        parent_id: Option<ChannelId>,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<ChannelId>> {
+        let client = self.client.clone();
+        let name = name.trim_start_matches("#").to_owned();
+        cx.spawn(move |this, mut cx| async move {
+            let response = client
+                .request(proto::CreateChannel { name, parent_id })
+                .await?;
+
+            let channel = response
+                .channel
+                .ok_or_else(|| anyhow!("missing channel in response"))?;
+            let channel_id = channel.id;
+
+            this.update(&mut cx, |this, cx| {
+                let task = this.update_channels(
+                    proto::UpdateChannels {
+                        channels: vec![channel],
+                        ..Default::default()
+                    },
+                    cx,
+                );
+                assert!(task.is_none());
+
+                // This event is emitted because the collab panel wants to clear the pending edit state
+                // before this frame is rendered. But we can't guarantee that the collab panel's future
+                // will resolve before this flush_effects finishes. Synchronously emitting this event
+                // ensures that the collab panel will observe this creation before the frame completes
+                cx.emit(ChannelEvent::ChannelCreated(channel_id));
+            })?;
+
+            Ok(channel_id)
+        })
+    }
+
+    pub fn move_channel(
+        &mut self,
+        channel_id: ChannelId,
+        to: Option<ChannelId>,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<()>> {
+        let client = self.client.clone();
+        cx.spawn(move |_, _| async move {
+            let _ = client
+                .request(proto::MoveChannel { channel_id, to })
+                .await?;
+
+            Ok(())
+        })
+    }
+
+    pub fn set_channel_visibility(
+        &mut self,
+        channel_id: ChannelId,
+        visibility: ChannelVisibility,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<()>> {
+        let client = self.client.clone();
+        cx.spawn(move |_, _| async move {
+            let _ = client
+                .request(proto::SetChannelVisibility {
+                    channel_id,
+                    visibility: visibility.into(),
+                })
+                .await?;
+
+            Ok(())
+        })
+    }
+
+    pub fn invite_member(
+        &mut self,
+        channel_id: ChannelId,
+        user_id: UserId,
+        role: proto::ChannelRole,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<()>> {
+        if !self.outgoing_invites.insert((channel_id, user_id)) {
+            return Task::ready(Err(anyhow!("invite request already in progress")));
+        }
+
+        cx.notify();
+        let client = self.client.clone();
+        cx.spawn(move |this, mut cx| async move {
+            let result = client
+                .request(proto::InviteChannelMember {
+                    channel_id,
+                    user_id,
+                    role: role.into(),
+                })
+                .await;
+
+            this.update(&mut cx, |this, cx| {
+                this.outgoing_invites.remove(&(channel_id, user_id));
+                cx.notify();
+            })?;
+
+            result?;
+
+            Ok(())
+        })
+    }
+
+    pub fn remove_member(
+        &mut self,
+        channel_id: ChannelId,
+        user_id: u64,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<()>> {
+        if !self.outgoing_invites.insert((channel_id, user_id)) {
+            return Task::ready(Err(anyhow!("invite request already in progress")));
+        }
+
+        cx.notify();
+        let client = self.client.clone();
+        cx.spawn(move |this, mut cx| async move {
+            let result = client
+                .request(proto::RemoveChannelMember {
+                    channel_id,
+                    user_id,
+                })
+                .await;
+
+            this.update(&mut cx, |this, cx| {
+                this.outgoing_invites.remove(&(channel_id, user_id));
+                cx.notify();
+            })?;
+            result?;
+            Ok(())
+        })
+    }
+
+    pub fn set_member_role(
+        &mut self,
+        channel_id: ChannelId,
+        user_id: UserId,
+        role: proto::ChannelRole,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<()>> {
+        if !self.outgoing_invites.insert((channel_id, user_id)) {
+            return Task::ready(Err(anyhow!("member request already in progress")));
+        }
+
+        cx.notify();
+        let client = self.client.clone();
+        cx.spawn(move |this, mut cx| async move {
+            let result = client
+                .request(proto::SetChannelMemberRole {
+                    channel_id,
+                    user_id,
+                    role: role.into(),
+                })
+                .await;
+
+            this.update(&mut cx, |this, cx| {
+                this.outgoing_invites.remove(&(channel_id, user_id));
+                cx.notify();
+            })?;
+
+            result?;
+            Ok(())
+        })
+    }
+
+    pub fn rename(
+        &mut self,
+        channel_id: ChannelId,
+        new_name: &str,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<()>> {
+        let client = self.client.clone();
+        let name = new_name.to_string();
+        cx.spawn(move |this, mut cx| async move {
+            let channel = client
+                .request(proto::RenameChannel { channel_id, name })
+                .await?
+                .channel
+                .ok_or_else(|| anyhow!("missing channel in response"))?;
+            this.update(&mut cx, |this, cx| {
+                let task = this.update_channels(
+                    proto::UpdateChannels {
+                        channels: vec![channel],
+                        ..Default::default()
+                    },
+                    cx,
+                );
+                assert!(task.is_none());
+
+                // This event is emitted because the collab panel wants to clear the pending edit state
+                // before this frame is rendered. But we can't guarantee that the collab panel's future
+                // will resolve before this flush_effects finishes. Synchronously emitting this event
+                // ensures that the collab panel will observe this creation before the frame complete
+                cx.emit(ChannelEvent::ChannelRenamed(channel_id))
+            })?;
+            Ok(())
+        })
+    }
+
+    pub fn respond_to_channel_invite(
+        &mut self,
+        channel_id: ChannelId,
+        accept: bool,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<()>> {
+        let client = self.client.clone();
+        cx.background_executor().spawn(async move {
+            client
+                .request(proto::RespondToChannelInvite { channel_id, accept })
+                .await?;
+            Ok(())
+        })
+    }
+
+    pub fn get_channel_member_details(
+        &self,
+        channel_id: ChannelId,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<Vec<ChannelMembership>>> {
+        let client = self.client.clone();
+        let user_store = self.user_store.downgrade();
+        cx.spawn(move |_, mut cx| async move {
+            let response = client
+                .request(proto::GetChannelMembers { channel_id })
+                .await?;
+
+            let user_ids = response.members.iter().map(|m| m.user_id).collect();
+            let user_store = user_store
+                .upgrade()
+                .ok_or_else(|| anyhow!("user store dropped"))?;
+            let users = user_store
+                .update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))?
+                .await?;
+
+            Ok(users
+                .into_iter()
+                .zip(response.members)
+                .filter_map(|(user, member)| {
+                    Some(ChannelMembership {
+                        user,
+                        role: member.role(),
+                        kind: member.kind(),
+                    })
+                })
+                .collect())
+        })
+    }
+
+    pub fn remove_channel(&self, channel_id: ChannelId) -> impl Future<Output = Result<()>> {
+        let client = self.client.clone();
+        async move {
+            client.request(proto::DeleteChannel { channel_id }).await?;
+            Ok(())
+        }
+    }
+
+    pub fn has_pending_channel_invite_response(&self, _: &Arc<Channel>) -> bool {
+        false
+    }
+
+    pub fn has_pending_channel_invite(&self, channel_id: ChannelId, user_id: UserId) -> bool {
+        self.outgoing_invites.contains(&(channel_id, user_id))
+    }
+
+    async fn handle_update_channels(
+        this: Model<Self>,
+        message: TypedEnvelope<proto::UpdateChannels>,
+        _: Arc<Client>,
+        mut cx: AsyncAppContext,
+    ) -> Result<()> {
+        this.update(&mut cx, |this, _| {
+            this.update_channels_tx
+                .unbounded_send(message.payload)
+                .unwrap();
+        })?;
+        Ok(())
+    }
+
+    fn handle_connect(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
+        self.channel_index.clear();
+        self.channel_invitations.clear();
+        self.channel_participants.clear();
+        self.channel_index.clear();
+        self.outgoing_invites.clear();
+        self.disconnect_channel_buffers_task.take();
+
+        for chat in self.opened_chats.values() {
+            if let OpenedModelHandle::Open(chat) = chat {
+                if let Some(chat) = chat.upgrade() {
+                    chat.update(cx, |chat, cx| {
+                        chat.rejoin(cx);
+                    });
+                }
+            }
+        }
+
+        let mut buffer_versions = Vec::new();
+        for buffer in self.opened_buffers.values() {
+            if let OpenedModelHandle::Open(buffer) = buffer {
+                if let Some(buffer) = buffer.upgrade() {
+                    let channel_buffer = buffer.read(cx);
+                    let buffer = channel_buffer.buffer().read(cx);
+                    buffer_versions.push(proto::ChannelBufferVersion {
+                        channel_id: channel_buffer.channel_id,
+                        epoch: channel_buffer.epoch(),
+                        version: language::proto::serialize_version(&buffer.version()),
+                    });
+                }
+            }
+        }
+
+        if buffer_versions.is_empty() {
+            return Task::ready(Ok(()));
+        }
+
+        let response = self.client.request(proto::RejoinChannelBuffers {
+            buffers: buffer_versions,
+        });
+
+        cx.spawn(|this, mut cx| async move {
+            let mut response = response.await?;
+
+            this.update(&mut cx, |this, cx| {
+                this.opened_buffers.retain(|_, buffer| match buffer {
+                    OpenedModelHandle::Open(channel_buffer) => {
+                        let Some(channel_buffer) = channel_buffer.upgrade() else {
+                            return false;
+                        };
+
+                        channel_buffer.update(cx, |channel_buffer, cx| {
+                            let channel_id = channel_buffer.channel_id;
+                            if let Some(remote_buffer) = response
+                                .buffers
+                                .iter_mut()
+                                .find(|buffer| buffer.channel_id == channel_id)
+                            {
+                                let channel_id = channel_buffer.channel_id;
+                                let remote_version =
+                                    language::proto::deserialize_version(&remote_buffer.version);
+
+                                channel_buffer.replace_collaborators(
+                                    mem::take(&mut remote_buffer.collaborators),
+                                    cx,
+                                );
+
+                                let operations = channel_buffer
+                                    .buffer()
+                                    .update(cx, |buffer, cx| {
+                                        let outgoing_operations =
+                                            buffer.serialize_ops(Some(remote_version), cx);
+                                        let incoming_operations =
+                                            mem::take(&mut remote_buffer.operations)
+                                                .into_iter()
+                                                .map(language::proto::deserialize_operation)
+                                                .collect::<Result<Vec<_>>>()?;
+                                        buffer.apply_ops(incoming_operations, cx)?;
+                                        anyhow::Ok(outgoing_operations)
+                                    })
+                                    .log_err();
+
+                                if let Some(operations) = operations {
+                                    let client = this.client.clone();
+                                    cx.background_executor()
+                                        .spawn(async move {
+                                            let operations = operations.await;
+                                            for chunk in
+                                                language::proto::split_operations(operations)
+                                            {
+                                                client
+                                                    .send(proto::UpdateChannelBuffer {
+                                                        channel_id,
+                                                        operations: chunk,
+                                                    })
+                                                    .ok();
+                                            }
+                                        })
+                                        .detach();
+                                    return true;
+                                }
+                            }
+
+                            channel_buffer.disconnect(cx);
+                            false
+                        })
+                    }
+                    OpenedModelHandle::Loading(_) => true,
+                });
+            })
+            .ok();
+            anyhow::Ok(())
+        })
+    }
+
+    fn handle_disconnect(&mut self, wait_for_reconnect: bool, cx: &mut ModelContext<Self>) {
+        cx.notify();
+
+        self.disconnect_channel_buffers_task.get_or_insert_with(|| {
+            cx.spawn(move |this, mut cx| async move {
+                if wait_for_reconnect {
+                    cx.background_executor().timer(RECONNECT_TIMEOUT).await;
+                }
+
+                if let Some(this) = this.upgrade() {
+                    this.update(&mut cx, |this, cx| {
+                        for (_, buffer) in this.opened_buffers.drain() {
+                            if let OpenedModelHandle::Open(buffer) = buffer {
+                                if let Some(buffer) = buffer.upgrade() {
+                                    buffer.update(cx, |buffer, cx| buffer.disconnect(cx));
+                                }
+                            }
+                        }
+                    })
+                    .ok();
+                }
+            })
+        });
+    }
+
+    pub(crate) fn update_channels(
+        &mut self,
+        payload: proto::UpdateChannels,
+        cx: &mut ModelContext<ChannelStore>,
+    ) -> Option<Task<Result<()>>> {
+        if !payload.remove_channel_invitations.is_empty() {
+            self.channel_invitations
+                .retain(|channel| !payload.remove_channel_invitations.contains(&channel.id));
+        }
+        for channel in payload.channel_invitations {
+            match self
+                .channel_invitations
+                .binary_search_by_key(&channel.id, |c| c.id)
+            {
+                Ok(ix) => Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name,
+                Err(ix) => self.channel_invitations.insert(
+                    ix,
+                    Arc::new(Channel {
+                        id: channel.id,
+                        visibility: channel.visibility(),
+                        role: channel.role(),
+                        name: channel.name,
+                        unseen_note_version: None,
+                        unseen_message_id: None,
+                        parent_path: channel.parent_path,
+                    }),
+                ),
+            }
+        }
+
+        let channels_changed = !payload.channels.is_empty()
+            || !payload.delete_channels.is_empty()
+            || !payload.unseen_channel_messages.is_empty()
+            || !payload.unseen_channel_buffer_changes.is_empty();
+
+        if channels_changed {
+            if !payload.delete_channels.is_empty() {
+                self.channel_index.delete_channels(&payload.delete_channels);
+                self.channel_participants
+                    .retain(|channel_id, _| !&payload.delete_channels.contains(channel_id));
+
+                for channel_id in &payload.delete_channels {
+                    let channel_id = *channel_id;
+                    if payload
+                        .channels
+                        .iter()
+                        .any(|channel| channel.id == channel_id)
+                    {
+                        continue;
+                    }
+                    if let Some(OpenedModelHandle::Open(buffer)) =
+                        self.opened_buffers.remove(&channel_id)
+                    {
+                        if let Some(buffer) = buffer.upgrade() {
+                            buffer.update(cx, ChannelBuffer::disconnect);
+                        }
+                    }
+                }
+            }
+
+            let mut index = self.channel_index.bulk_insert();
+            for channel in payload.channels {
+                let id = channel.id;
+                let channel_changed = index.insert(channel);
+
+                if channel_changed {
+                    if let Some(OpenedModelHandle::Open(buffer)) = self.opened_buffers.get(&id) {
+                        if let Some(buffer) = buffer.upgrade() {
+                            buffer.update(cx, ChannelBuffer::channel_changed);
+                        }
+                    }
+                }
+            }
+
+            for unseen_buffer_change in payload.unseen_channel_buffer_changes {
+                let version = language::proto::deserialize_version(&unseen_buffer_change.version);
+                index.note_changed(
+                    unseen_buffer_change.channel_id,
+                    unseen_buffer_change.epoch,
+                    &version,
+                );
+            }
+
+            for unseen_channel_message in payload.unseen_channel_messages {
+                index.new_messages(
+                    unseen_channel_message.channel_id,
+                    unseen_channel_message.message_id,
+                );
+            }
+        }
+
+        cx.notify();
+        if payload.channel_participants.is_empty() {
+            return None;
+        }
+
+        let mut all_user_ids = Vec::new();
+        let channel_participants = payload.channel_participants;
+        for entry in &channel_participants {
+            for user_id in entry.participant_user_ids.iter() {
+                if let Err(ix) = all_user_ids.binary_search(user_id) {
+                    all_user_ids.insert(ix, *user_id);
+                }
+            }
+        }
+
+        let users = self
+            .user_store
+            .update(cx, |user_store, cx| user_store.get_users(all_user_ids, cx));
+        Some(cx.spawn(|this, mut cx| async move {
+            let users = users.await?;
+
+            this.update(&mut cx, |this, cx| {
+                for entry in &channel_participants {
+                    let mut participants: Vec<_> = entry
+                        .participant_user_ids
+                        .iter()
+                        .filter_map(|user_id| {
+                            users
+                                .binary_search_by_key(&user_id, |user| &user.id)
+                                .ok()
+                                .map(|ix| users[ix].clone())
+                        })
+                        .collect();
+
+                    participants.sort_by_key(|u| u.id);
+
+                    this.channel_participants
+                        .insert(entry.channel_id, participants);
+                }
+
+                cx.notify();
+            })
+        }))
+    }
+}

crates/channel2/src/channel_store/channel_index.rs 🔗

@@ -0,0 +1,184 @@
+use crate::{Channel, ChannelId};
+use collections::BTreeMap;
+use rpc::proto;
+use std::sync::Arc;
+
+#[derive(Default, Debug)]
+pub struct ChannelIndex {
+    channels_ordered: Vec<ChannelId>,
+    channels_by_id: BTreeMap<ChannelId, Arc<Channel>>,
+}
+
+impl ChannelIndex {
+    pub fn by_id(&self) -> &BTreeMap<ChannelId, Arc<Channel>> {
+        &self.channels_by_id
+    }
+
+    pub fn ordered_channels(&self) -> &[ChannelId] {
+        &self.channels_ordered
+    }
+
+    pub fn clear(&mut self) {
+        self.channels_ordered.clear();
+        self.channels_by_id.clear();
+    }
+
+    /// Delete the given channels from this index.
+    pub fn delete_channels(&mut self, channels: &[ChannelId]) {
+        self.channels_by_id
+            .retain(|channel_id, _| !channels.contains(channel_id));
+        self.channels_ordered
+            .retain(|channel_id| !channels.contains(channel_id));
+    }
+
+    pub fn bulk_insert(&mut self) -> ChannelPathsInsertGuard {
+        ChannelPathsInsertGuard {
+            channels_ordered: &mut self.channels_ordered,
+            channels_by_id: &mut self.channels_by_id,
+        }
+    }
+
+    pub fn acknowledge_note_version(
+        &mut self,
+        channel_id: ChannelId,
+        epoch: u64,
+        version: &clock::Global,
+    ) {
+        if let Some(channel) = self.channels_by_id.get_mut(&channel_id) {
+            let channel = Arc::make_mut(channel);
+            if let Some((unseen_epoch, unseen_version)) = &channel.unseen_note_version {
+                if epoch > *unseen_epoch
+                    || epoch == *unseen_epoch && version.observed_all(unseen_version)
+                {
+                    channel.unseen_note_version = None;
+                }
+            }
+        }
+    }
+
+    pub fn acknowledge_message_id(&mut self, channel_id: ChannelId, message_id: u64) {
+        if let Some(channel) = self.channels_by_id.get_mut(&channel_id) {
+            let channel = Arc::make_mut(channel);
+            if let Some(unseen_message_id) = channel.unseen_message_id {
+                if message_id >= unseen_message_id {
+                    channel.unseen_message_id = None;
+                }
+            }
+        }
+    }
+
+    pub fn note_changed(&mut self, channel_id: ChannelId, epoch: u64, version: &clock::Global) {
+        insert_note_changed(&mut self.channels_by_id, channel_id, epoch, version);
+    }
+
+    pub fn new_message(&mut self, channel_id: ChannelId, message_id: u64) {
+        insert_new_message(&mut self.channels_by_id, channel_id, message_id)
+    }
+}
+
+/// A guard for ensuring that the paths index maintains its sort and uniqueness
+/// invariants after a series of insertions
+#[derive(Debug)]
+pub struct ChannelPathsInsertGuard<'a> {
+    channels_ordered: &'a mut Vec<ChannelId>,
+    channels_by_id: &'a mut BTreeMap<ChannelId, Arc<Channel>>,
+}
+
+impl<'a> ChannelPathsInsertGuard<'a> {
+    pub fn note_changed(&mut self, channel_id: ChannelId, epoch: u64, version: &clock::Global) {
+        insert_note_changed(&mut self.channels_by_id, channel_id, epoch, &version);
+    }
+
+    pub fn new_messages(&mut self, channel_id: ChannelId, message_id: u64) {
+        insert_new_message(&mut self.channels_by_id, channel_id, message_id)
+    }
+
+    pub fn insert(&mut self, channel_proto: proto::Channel) -> bool {
+        let mut ret = false;
+        if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) {
+            let existing_channel = Arc::make_mut(existing_channel);
+
+            ret = existing_channel.visibility != channel_proto.visibility()
+                || existing_channel.role != channel_proto.role()
+                || existing_channel.name != channel_proto.name;
+
+            existing_channel.visibility = channel_proto.visibility();
+            existing_channel.role = channel_proto.role();
+            existing_channel.name = channel_proto.name;
+        } else {
+            self.channels_by_id.insert(
+                channel_proto.id,
+                Arc::new(Channel {
+                    id: channel_proto.id,
+                    visibility: channel_proto.visibility(),
+                    role: channel_proto.role(),
+                    name: channel_proto.name,
+                    unseen_note_version: None,
+                    unseen_message_id: None,
+                    parent_path: channel_proto.parent_path,
+                }),
+            );
+            self.insert_root(channel_proto.id);
+        }
+        ret
+    }
+
+    fn insert_root(&mut self, channel_id: ChannelId) {
+        self.channels_ordered.push(channel_id);
+    }
+}
+
+impl<'a> Drop for ChannelPathsInsertGuard<'a> {
+    fn drop(&mut self) {
+        self.channels_ordered.sort_by(|a, b| {
+            let a = channel_path_sorting_key(*a, &self.channels_by_id);
+            let b = channel_path_sorting_key(*b, &self.channels_by_id);
+            a.cmp(b)
+        });
+        self.channels_ordered.dedup();
+    }
+}
+
+fn channel_path_sorting_key<'a>(
+    id: ChannelId,
+    channels_by_id: &'a BTreeMap<ChannelId, Arc<Channel>>,
+) -> impl Iterator<Item = &str> {
+    let (parent_path, name) = channels_by_id
+        .get(&id)
+        .map_or((&[] as &[_], None), |channel| {
+            (channel.parent_path.as_slice(), Some(channel.name.as_str()))
+        });
+    parent_path
+        .iter()
+        .filter_map(|id| Some(channels_by_id.get(id)?.name.as_str()))
+        .chain(name)
+}
+
+fn insert_note_changed(
+    channels_by_id: &mut BTreeMap<ChannelId, Arc<Channel>>,
+    channel_id: u64,
+    epoch: u64,
+    version: &clock::Global,
+) {
+    if let Some(channel) = channels_by_id.get_mut(&channel_id) {
+        let unseen_version = Arc::make_mut(channel)
+            .unseen_note_version
+            .get_or_insert((0, clock::Global::new()));
+        if epoch > unseen_version.0 {
+            *unseen_version = (epoch, version.clone());
+        } else {
+            unseen_version.1.join(&version);
+        }
+    }
+}
+
+fn insert_new_message(
+    channels_by_id: &mut BTreeMap<ChannelId, Arc<Channel>>,
+    channel_id: u64,
+    message_id: u64,
+) {
+    if let Some(channel) = channels_by_id.get_mut(&channel_id) {
+        let unseen_message_id = Arc::make_mut(channel).unseen_message_id.get_or_insert(0);
+        *unseen_message_id = message_id.max(*unseen_message_id);
+    }
+}

crates/channel2/src/channel_store_tests.rs 🔗

@@ -0,0 +1,380 @@
+use crate::channel_chat::ChannelChatEvent;
+
+use super::*;
+use client::{test::FakeServer, Client, UserStore};
+use gpui::{AppContext, Context, Model, TestAppContext};
+use rpc::proto::{self};
+use settings::SettingsStore;
+use util::http::FakeHttpClient;
+
+#[gpui::test]
+fn test_update_channels(cx: &mut AppContext) {
+    let channel_store = init_test(cx);
+
+    update_channels(
+        &channel_store,
+        proto::UpdateChannels {
+            channels: vec![
+                proto::Channel {
+                    id: 1,
+                    name: "b".to_string(),
+                    visibility: proto::ChannelVisibility::Members as i32,
+                    role: proto::ChannelRole::Admin.into(),
+                    parent_path: Vec::new(),
+                },
+                proto::Channel {
+                    id: 2,
+                    name: "a".to_string(),
+                    visibility: proto::ChannelVisibility::Members as i32,
+                    role: proto::ChannelRole::Member.into(),
+                    parent_path: Vec::new(),
+                },
+            ],
+            ..Default::default()
+        },
+        cx,
+    );
+    assert_channels(
+        &channel_store,
+        &[
+            //
+            (0, "a".to_string(), proto::ChannelRole::Member),
+            (0, "b".to_string(), proto::ChannelRole::Admin),
+        ],
+        cx,
+    );
+
+    update_channels(
+        &channel_store,
+        proto::UpdateChannels {
+            channels: vec![
+                proto::Channel {
+                    id: 3,
+                    name: "x".to_string(),
+                    visibility: proto::ChannelVisibility::Members as i32,
+                    role: proto::ChannelRole::Admin.into(),
+                    parent_path: vec![1],
+                },
+                proto::Channel {
+                    id: 4,
+                    name: "y".to_string(),
+                    visibility: proto::ChannelVisibility::Members as i32,
+                    role: proto::ChannelRole::Member.into(),
+                    parent_path: vec![2],
+                },
+            ],
+            ..Default::default()
+        },
+        cx,
+    );
+    assert_channels(
+        &channel_store,
+        &[
+            (0, "a".to_string(), proto::ChannelRole::Member),
+            (1, "y".to_string(), proto::ChannelRole::Member),
+            (0, "b".to_string(), proto::ChannelRole::Admin),
+            (1, "x".to_string(), proto::ChannelRole::Admin),
+        ],
+        cx,
+    );
+}
+
+#[gpui::test]
+fn test_dangling_channel_paths(cx: &mut AppContext) {
+    let channel_store = init_test(cx);
+
+    update_channels(
+        &channel_store,
+        proto::UpdateChannels {
+            channels: vec![
+                proto::Channel {
+                    id: 0,
+                    name: "a".to_string(),
+                    visibility: proto::ChannelVisibility::Members as i32,
+                    role: proto::ChannelRole::Admin.into(),
+                    parent_path: vec![],
+                },
+                proto::Channel {
+                    id: 1,
+                    name: "b".to_string(),
+                    visibility: proto::ChannelVisibility::Members as i32,
+                    role: proto::ChannelRole::Admin.into(),
+                    parent_path: vec![0],
+                },
+                proto::Channel {
+                    id: 2,
+                    name: "c".to_string(),
+                    visibility: proto::ChannelVisibility::Members as i32,
+                    role: proto::ChannelRole::Admin.into(),
+                    parent_path: vec![0, 1],
+                },
+            ],
+            ..Default::default()
+        },
+        cx,
+    );
+    // Sanity check
+    assert_channels(
+        &channel_store,
+        &[
+            //
+            (0, "a".to_string(), proto::ChannelRole::Admin),
+            (1, "b".to_string(), proto::ChannelRole::Admin),
+            (2, "c".to_string(), proto::ChannelRole::Admin),
+        ],
+        cx,
+    );
+
+    update_channels(
+        &channel_store,
+        proto::UpdateChannels {
+            delete_channels: vec![1, 2],
+            ..Default::default()
+        },
+        cx,
+    );
+
+    // Make sure that the 1/2/3 path is gone
+    assert_channels(
+        &channel_store,
+        &[(0, "a".to_string(), proto::ChannelRole::Admin)],
+        cx,
+    );
+}
+
+#[gpui::test]
+async fn test_channel_messages(cx: &mut TestAppContext) {
+    let user_id = 5;
+    let channel_id = 5;
+    let channel_store = cx.update(init_test);
+    let client = channel_store.update(cx, |s, _| s.client());
+    let server = FakeServer::for_client(user_id, &client, cx).await;
+
+    // Get the available channels.
+    server.send(proto::UpdateChannels {
+        channels: vec![proto::Channel {
+            id: channel_id,
+            name: "the-channel".to_string(),
+            visibility: proto::ChannelVisibility::Members as i32,
+            role: proto::ChannelRole::Member.into(),
+            parent_path: vec![],
+        }],
+        ..Default::default()
+    });
+    cx.executor().run_until_parked();
+    cx.update(|cx| {
+        assert_channels(
+            &channel_store,
+            &[(0, "the-channel".to_string(), proto::ChannelRole::Member)],
+            cx,
+        );
+    });
+
+    let get_users = server.receive::<proto::GetUsers>().await.unwrap();
+    assert_eq!(get_users.payload.user_ids, vec![5]);
+    server.respond(
+        get_users.receipt(),
+        proto::UsersResponse {
+            users: vec![proto::User {
+                id: 5,
+                github_login: "nathansobo".into(),
+                avatar_url: "http://avatar.com/nathansobo".into(),
+            }],
+        },
+    );
+
+    // Join a channel and populate its existing messages.
+    let channel = channel_store.update(cx, |store, cx| {
+        let channel_id = store.ordered_channels().next().unwrap().1.id;
+        store.open_channel_chat(channel_id, cx)
+    });
+    let join_channel = server.receive::<proto::JoinChannelChat>().await.unwrap();
+    server.respond(
+        join_channel.receipt(),
+        proto::JoinChannelChatResponse {
+            messages: vec![
+                proto::ChannelMessage {
+                    id: 10,
+                    body: "a".into(),
+                    timestamp: 1000,
+                    sender_id: 5,
+                    mentions: vec![],
+                    nonce: Some(1.into()),
+                },
+                proto::ChannelMessage {
+                    id: 11,
+                    body: "b".into(),
+                    timestamp: 1001,
+                    sender_id: 6,
+                    mentions: vec![],
+                    nonce: Some(2.into()),
+                },
+            ],
+            done: false,
+        },
+    );
+
+    cx.executor().start_waiting();
+
+    // Client requests all users for the received messages
+    let mut get_users = server.receive::<proto::GetUsers>().await.unwrap();
+    get_users.payload.user_ids.sort();
+    assert_eq!(get_users.payload.user_ids, vec![6]);
+    server.respond(
+        get_users.receipt(),
+        proto::UsersResponse {
+            users: vec![proto::User {
+                id: 6,
+                github_login: "maxbrunsfeld".into(),
+                avatar_url: "http://avatar.com/maxbrunsfeld".into(),
+            }],
+        },
+    );
+
+    let channel = channel.await.unwrap();
+    channel.update(cx, |channel, _| {
+        assert_eq!(
+            channel
+                .messages_in_range(0..2)
+                .map(|message| (message.sender.github_login.clone(), message.body.clone()))
+                .collect::<Vec<_>>(),
+            &[
+                ("nathansobo".into(), "a".into()),
+                ("maxbrunsfeld".into(), "b".into())
+            ]
+        );
+    });
+
+    // Receive a new message.
+    server.send(proto::ChannelMessageSent {
+        channel_id,
+        message: Some(proto::ChannelMessage {
+            id: 12,
+            body: "c".into(),
+            timestamp: 1002,
+            sender_id: 7,
+            mentions: vec![],
+            nonce: Some(3.into()),
+        }),
+    });
+
+    // Client requests user for message since they haven't seen them yet
+    let get_users = server.receive::<proto::GetUsers>().await.unwrap();
+    assert_eq!(get_users.payload.user_ids, vec![7]);
+    server.respond(
+        get_users.receipt(),
+        proto::UsersResponse {
+            users: vec![proto::User {
+                id: 7,
+                github_login: "as-cii".into(),
+                avatar_url: "http://avatar.com/as-cii".into(),
+            }],
+        },
+    );
+
+    assert_eq!(
+        channel.next_event(cx),
+        ChannelChatEvent::MessagesUpdated {
+            old_range: 2..2,
+            new_count: 1,
+        }
+    );
+    channel.update(cx, |channel, _| {
+        assert_eq!(
+            channel
+                .messages_in_range(2..3)
+                .map(|message| (message.sender.github_login.clone(), message.body.clone()))
+                .collect::<Vec<_>>(),
+            &[("as-cii".into(), "c".into())]
+        )
+    });
+
+    // Scroll up to view older messages.
+    channel.update(cx, |channel, cx| {
+        channel.load_more_messages(cx).unwrap().detach();
+    });
+    let get_messages = server.receive::<proto::GetChannelMessages>().await.unwrap();
+    assert_eq!(get_messages.payload.channel_id, 5);
+    assert_eq!(get_messages.payload.before_message_id, 10);
+    server.respond(
+        get_messages.receipt(),
+        proto::GetChannelMessagesResponse {
+            done: true,
+            messages: vec![
+                proto::ChannelMessage {
+                    id: 8,
+                    body: "y".into(),
+                    timestamp: 998,
+                    sender_id: 5,
+                    nonce: Some(4.into()),
+                    mentions: vec![],
+                },
+                proto::ChannelMessage {
+                    id: 9,
+                    body: "z".into(),
+                    timestamp: 999,
+                    sender_id: 6,
+                    nonce: Some(5.into()),
+                    mentions: vec![],
+                },
+            ],
+        },
+    );
+
+    assert_eq!(
+        channel.next_event(cx),
+        ChannelChatEvent::MessagesUpdated {
+            old_range: 0..0,
+            new_count: 2,
+        }
+    );
+    channel.update(cx, |channel, _| {
+        assert_eq!(
+            channel
+                .messages_in_range(0..2)
+                .map(|message| (message.sender.github_login.clone(), message.body.clone()))
+                .collect::<Vec<_>>(),
+            &[
+                ("nathansobo".into(), "y".into()),
+                ("maxbrunsfeld".into(), "z".into())
+            ]
+        );
+    });
+}
+
+fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
+    let http = FakeHttpClient::with_404_response();
+    let client = Client::new(http.clone(), cx);
+    let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http, cx));
+
+    let settings_store = SettingsStore::test(cx);
+    cx.set_global(settings_store);
+    client::init(&client, cx);
+    crate::init(&client, user_store, cx);
+
+    ChannelStore::global(cx)
+}
+
+fn update_channels(
+    channel_store: &Model<ChannelStore>,
+    message: proto::UpdateChannels,
+    cx: &mut AppContext,
+) {
+    let task = channel_store.update(cx, |store, cx| store.update_channels(message, cx));
+    assert!(task.is_none());
+}
+
+#[track_caller]
+fn assert_channels(
+    channel_store: &Model<ChannelStore>,
+    expected_channels: &[(usize, String, proto::ChannelRole)],
+    cx: &mut AppContext,
+) {
+    let actual = channel_store.update(cx, |store, _| {
+        store
+            .ordered_channels()
+            .map(|(depth, channel)| (depth, channel.name.to_string(), channel.role))
+            .collect::<Vec<_>>()
+    });
+    assert_eq!(actual, expected_channels);
+}

crates/client2/src/user.rs 🔗

@@ -292,22 +292,18 @@ impl UserStore {
                         .upgrade()
                         .ok_or_else(|| anyhow!("can't upgrade user store handle"))?;
                     for contact in message.contacts {
-                        let should_notify = contact.should_notify;
-                        updated_contacts.push((
-                            Arc::new(Contact::from_proto(contact, &this, &mut cx).await?),
-                            should_notify,
+                        updated_contacts.push(Arc::new(
+                            Contact::from_proto(contact, &this, &mut cx).await?,
                         ));
                     }
 
                     let mut incoming_requests = Vec::new();
                     for request in message.incoming_requests {
                         incoming_requests.push({
-                            let user = this
-                                .update(&mut cx, |this, cx| {
-                                    this.get_user(request.requester_id, cx)
-                                })?
-                                .await?;
-                            (user, request.should_notify)
+                            this.update(&mut cx, |this, cx| {
+                                this.get_user(request.requester_id, cx)
+                            })?
+                            .await?
                         });
                     }
 
@@ -331,13 +327,7 @@ impl UserStore {
                         this.contacts
                             .retain(|contact| !removed_contacts.contains(&contact.user.id));
                         // Update existing contacts and insert new ones
-                        for (updated_contact, should_notify) in updated_contacts {
-                            if should_notify {
-                                cx.emit(Event::Contact {
-                                    user: updated_contact.user.clone(),
-                                    kind: ContactEventKind::Accepted,
-                                });
-                            }
+                        for updated_contact in updated_contacts {
                             match this.contacts.binary_search_by_key(
                                 &&updated_contact.user.github_login,
                                 |contact| &contact.user.github_login,
@@ -360,14 +350,7 @@ impl UserStore {
                             }
                         });
                         // Update existing incoming requests and insert new ones
-                        for (user, should_notify) in incoming_requests {
-                            if should_notify {
-                                cx.emit(Event::Contact {
-                                    user: user.clone(),
-                                    kind: ContactEventKind::Requested,
-                                });
-                            }
-
+                        for user in incoming_requests {
                             match this
                                 .incoming_contact_requests
                                 .binary_search_by_key(&&user.github_login, |contact| {

crates/copilot2/Cargo.toml 🔗

@@ -20,7 +20,7 @@ test-support = [
 
 [dependencies]
 collections = { path = "../collections" }
-context_menu = { path = "../context_menu" }
+# context_menu = { path = "../context_menu" }
 gpui = { package = "gpui2", path = "../gpui2" }
 language = { package = "language2", path = "../language2" }
 settings = { package = "settings2", path = "../settings2" }

crates/copilot2/src/copilot2.rs 🔗

@@ -42,6 +42,11 @@ use util::{
 //     copilot,
 //     [Suggest, NextSuggestion, PreviousSuggestion, Reinstall]
 // );
+//
+pub struct Suggest;
+pub struct NextSuggestion;
+pub struct PreviousSuggestion;
+pub struct Reinstall;
 
 pub fn init(
     new_server_id: LanguageServerId,

crates/editor2/Cargo.toml 🔗

@@ -0,0 +1,93 @@
+[package]
+name = "editor2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/editor.rs"
+doctest = false
+
+[features]
+test-support = [
+    "copilot/test-support",
+    "text/test-support",
+    "language/test-support",
+    "gpui/test-support",
+    "multi_buffer/test-support",
+    "project/test-support",
+    "util/test-support",
+    "workspace/test-support",
+    "tree-sitter-rust",
+    "tree-sitter-typescript"
+]
+
+[dependencies]
+client = { package = "client2", path = "../client2" }
+clock = { path = "../clock" }
+copilot = { package="copilot2", path = "../copilot2" }
+db = { package="db2", path = "../db2" }
+drag_and_drop = { path = "../drag_and_drop" }
+collections = { path = "../collections" }
+# context_menu = { path = "../context_menu" }
+fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
+git = { path = "../git" }
+gpui = { package = "gpui2", path = "../gpui2" }
+language = { package = "language2", path = "../language2" }
+lsp = { package = "lsp2", path = "../lsp2" }
+multi_buffer = { package = "multi_buffer2", path = "../multi_buffer2" }
+project = { package = "project2", path = "../project2" }
+rpc = { package = "rpc2", path = "../rpc2" }
+rich_text = { package = "rich_text2", path = "../rich_text2" }
+settings = { package="settings2", path = "../settings2" }
+snippet = { path = "../snippet" }
+sum_tree = { path = "../sum_tree" }
+text = { package="text2", path = "../text2" }
+theme = { package="theme2", path = "../theme2" }
+util = { path = "../util" }
+sqlez = { path = "../sqlez" }
+workspace = { package = "workspace2", path = "../workspace2" }
+
+aho-corasick = "1.1"
+anyhow.workspace = true
+convert_case = "0.6.0"
+futures.workspace = true
+indoc = "1.0.4"
+itertools = "0.10"
+lazy_static.workspace = true
+log.workspace = true
+ordered-float.workspace = true
+parking_lot.workspace = true
+postage.workspace = true
+rand.workspace = true
+schemars.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+serde_derive.workspace = true
+smallvec.workspace = true
+smol.workspace = true
+
+tree-sitter-rust = { workspace = true, optional = true }
+tree-sitter-html = { workspace = true, optional = true }
+tree-sitter-typescript = { workspace = true, optional = true }
+
+[dev-dependencies]
+copilot = { package="copilot2", path = "../copilot2", features = ["test-support"] }
+text = { package="text2", path = "../text2", features = ["test-support"] }
+language = { package="language2", path = "../language2", features = ["test-support"] }
+lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+util = { path = "../util", features = ["test-support"] }
+project = { package = "project2", path = "../project2", features = ["test-support"] }
+settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
+workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+multi_buffer = { package = "multi_buffer2", path = "../multi_buffer2", features = ["test-support"] }
+
+ctor.workspace = true
+env_logger.workspace = true
+rand.workspace = true
+unindent.workspace = true
+tree-sitter.workspace = true
+tree-sitter-rust.workspace = true
+tree-sitter-html.workspace = true
+tree-sitter-typescript.workspace = true
@@ -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
+    }
+}

crates/editor2/src/display_map.rs 🔗

@@ -0,0 +1,1900 @@
+mod block_map;
+mod fold_map;
+mod inlay_map;
+mod tab_map;
+mod wrap_map;
+
+use crate::{
+    link_go_to_definition::InlayHighlight, movement::TextLayoutDetails, Anchor, AnchorRangeExt,
+    InlayId, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
+};
+pub use block_map::{BlockMap, BlockPoint};
+use collections::{BTreeMap, HashMap, HashSet};
+use fold_map::FoldMap;
+use gpui::{Font, FontId, HighlightStyle, Hsla, Line, Model, ModelContext, Pixels};
+use inlay_map::InlayMap;
+use language::{
+    language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
+};
+use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
+use sum_tree::{Bias, TreeMap};
+use tab_map::TabMap;
+use wrap_map::WrapMap;
+
+pub use block_map::{
+    BlockBufferRows as DisplayBufferRows, BlockChunks as DisplayChunks, BlockContext,
+    BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
+};
+
+pub use self::fold_map::FoldPoint;
+pub use self::inlay_map::{Inlay, InlayOffset, InlayPoint};
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum FoldStatus {
+    Folded,
+    Foldable,
+}
+
+pub trait ToDisplayPoint {
+    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
+}
+
+type TextHighlights = TreeMap<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);
+//         });
+//     }
+// }

crates/editor2/src/display_map/block_map.rs 🔗

@@ -0,0 +1,1667 @@
+use super::{
+    wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
+    Highlights,
+};
+use crate::{Anchor, Editor, ExcerptId, ExcerptRange, ToPoint as _};
+use collections::{Bound, HashMap, HashSet};
+use gpui::{AnyElement, ViewContext};
+use language::{BufferSnapshot, Chunk, Patch, Point};
+use parking_lot::Mutex;
+use std::{
+    cell::RefCell,
+    cmp::{self, Ordering},
+    fmt::Debug,
+    ops::{Deref, DerefMut, Range},
+    sync::{
+        atomic::{AtomicUsize, Ordering::SeqCst},
+        Arc,
+    },
+};
+use sum_tree::{Bias, SumTree};
+use text::Edit;
+
+const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
+
+pub struct BlockMap {
+    next_block_id: AtomicUsize,
+    wrap_snapshot: RefCell<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)
+//         }
+//     }
+// }

crates/editor2/src/display_map/fold_map.rs 🔗

@@ -0,0 +1,1707 @@
+use super::{
+    inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
+    Highlights,
+};
+use crate::{Anchor, AnchorRangeExt, MultiBufferSnapshot, ToOffset};
+use gpui::{HighlightStyle, Hsla};
+use language::{Chunk, Edit, Point, TextSummary};
+use std::{
+    any::TypeId,
+    cmp::{self, Ordering},
+    iter,
+    ops::{Add, AddAssign, Range, Sub},
+};
+use sum_tree::{Bias, Cursor, FilterCursor, SumTree};
+
+#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
+pub struct FoldPoint(pub Point);
+
+impl FoldPoint {
+    pub fn new(row: u32, column: u32) -> Self {
+        Self(Point::new(row, column))
+    }
+
+    pub fn row(self) -> u32 {
+        self.0.row
+    }
+
+    pub fn column(self) -> u32 {
+        self.0.column
+    }
+
+    pub fn row_mut(&mut self) -> &mut u32 {
+        &mut self.0.row
+    }
+
+    #[cfg(test)]
+    pub fn column_mut(&mut self) -> &mut u32 {
+        &mut self.0.column
+    }
+
+    pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint {
+        let mut cursor = snapshot.transforms.cursor::<(FoldPoint, InlayPoint)>();
+        cursor.seek(&self, Bias::Right, &());
+        let overshoot = self.0 - cursor.start().0 .0;
+        InlayPoint(cursor.start().1 .0 + overshoot)
+    }
+
+    pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset {
+        let mut cursor = snapshot
+            .transforms
+            .cursor::<(FoldPoint, TransformSummary)>();
+        cursor.seek(&self, Bias::Right, &());
+        let overshoot = self.0 - cursor.start().1.output.lines;
+        let mut offset = cursor.start().1.output.len;
+        if !overshoot.is_zero() {
+            let transform = cursor.item().expect("display point out of range");
+            assert!(transform.output_text.is_none());
+            let end_inlay_offset = snapshot
+                .inlay_snapshot
+                .to_offset(InlayPoint(cursor.start().1.input.lines + overshoot));
+            offset += end_inlay_offset.0 - cursor.start().1.input.len;
+        }
+        FoldOffset(offset)
+    }
+}
+
+impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
+    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
+        self.0 += &summary.output.lines;
+    }
+}
+
+pub struct FoldMapWriter<'a>(&'a mut FoldMap);
+
+impl<'a> FoldMapWriter<'a> {
+    pub fn fold<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
+        }
+    }
+}

crates/editor2/src/display_map/inlay_map.rs 🔗

@@ -0,0 +1,1896 @@
+use crate::{Anchor, InlayId, MultiBufferSnapshot, ToOffset};
+use collections::{BTreeMap, BTreeSet};
+use gpui::HighlightStyle;
+use language::{Chunk, Edit, Point, TextSummary};
+use multi_buffer::{MultiBufferChunks, MultiBufferRows};
+use std::{
+    any::TypeId,
+    cmp,
+    iter::Peekable,
+    ops::{Add, AddAssign, Range, Sub, SubAssign},
+    sync::Arc,
+    vec,
+};
+use sum_tree::{Bias, Cursor, SumTree, TreeMap};
+use text::{Patch, Rope};
+
+use super::Highlights;
+
+pub struct InlayMap {
+    snapshot: InlaySnapshot,
+    inlays: Vec<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);
+    }
+}

crates/editor2/src/display_map/tab_map.rs 🔗

@@ -0,0 +1,765 @@
+use super::{
+    fold_map::{self, FoldChunks, FoldEdit, FoldPoint, FoldSnapshot},
+    Highlights,
+};
+use crate::MultiBufferSnapshot;
+use language::{Chunk, Point};
+use std::{cmp, mem, num::NonZeroU32, ops::Range};
+use sum_tree::Bias;
+
+const MAX_EXPANSION_COLUMN: u32 = 256;
+
+pub struct TabMap(TabSnapshot);
+
+impl TabMap {
+    pub fn new(fold_snapshot: FoldSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) {
+        let snapshot = TabSnapshot {
+            fold_snapshot,
+            tab_size,
+            max_expansion_column: MAX_EXPANSION_COLUMN,
+            version: 0,
+        };
+        (Self(snapshot.clone()), snapshot)
+    }
+
+    #[cfg(test)]
+    pub fn set_max_expansion_column(&mut self, column: u32) -> TabSnapshot {
+        self.0.max_expansion_column = column;
+        self.0.clone()
+    }
+
+    pub fn sync(
+        &mut self,
+        fold_snapshot: FoldSnapshot,
+        mut fold_edits: Vec<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})"
+            );
+        }
+    }
+}

crates/editor2/src/display_map/wrap_map.rs 🔗

@@ -0,0 +1,1362 @@
+use super::{
+    fold_map::FoldBufferRows,
+    tab_map::{self, TabEdit, TabPoint, TabSnapshot},
+    Highlights,
+};
+use crate::MultiBufferSnapshot;
+use gpui::{AppContext, Context, Font, LineWrapper, Model, ModelContext, Pixels, Task};
+use language::{Chunk, Point};
+use lazy_static::lazy_static;
+use smol::future::yield_now;
+use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration};
+use sum_tree::{Bias, Cursor, SumTree};
+use text::Patch;
+use util::ResultExt;
+
+pub use super::tab_map::TextSummary;
+pub type WrapEdit = text::Edit<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
+//                 );
+//             }
+//         }
+//     }
+// }

crates/editor2/src/editor.rs 🔗

@@ -0,0 +1,10120 @@
+#![allow(unused)]
+mod blink_manager;
+pub mod display_map;
+mod editor_settings;
+mod element;
+mod inlay_hint_cache;
+
+mod git;
+mod highlight_matching_bracket;
+mod hover_popover;
+pub mod items;
+mod link_go_to_definition;
+mod mouse_context_menu;
+pub mod movement;
+mod persistence;
+pub mod scroll;
+pub mod selections_collection;
+
+#[cfg(test)]
+mod editor_tests;
+#[cfg(any(test, feature = "test-support"))]
+pub mod test;
+use aho_corasick::AhoCorasick;
+use anyhow::{Context as _, Result};
+use blink_manager::BlinkManager;
+use client::{ClickhouseEvent, Client, Collaborator, ParticipantIndex, TelemetrySettings};
+use clock::ReplicaId;
+use collections::{BTreeMap, HashMap, HashSet, VecDeque};
+use copilot::Copilot;
+pub use display_map::DisplayPoint;
+use display_map::*;
+pub use editor_settings::EditorSettings;
+pub use element::{
+    Cursor, EditorElement, HighlightedRange, HighlightedRangeLine, LineWithInvisibles,
+};
+use futures::FutureExt;
+use fuzzy::{StringMatch, StringMatchCandidate};
+use gpui::{
+    div, AnyElement, AppContext, BackgroundExecutor, Context, Div, Element, EventEmitter,
+    FocusHandle, Hsla, Model, Pixels, Render, Subscription, Task, TextStyle, View, ViewContext,
+    VisualContext, WeakView, WindowContext,
+};
+use highlight_matching_bracket::refresh_matching_bracket_highlights;
+use hover_popover::{hide_hover, HoverState};
+use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
+pub use items::MAX_TAB_TITLE_LEN;
+use itertools::Itertools;
+pub use language::{char_kind, CharKind};
+use language::{
+    language_settings::{self, all_language_settings, InlayHintSettings},
+    point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, Completion, CursorShape,
+    Diagnostic, Language, LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection,
+    SelectionGoal, TransactionId,
+};
+use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
+use lsp::{Documentation, LanguageServerId};
+pub use multi_buffer::{
+    Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
+    ToPoint,
+};
+use ordered_float::OrderedFloat;
+use parking_lot::RwLock;
+use project::{FormatTrigger, Location, Project};
+use rpc::proto::*;
+use scroll::{
+    autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
+};
+use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
+use serde::{Deserialize, Serialize};
+use settings::{Settings, SettingsStore};
+use std::{
+    any::TypeId,
+    borrow::Cow,
+    cmp::{self, Reverse},
+    ops::{ControlFlow, Deref, DerefMut, Range},
+    path::Path,
+    sync::Arc,
+    time::{Duration, Instant},
+};
+pub use sum_tree::Bias;
+use sum_tree::TreeMap;
+use text::Rope;
+use theme::ThemeColors;
+use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
+use workspace::{ItemNavHistory, SplitDirection, ViewId, Workspace};
+
+const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
+const MAX_LINE_LEN: usize = 1024;
+const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
+const MAX_SELECTION_HISTORY_LEN: usize = 1024;
+const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
+pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
+pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
+
+pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
+
+// pub fn render_parsed_markdown<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, &region.pair.start) {
+    //                             if buffer.contains_str_at(range.end, &region.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 = &regions[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)
+    }
+}

crates/editor2/src/editor_settings.rs 🔗

@@ -0,0 +1,62 @@
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use settings::Settings;
+
+#[derive(Deserialize)]
+pub struct EditorSettings {
+    pub cursor_blink: bool,
+    pub hover_popover_enabled: bool,
+    pub show_completions_on_input: bool,
+    pub show_completion_documentation: bool,
+    pub use_on_type_format: bool,
+    pub scrollbar: Scrollbar,
+    pub relative_line_numbers: bool,
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+pub struct Scrollbar {
+    pub show: ShowScrollbar,
+    pub git_diff: bool,
+    pub selections: bool,
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[serde(rename_all = "snake_case")]
+pub enum ShowScrollbar {
+    Auto,
+    System,
+    Always,
+    Never,
+}
+
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+pub struct EditorSettingsContent {
+    pub cursor_blink: Option<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)
+    }
+}

crates/editor2/src/editor_tests.rs 🔗

@@ -0,0 +1,8191 @@
+// use super::*;
+// use crate::{
+//     scroll::scroll_amount::ScrollAmount,
+//     test::{
+//         assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
+//         editor_test_context::EditorTestContext, select_ranges,
+//     },
+//     JoinLines,
+// };
+// use drag_and_drop::DragAndDrop;
+// use futures::StreamExt;
+// use gpui::{
+//     executor::Deterministic,
+//     geometry::{rect::RectF, vector::vec2f},
+//     platform::{WindowBounds, WindowOptions},
+//     serde_json::{self, json},
+//     TestAppContext,
+// };
+// use indoc::indoc;
+// use language::{
+//     language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
+//     BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry,
+//     Override, Point,
+// };
+// use parking_lot::Mutex;
+// use project::project_settings::{LspSettings, ProjectSettings};
+// use project::FakeFs;
+// use std::sync::atomic;
+// use std::sync::atomic::AtomicUsize;
+// use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
+// use unindent::Unindent;
+// use util::{
+//     assert_set_eq,
+//     test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
+// };
+// use workspace::{
+//     item::{FollowableItem, Item, ItemHandle},
+//     NavigationEntry, ViewId,
+// };
+
+// #[gpui::test]
+// fn test_edit_events(cx: &mut TestAppContext) {
+//     init_test(cx, |_| {});
+
+//     let buffer = cx.add_model(|cx| {
+//         let mut buffer = language::Buffer::new(0, cx.model_id() as u64, "123456");
+//         buffer.set_group_interval(Duration::from_secs(1));
+//         buffer
+//     });
+
+//     let events = Rc::new(RefCell::new(Vec::new()));
+//     let editor1 = cx
+//         .add_window({
+//             let events = events.clone();
+//             |cx| {
+//                 cx.subscribe(&cx.handle(), move |_, _, event, _| {
+//                     if matches!(
+//                         event,
+//                         Event::Edited | Event::BufferEdited | Event::DirtyChanged
+//                     ) {
+//                         events.borrow_mut().push(("editor1", event.clone()));
+//                     }
+//                 })
+//                 .detach();
+//                 Editor::for_buffer(buffer.clone(), None, cx)
+//             }
+//         })
+//         .root(cx);
+//     let editor2 = cx
+//         .add_window({
+//             let events = events.clone();
+//             |cx| {
+//                 cx.subscribe(&cx.handle(), move |_, _, event, _| {
+//                     if matches!(
+//                         event,
+//                         Event::Edited | Event::BufferEdited | Event::DirtyChanged
+//                     ) {
+//                         events.borrow_mut().push(("editor2", event.clone()));
+//                     }
+//                 })
+//                 .detach();
+//                 Editor::for_buffer(buffer.clone(), None, cx)
+//             }
+//         })
+//         .root(cx);
+//     assert_eq!(mem::take(&mut *events.borrow_mut()), []);
+
+//     // Mutating editor 1 will emit an `Edited` event only for that editor.
+//     editor1.update(cx, |editor, cx| editor.insert("X", cx));
+//     assert_eq!(
+//         mem::take(&mut *events.borrow_mut()),
+//         [
+//             ("editor1", Event::Edited),
+//             ("editor1", Event::BufferEdited),
+//             ("editor2", Event::BufferEdited),
+//             ("editor1", Event::DirtyChanged),
+//             ("editor2", Event::DirtyChanged)
+//         ]
+//     );
+
+//     // Mutating editor 2 will emit an `Edited` event only for that editor.
+//     editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
+//     assert_eq!(
+//         mem::take(&mut *events.borrow_mut()),
+//         [
+//             ("editor2", Event::Edited),
+//             ("editor1", Event::BufferEdited),
+//             ("editor2", Event::BufferEdited),
+//         ]
+//     );
+
+//     // Undoing on editor 1 will emit an `Edited` event only for that editor.
+//     editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
+//     assert_eq!(
+//         mem::take(&mut *events.borrow_mut()),
+//         [
+//             ("editor1", Event::Edited),
+//             ("editor1", Event::BufferEdited),
+//             ("editor2", Event::BufferEdited),
+//             ("editor1", Event::DirtyChanged),
+//             ("editor2", Event::DirtyChanged),
+//         ]
+//     );
+
+//     // Redoing on editor 1 will emit an `Edited` event only for that editor.
+//     editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
+//     assert_eq!(
+//         mem::take(&mut *events.borrow_mut()),
+//         [
+//             ("editor1", Event::Edited),
+//             ("editor1", Event::BufferEdited),
+//             ("editor2", Event::BufferEdited),
+//             ("editor1", Event::DirtyChanged),
+//             ("editor2", Event::DirtyChanged),
+//         ]
+//     );
+
+//     // Undoing on editor 2 will emit an `Edited` event only for that editor.
+//     editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
+//     assert_eq!(
+//         mem::take(&mut *events.borrow_mut()),
+//         [
+//             ("editor2", Event::Edited),
+//             ("editor1", Event::BufferEdited),
+//             ("editor2", Event::BufferEdited),
+//             ("editor1", Event::DirtyChanged),
+//             ("editor2", Event::DirtyChanged),
+//         ]
+//     );
+
+//     // Redoing on editor 2 will emit an `Edited` event only for that editor.
+//     editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
+//     assert_eq!(
+//         mem::take(&mut *events.borrow_mut()),
+//         [
+//             ("editor2", Event::Edited),
+//             ("editor1", Event::BufferEdited),
+//             ("editor2", Event::BufferEdited),
+//             ("editor1", Event::DirtyChanged),
+//             ("editor2", Event::DirtyChanged),
+//         ]
+//     );
+
+//     // No event is emitted when the mutation is a no-op.
+//     editor2.update(cx, |editor, cx| {
+//         editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
+
+//         editor.backspace(&Backspace, cx);
+//     });
+//     assert_eq!(mem::take(&mut *events.borrow_mut()), []);
+// }
+
+// #[gpui::test]
+// fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
+//     init_test(cx, |_| {});
+
+//     let mut now = Instant::now();
+//     let buffer = cx.add_model(|cx| language::Buffer::new(0, cx.model_id() as u64, "123456"));
+//     let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval());
+//     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+//     let editor = cx
+//         .add_window(|cx| build_editor(buffer.clone(), cx))
+//         .root(cx);
+
+//     editor.update(cx, |editor, cx| {
+//         editor.start_transaction_at(now, cx);
+//         editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
+
+//         editor.insert("cd", cx);
+//         editor.end_transaction_at(now, cx);
+//         assert_eq!(editor.text(cx), "12cd56");
+//         assert_eq!(editor.selections.ranges(cx), vec![4..4]);
+
+//         editor.start_transaction_at(now, cx);
+//         editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
+//         editor.insert("e", cx);
+//         editor.end_transaction_at(now, cx);
+//         assert_eq!(editor.text(cx), "12cde6");
+//         assert_eq!(editor.selections.ranges(cx), vec![5..5]);
+
+//         now += group_interval + Duration::from_millis(1);
+//         editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
+
+//         // Simulate an edit in another editor
+//         buffer.update(cx, |buffer, cx| {
+//             buffer.start_transaction_at(now, cx);
+//             buffer.edit([(0..1, "a")], None, cx);
+//             buffer.edit([(1..1, "b")], None, cx);
+//             buffer.end_transaction_at(now, cx);
+//         });
+
+//         assert_eq!(editor.text(cx), "ab2cde6");
+//         assert_eq!(editor.selections.ranges(cx), vec![3..3]);
+
+//         // Last transaction happened past the group interval in a different editor.
+//         // Undo it individually and don't restore selections.
+//         editor.undo(&Undo, cx);
+//         assert_eq!(editor.text(cx), "12cde6");
+//         assert_eq!(editor.selections.ranges(cx), vec![2..2]);
+
+//         // First two transactions happened within the group interval in this editor.
+//         // Undo them together and restore selections.
+//         editor.undo(&Undo, cx);
+//         editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
+//         assert_eq!(editor.text(cx), "123456");
+//         assert_eq!(editor.selections.ranges(cx), vec![0..0]);
+
+//         // Redo the first two transactions together.
+//         editor.redo(&Redo, cx);
+//         assert_eq!(editor.text(cx), "12cde6");
+//         assert_eq!(editor.selections.ranges(cx), vec![5..5]);
+
+//         // Redo the last transaction on its own.
+//         editor.redo(&Redo, cx);
+//         assert_eq!(editor.text(cx), "ab2cde6");
+//         assert_eq!(editor.selections.ranges(cx), vec![6..6]);
+
+//         // Test empty transactions.
+//         editor.start_transaction_at(now, cx);
+//         editor.end_transaction_at(now, cx);
+//         editor.undo(&Undo, cx);
+//         assert_eq!(editor.text(cx), "12cde6");
+//     });
+// }
+
+// #[gpui::test]
+// fn test_ime_composition(cx: &mut TestAppContext) {
+//     init_test(cx, |_| {});
+
+//     let buffer = cx.add_model(|cx| {
+//         let mut buffer = language::Buffer::new(0, cx.model_id() as u64, "abcde");
+//         // Ensure automatic grouping doesn't occur.
+//         buffer.set_group_interval(Duration::ZERO);
+//         buffer
+//     });
+
+//     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+//     cx.add_window(|cx| {
+//         let mut editor = build_editor(buffer.clone(), cx);
+
+//         // Start a new IME composition.
+//         editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
+//         editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
+//         editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
+//         assert_eq!(editor.text(cx), "äbcde");
+//         assert_eq!(
+//             editor.marked_text_ranges(cx),
+//             Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
+//         );
+
+//         // Finalize IME composition.
+//         editor.replace_text_in_range(None, "ā", cx);
+//         assert_eq!(editor.text(cx), "ābcde");
+//         assert_eq!(editor.marked_text_ranges(cx), None);
+
+//         // IME composition edits are grouped and are undone/redone at once.
+//         editor.undo(&Default::default(), cx);
+//         assert_eq!(editor.text(cx), "abcde");
+//         assert_eq!(editor.marked_text_ranges(cx), None);
+//         editor.redo(&Default::default(), cx);
+//         assert_eq!(editor.text(cx), "ābcde");
+//         assert_eq!(editor.marked_text_ranges(cx), None);
+
+//         // Start a new IME composition.
+//         editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
+//         assert_eq!(
+//             editor.marked_text_ranges(cx),
+//             Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
+//         );
+
+//         // Undoing during an IME composition cancels it.
+//         editor.undo(&Default::default(), cx);
+//         assert_eq!(editor.text(cx), "ābcde");
+//         assert_eq!(editor.marked_text_ranges(cx), None);
+
+//         // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
+//         editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
+//         assert_eq!(editor.text(cx), "ābcdè");
+//         assert_eq!(
+//             editor.marked_text_ranges(cx),
+//             Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
+//         );
+
+//         // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
+//         editor.replace_text_in_range(Some(4..999), "ę", cx);
+//         assert_eq!(editor.text(cx), "ābcdę");
+//         assert_eq!(editor.marked_text_ranges(cx), None);
+
+//         // Start a new IME composition with multiple cursors.
+//         editor.change_selections(None, cx, |s| {
+//             s.select_ranges([
+//                 OffsetUtf16(1)..OffsetUtf16(1),
+//                 OffsetUtf16(3)..OffsetUtf16(3),
+//                 OffsetUtf16(5)..OffsetUtf16(5),
+//             ])
+//         });
+//         editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
+//         assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
+//         assert_eq!(
+//             editor.marked_text_ranges(cx),
+//             Some(vec![
+//                 OffsetUtf16(0)..OffsetUtf16(3),
+//                 OffsetUtf16(4)..OffsetUtf16(7),
+//                 OffsetUtf16(8)..OffsetUtf16(11)
+//             ])
+//         );
+
+//         // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
+//         editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
+//         assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
+//         assert_eq!(
+//             editor.marked_text_ranges(cx),
+//             Some(vec![
+//                 OffsetUtf16(1)..OffsetUtf16(2),
+//                 OffsetUtf16(5)..OffsetUtf16(6),
+//                 OffsetUtf16(9)..OffsetUtf16(10)
+//             ])
+//         );
+
+//         // Finalize IME composition with multiple cursors.
+//         editor.replace_text_in_range(Some(9..10), "2", cx);
+//         assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
+//         assert_eq!(editor.marked_text_ranges(cx), None);
+
+//         editor
+//     });
+// }
+
+// #[gpui::test]
+// fn test_selection_with_mouse(cx: &mut TestAppContext) {
+//     init_test(cx, |_| {});
+
+//     let editor = cx
+//         .add_window(|cx| {
+//             let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
+//             build_editor(buffer, cx)
+//         })
+//         .root(cx);
+//     editor.update(cx, |view, cx| {
+//         view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
+//     });
+//     assert_eq!(
+//         editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
+//         [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
+//     );
+
+//     editor.update(cx, |view, cx| {
+//         view.update_selection(DisplayPoint::new(3, 3), 0, Point<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);
+// }

crates/editor2/src/element.rs 🔗

@@ -0,0 +1,3488 @@
+use super::{
+    display_map::ToDisplayPoint, DisplayPoint, Editor, EditorSnapshot, ToPoint, MAX_LINE_LEN,
+};
+use crate::{
+    display_map::{BlockStyle, DisplaySnapshot},
+    EditorStyle,
+};
+use anyhow::Result;
+use gpui::{
+    black, px, relative, AnyElement, Bounds, Element, Hsla, Line, Pixels, Size, Style, TextRun,
+    TextSystem,
+};
+use language::{CursorShape, Selection};
+use smallvec::SmallVec;
+use std::{ops::Range, sync::Arc};
+use sum_tree::Bias;
+
+enum FoldMarkers {}
+
+struct SelectionLayout {
+    head: DisplayPoint,
+    cursor_shape: CursorShape,
+    is_newest: bool,
+    is_local: bool,
+    range: Range<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()
+//     }
+// }

crates/editor2/src/git.rs 🔗

@@ -0,0 +1,282 @@
+use std::ops::Range;
+
+use git::diff::{DiffHunk, DiffHunkStatus};
+use language::Point;
+
+use crate::{
+    display_map::{DisplaySnapshot, ToDisplayPoint},
+    AnchorRangeExt,
+};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum DisplayDiffHunk {
+    Folded {
+        display_row: u32,
+    },
+
+    Unfolded {
+        display_row_range: Range<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(),
+//         );
+//     }
+// }

crates/editor2/src/highlight_matching_bracket.rs 🔗

@@ -0,0 +1,138 @@
+use gpui::ViewContext;
+
+use crate::{Editor, RangeToAnchorExt};
+
+enum MatchingBracketHighlight {}
+
+pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
+    // 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);
+//             }
+//         "#});
+//     }
+// }

crates/editor2/src/hover_popover.rs 🔗

@@ -0,0 +1,1331 @@
+use crate::{
+    display_map::InlayOffset,
+    link_go_to_definition::{InlayHighlight, RangeInEditor},
+    Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle,
+    ExcerptId, RangeToAnchorExt,
+};
+use futures::FutureExt;
+use gpui::{AnyElement, AppContext, Model, Task, ViewContext, WeakView};
+use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown};
+use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};
+use settings::Settings;
+use std::{ops::Range, sync::Arc, time::Duration};
+use util::TryFutureExt;
+use workspace::Workspace;
+
+pub const HOVER_DELAY_MILLIS: u64 = 350;
+pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
+
+pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.;
+pub const MIN_POPOVER_LINE_HEIGHT: f32 = 4.;
+pub const HOVER_POPOVER_GAP: f32 = 10.;
+
+// actions!(editor, [Hover]);
+
+pub fn init(cx: &mut AppContext) {
+    // cx.add_action(hover);
+}
+
+// todo!()
+// /// Bindable action which uses the most recent selection head to trigger a hover
+// pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<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![HoverBlock {
+//                         text: "one [two](https://the-url) three".to_string(),
+//                         kind: HoverBlockKind::Markdown,
+//                     }],
+//                     expected_marked_text: "one «two» three".to_string(),
+//                     expected_styles: vec![HighlightStyle {
+//                         underline: Some(Underline {
+//                             thickness: 1.0.into(),
+//                             ..Default::default()
+//                         }),
+//                         ..Default::default()
+//                     }],
+//                 },
+//                 // Lists
+//                 Row {
+//                     blocks: vec![HoverBlock {
+//                         text: "
+//                             lists:
+//                             * one
+//                                 - a
+//                                 - b
+//                             * two
+//                                 - [c](https://the-url)
+//                                 - d"
+//                         .unindent(),
+//                         kind: HoverBlockKind::Markdown,
+//                     }],
+//                     expected_marked_text: "
+//                         lists:
+//                         - one
+//                           - a
+//                           - b
+//                         - two
+//                           - «c»
+//                           - d"
+//                     .unindent(),
+//                     expected_styles: vec![HighlightStyle {
+//                         underline: Some(Underline {
+//                             thickness: 1.0.into(),
+//                             ..Default::default()
+//                         }),
+//                         ..Default::default()
+//                     }],
+//                 },
+//                 // Multi-paragraph list items
+//                 Row {
+//                     blocks: vec![HoverBlock {
+//                         text: "
+//                             * one two
+//                               three
+
+//                             * four five
+//                                 * six seven
+//                                   eight
+
+//                                   nine
+//                                 * ten
+//                             * six"
+//                             .unindent(),
+//                         kind: HoverBlockKind::Markdown,
+//                     }],
+//                     expected_marked_text: "
+//                         - one two three
+//                         - four five
+//                           - six seven eight
+
+//                             nine
+//                           - ten
+//                         - six"
+//                         .unindent(),
+//                     expected_styles: vec![HighlightStyle {
+//                         underline: Some(Underline {
+//                             thickness: 1.0.into(),
+//                             ..Default::default()
+//                         }),
+//                         ..Default::default()
+//                     }],
+//                 },
+//             ];
+
+//             for Row {
+//                 blocks,
+//                 expected_marked_text,
+//                 expected_styles,
+//             } in &rows[0..]
+//             {
+//                 let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));
+
+//                 let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);
+//                 let expected_highlights = ranges
+//                     .into_iter()
+//                     .zip(expected_styles.iter().cloned())
+//                     .collect::<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"
+//             );
+//         });
+//     }
+// }

crates/editor2/src/inlay_hint_cache.rs 🔗

@@ -0,0 +1,3355 @@
+use std::{
+    cmp,
+    ops::{ControlFlow, Range},
+    sync::Arc,
+    time::Duration,
+};
+
+use crate::{
+    display_map::Inlay, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, MultiBufferSnapshot,
+};
+use anyhow::Context;
+use clock::Global;
+use futures::future;
+use gpui::{Model, ModelContext, Task, ViewContext};
+use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot};
+use parking_lot::RwLock;
+use project::{InlayHint, ResolveState};
+
+use collections::{hash_map, HashMap, HashSet};
+use language::language_settings::InlayHintSettings;
+use smol::lock::Semaphore;
+use sum_tree::Bias;
+use text::{ToOffset, ToPoint};
+use util::post_inc;
+
+pub struct InlayHintCache {
+    hints: HashMap<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
+//     }
+// }

crates/editor2/src/items.rs 🔗

@@ -0,0 +1,1339 @@
+use crate::{
+    display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition,
+    movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor,
+    Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
+};
+use anyhow::{anyhow, Context, Result};
+use collections::HashSet;
+use futures::future::try_join_all;
+use gpui::{
+    div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, FocusHandle, Model,
+    ParentElement, Pixels, SharedString, Styled, Subscription, Task, View, ViewContext,
+    VisualContext, WeakView,
+};
+use language::{
+    proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
+    SelectionGoal,
+};
+use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
+use rpc::proto::{self, update_view, PeerId};
+use smallvec::SmallVec;
+use std::{
+    borrow::Cow,
+    cmp::{self, Ordering},
+    iter,
+    ops::Range,
+    path::{Path, PathBuf},
+    sync::Arc,
+};
+use text::Selection;
+use theme::{ActiveTheme, ThemeVariant};
+use util::{paths::PathExt, ResultExt, TryFutureExt};
+use workspace::item::{BreadcrumbText, FollowableItemHandle};
+use workspace::{
+    item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
+    searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
+    ItemId, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
+};
+
+pub const MAX_TAB_TITLE_LEN: usize = 24;
+
+impl FollowableItem for Editor {
+    fn remote_id(&self) -> Option<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(&current_index_position, &buffer)
+                    .is_gt()
+                {
+                    count = count - 1
+                }
+
+                (current_index + count) % matches.len()
+            }
+            Direction::Prev => {
+                if matches[current_index]
+                    .end
+                    .cmp(&current_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;
+//             }
+//         "});
+//     }
+// }

crates/editor2/src/mouse_context_menu.rs 🔗

@@ -0,0 +1,94 @@
+use crate::{DisplayPoint, Editor, EditorMode, SelectMode};
+use gpui::{Pixels, Point, ViewContext};
+
+pub fn deploy_context_menu(
+    editor: &mut Editor,
+    position: Point<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()));
+//     }
+// }

crates/editor2/src/movement.rs 🔗

@@ -0,0 +1,933 @@
+use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
+use crate::{char_kind, CharKind, EditorStyle, ToOffset, ToPoint};
+use gpui::{px, TextSystem};
+use language::Point;
+use serde::de::IntoDeserializer;
+use std::ops::Range;
+
+#[derive(Debug, PartialEq)]
+pub enum FindRange {
+    SingleLine,
+    MultiLine,
+}
+
+/// TextLayoutDetails encompasses everything we need to move vertically
+/// taking into account variable width characters.
+pub struct TextLayoutDetails {
+    pub text_system: TextSystem,
+    pub editor_style: EditorStyle,
+}
+
+pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
+    if point.column() > 0 {
+        *point.column_mut() -= 1;
+    } else if point.row() > 0 {
+        *point.row_mut() -= 1;
+        *point.column_mut() = map.line_len(point.row());
+    }
+    map.clip_point(point, Bias::Left)
+}
+
+pub fn saturating_left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
+    if point.column() > 0 {
+        *point.column_mut() -= 1;
+    }
+    map.clip_point(point, Bias::Left)
+}
+
+pub fn right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
+    let max_column = map.line_len(point.row());
+    if point.column() < max_column {
+        *point.column_mut() += 1;
+    } else if point.row() < map.max_point().row() {
+        *point.row_mut() += 1;
+        *point.column_mut() = 0;
+    }
+    map.clip_point(point, Bias::Right)
+}
+
+pub fn saturating_right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
+    *point.column_mut() += 1;
+    map.clip_point(point, Bias::Right)
+}
+
+pub fn up(
+    map: &DisplaySnapshot,
+    start: DisplayPoint,
+    goal: SelectionGoal,
+    preserve_column_at_start: bool,
+    text_layout_details: &TextLayoutDetails,
+) -> (DisplayPoint, SelectionGoal) {
+    up_by_rows(
+        map,
+        start,
+        1,
+        goal,
+        preserve_column_at_start,
+        text_layout_details,
+    )
+}
+
+pub fn down(
+    map: &DisplaySnapshot,
+    start: DisplayPoint,
+    goal: SelectionGoal,
+    preserve_column_at_end: bool,
+    text_layout_details: &TextLayoutDetails,
+) -> (DisplayPoint, SelectionGoal) {
+    down_by_rows(
+        map,
+        start,
+        1,
+        goal,
+        preserve_column_at_end,
+        text_layout_details,
+    )
+}
+
+pub fn up_by_rows(
+    map: &DisplaySnapshot,
+    start: DisplayPoint,
+    row_count: u32,
+    goal: SelectionGoal,
+    preserve_column_at_start: bool,
+    text_layout_details: &TextLayoutDetails,
+) -> (DisplayPoint, SelectionGoal) {
+    let mut goal_x = match goal {
+        SelectionGoal::HorizontalPosition(x) => x.into(), // todo!("Can the fields in SelectionGoal by Pixels? We should extract a geometry crate and depend on that.")
+        SelectionGoal::WrappedHorizontalPosition((_, x)) => x.into(),
+        SelectionGoal::HorizontalRange { end, .. } => end.into(),
+        _ => map.x_for_point(start, text_layout_details),
+    };
+
+    let prev_row = start.row().saturating_sub(row_count);
+    let mut point = map.clip_point(
+        DisplayPoint::new(prev_row, map.line_len(prev_row)),
+        Bias::Left,
+    );
+    if point.row() < start.row() {
+        *point.column_mut() = map.column_for_x(point.row(), goal_x, text_layout_details)
+    } else if preserve_column_at_start {
+        return (start, goal);
+    } else {
+        point = DisplayPoint::new(0, 0);
+        goal_x = px(0.);
+    }
+
+    let mut clipped_point = map.clip_point(point, Bias::Left);
+    if clipped_point.row() < point.row() {
+        clipped_point = map.clip_point(point, Bias::Right);
+    }
+    (
+        clipped_point,
+        SelectionGoal::HorizontalPosition(goal_x.into()),
+    )
+}
+
+pub fn down_by_rows(
+    map: &DisplaySnapshot,
+    start: DisplayPoint,
+    row_count: u32,
+    goal: SelectionGoal,
+    preserve_column_at_end: bool,
+    text_layout_details: &TextLayoutDetails,
+) -> (DisplayPoint, SelectionGoal) {
+    let mut goal_x = match goal {
+        SelectionGoal::HorizontalPosition(x) => x.into(),
+        SelectionGoal::WrappedHorizontalPosition((_, x)) => x.into(),
+        SelectionGoal::HorizontalRange { end, .. } => end.into(),
+        _ => map.x_for_point(start, text_layout_details),
+    };
+
+    let new_row = start.row() + row_count;
+    let mut point = map.clip_point(DisplayPoint::new(new_row, 0), Bias::Right);
+    if point.row() > start.row() {
+        *point.column_mut() = map.column_for_x(point.row(), goal_x, text_layout_details)
+    } else if preserve_column_at_end {
+        return (start, goal);
+    } else {
+        point = map.max_point();
+        goal_x = map.x_for_point(point, text_layout_details)
+    }
+
+    let mut clipped_point = map.clip_point(point, Bias::Right);
+    if clipped_point.row() > point.row() {
+        clipped_point = map.clip_point(point, Bias::Left);
+    }
+    (
+        clipped_point,
+        SelectionGoal::HorizontalPosition(goal_x.into()),
+    )
+}
+
+pub fn line_beginning(
+    map: &DisplaySnapshot,
+    display_point: DisplayPoint,
+    stop_at_soft_boundaries: bool,
+) -> DisplayPoint {
+    let point = display_point.to_point(map);
+    let soft_line_start = map.clip_point(DisplayPoint::new(display_point.row(), 0), Bias::Right);
+    let line_start = map.prev_line_boundary(point).1;
+
+    if stop_at_soft_boundaries && display_point != soft_line_start {
+        soft_line_start
+    } else {
+        line_start
+    }
+}
+
+pub fn indented_line_beginning(
+    map: &DisplaySnapshot,
+    display_point: DisplayPoint,
+    stop_at_soft_boundaries: bool,
+) -> DisplayPoint {
+    let point = display_point.to_point(map);
+    let soft_line_start = map.clip_point(DisplayPoint::new(display_point.row(), 0), Bias::Right);
+    let indent_start = Point::new(
+        point.row,
+        map.buffer_snapshot.indent_size_for_line(point.row).len,
+    )
+    .to_display_point(map);
+    let line_start = map.prev_line_boundary(point).1;
+
+    if stop_at_soft_boundaries && soft_line_start > indent_start && display_point != soft_line_start
+    {
+        soft_line_start
+    } else if stop_at_soft_boundaries && display_point != indent_start {
+        indent_start
+    } else {
+        line_start
+    }
+}
+
+pub fn line_end(
+    map: &DisplaySnapshot,
+    display_point: DisplayPoint,
+    stop_at_soft_boundaries: bool,
+) -> DisplayPoint {
+    let soft_line_end = map.clip_point(
+        DisplayPoint::new(display_point.row(), map.line_len(display_point.row())),
+        Bias::Left,
+    );
+    if stop_at_soft_boundaries && display_point != soft_line_end {
+        soft_line_end
+    } else {
+        map.next_line_boundary(display_point.to_point(map)).1
+    }
+}
+
+pub fn previous_word_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
+    let raw_point = point.to_point(map);
+    let scope = map.buffer_snapshot.language_scope_at(raw_point);
+
+    find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| {
+        (char_kind(&scope, left) != char_kind(&scope, right) && !right.is_whitespace())
+            || left == '\n'
+    })
+}
+
+pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
+    let raw_point = point.to_point(map);
+    let scope = map.buffer_snapshot.language_scope_at(raw_point);
+
+    find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| {
+        let is_word_start =
+            char_kind(&scope, left) != char_kind(&scope, right) && !right.is_whitespace();
+        let is_subword_start =
+            left == '_' && right != '_' || left.is_lowercase() && right.is_uppercase();
+        is_word_start || is_subword_start || left == '\n'
+    })
+}
+
+pub fn next_word_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
+    let raw_point = point.to_point(map);
+    let scope = map.buffer_snapshot.language_scope_at(raw_point);
+
+    find_boundary(map, point, FindRange::MultiLine, |left, right| {
+        (char_kind(&scope, left) != char_kind(&scope, right) && !left.is_whitespace())
+            || right == '\n'
+    })
+}
+
+pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
+    let raw_point = point.to_point(map);
+    let scope = map.buffer_snapshot.language_scope_at(raw_point);
+
+    find_boundary(map, point, FindRange::MultiLine, |left, right| {
+        let is_word_end =
+            (char_kind(&scope, left) != char_kind(&scope, right)) && !left.is_whitespace();
+        let is_subword_end =
+            left != '_' && right == '_' || left.is_lowercase() && right.is_uppercase();
+        is_word_end || is_subword_end || right == '\n'
+    })
+}
+
+pub fn start_of_paragraph(
+    map: &DisplaySnapshot,
+    display_point: DisplayPoint,
+    mut count: usize,
+) -> DisplayPoint {
+    let point = display_point.to_point(map);
+    if point.row == 0 {
+        return DisplayPoint::zero();
+    }
+
+    let mut found_non_blank_line = false;
+    for row in (0..point.row + 1).rev() {
+        let blank = map.buffer_snapshot.is_line_blank(row);
+        if found_non_blank_line && blank {
+            if count <= 1 {
+                return Point::new(row, 0).to_display_point(map);
+            }
+            count -= 1;
+            found_non_blank_line = false;
+        }
+
+        found_non_blank_line |= !blank;
+    }
+
+    DisplayPoint::zero()
+}
+
+pub fn end_of_paragraph(
+    map: &DisplaySnapshot,
+    display_point: DisplayPoint,
+    mut count: usize,
+) -> DisplayPoint {
+    let point = display_point.to_point(map);
+    if point.row == map.max_buffer_row() {
+        return map.max_point();
+    }
+
+    let mut found_non_blank_line = false;
+    for row in point.row..map.max_buffer_row() + 1 {
+        let blank = map.buffer_snapshot.is_line_blank(row);
+        if found_non_blank_line && blank {
+            if count <= 1 {
+                return Point::new(row, 0).to_display_point(map);
+            }
+            count -= 1;
+            found_non_blank_line = false;
+        }
+
+        found_non_blank_line |= !blank;
+    }
+
+    map.max_point()
+}
+
+/// Scans for a boundary preceding the given start point `from` until a boundary is found,
+/// indicated by the given predicate returning true.
+/// The predicate is called with the character to the left and right of the candidate boundary location.
+/// If FindRange::SingleLine is specified and no boundary is found before the start of the current line, the start of the current line will be returned.
+pub fn find_preceding_boundary(
+    map: &DisplaySnapshot,
+    from: DisplayPoint,
+    find_range: FindRange,
+    mut is_boundary: impl FnMut(char, char) -> bool,
+) -> DisplayPoint {
+    let mut prev_ch = None;
+    let mut offset = from.to_point(map).to_offset(&map.buffer_snapshot);
+
+    for ch in map.buffer_snapshot.reversed_chars_at(offset) {
+        if find_range == FindRange::SingleLine && ch == '\n' {
+            break;
+        }
+        if let Some(prev_ch) = prev_ch {
+            if is_boundary(ch, prev_ch) {
+                break;
+            }
+        }
+
+        offset -= ch.len_utf8();
+        prev_ch = Some(ch);
+    }
+
+    map.clip_point(offset.to_display_point(map), Bias::Left)
+}
+
+/// Scans for a boundary following the given start point until a boundary is found, indicated by the
+/// given predicate returning true. The predicate is called with the character to the left and right
+/// of the candidate boundary location, and will be called with `\n` characters indicating the start
+/// or end of a line.
+pub fn find_boundary(
+    map: &DisplaySnapshot,
+    from: DisplayPoint,
+    find_range: FindRange,
+    mut is_boundary: impl FnMut(char, char) -> bool,
+) -> DisplayPoint {
+    let mut offset = from.to_offset(&map, Bias::Right);
+    let mut prev_ch = None;
+
+    for ch in map.buffer_snapshot.chars_at(offset) {
+        if find_range == FindRange::SingleLine && ch == '\n' {
+            break;
+        }
+        if let Some(prev_ch) = prev_ch {
+            if is_boundary(prev_ch, ch) {
+                break;
+            }
+        }
+
+        offset += ch.len_utf8();
+        prev_ch = Some(ch);
+    }
+    map.clip_point(offset.to_display_point(map), Bias::Right)
+}
+
+pub fn chars_after(
+    map: &DisplaySnapshot,
+    mut offset: usize,
+) -> impl Iterator<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);
+//     }
+// }

crates/editor2/src/persistence.rs 🔗

@@ -0,0 +1,83 @@
+use std::path::PathBuf;
+
+use db::sqlez_macros::sql;
+use db::{define_connection, query};
+
+use workspace::{ItemId, WorkspaceDb, WorkspaceId};
+
+define_connection!(
+    // Current schema shape using pseudo-rust syntax:
+    // editors(
+    //   item_id: usize,
+    //   workspace_id: usize,
+    //   path: PathBuf,
+    //   scroll_top_row: usize,
+    //   scroll_vertical_offset: f32,
+    //   scroll_horizontal_offset: f32,
+    // )
+    pub static ref DB: EditorDb<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
+        }
+    }
+}

crates/editor2/src/scroll.rs 🔗

@@ -0,0 +1,448 @@
+pub mod actions;
+pub mod autoscroll;
+pub mod scroll_amount;
+
+use crate::{
+    display_map::{DisplaySnapshot, ToDisplayPoint},
+    hover_popover::hide_hover,
+    persistence::DB,
+    Anchor, DisplayPoint, Editor, EditorMode, Event, InlayHintRefreshReason, MultiBufferSnapshot,
+    ToPoint,
+};
+use gpui::{point, px, AppContext, Entity, Pixels, Styled, Task, ViewContext};
+use language::{Bias, Point};
+use std::{
+    cmp::Ordering,
+    time::{Duration, Instant},
+};
+use util::ResultExt;
+use workspace::{ItemId, WorkspaceId};
+
+use self::{
+    autoscroll::{Autoscroll, AutoscrollStrategy},
+    scroll_amount::ScrollAmount,
+};
+
+pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28);
+pub const VERTICAL_SCROLL_MARGIN: f32 = 3.;
+const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
+
+#[derive(Default)]
+pub struct ScrollbarAutoHide(pub bool);
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct ScrollAnchor {
+    pub offset: gpui::Point<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);
+        }
+    }
+}

crates/editor2/src/scroll/actions.rs 🔗

@@ -0,0 +1,148 @@
+use gpui::AppContext;
+
+// actions!(
+//     editor,
+//     [
+//         LineDown,
+//         LineUp,
+//         HalfPageDown,
+//         HalfPageUp,
+//         PageDown,
+//         PageUp,
+//         NextScreen,
+//         ScrollCursorTop,
+//         ScrollCursorCenter,
+//         ScrollCursorBottom,
+//     ]
+// );
+
+pub fn init(cx: &mut AppContext) {
+    // todo!()
+    // cx.add_action(Editor::next_screen);
+    // cx.add_action(Editor::scroll_cursor_top);
+    // cx.add_action(Editor::scroll_cursor_center);
+    // cx.add_action(Editor::scroll_cursor_bottom);
+    // cx.add_action(|this: &mut Editor, _: &LineDown, cx| {
+    //     this.scroll_screen(&ScrollAmount::Line(1.), cx)
+    // });
+    // cx.add_action(|this: &mut Editor, _: &LineUp, cx| {
+    //     this.scroll_screen(&ScrollAmount::Line(-1.), cx)
+    // });
+    // cx.add_action(|this: &mut Editor, _: &HalfPageDown, cx| {
+    //     this.scroll_screen(&ScrollAmount::Page(0.5), cx)
+    // });
+    // cx.add_action(|this: &mut Editor, _: &HalfPageUp, cx| {
+    //     this.scroll_screen(&ScrollAmount::Page(-0.5), cx)
+    // });
+    // cx.add_action(|this: &mut Editor, _: &PageDown, cx| {
+    //     this.scroll_screen(&ScrollAmount::Page(1.), cx)
+    // });
+    // cx.add_action(|this: &mut Editor, _: &PageUp, cx| {
+    //     this.scroll_screen(&ScrollAmount::Page(-1.), cx)
+    // });
+}
+
+// impl Editor {
+//     pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext<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,
+//         )
+//     }
+// }

crates/editor2/src/scroll/autoscroll.rs 🔗

@@ -0,0 +1,253 @@
+use std::{cmp, f32};
+
+use gpui::{px, Pixels, ViewContext};
+use language::Point;
+
+use crate::{display_map::ToDisplayPoint, Editor, EditorMode, LineWithInvisibles};
+
+#[derive(PartialEq, Eq)]
+pub enum Autoscroll {
+    Next,
+    Strategy(AutoscrollStrategy),
+}
+
+impl Autoscroll {
+    pub fn fit() -> Self {
+        Self::Strategy(AutoscrollStrategy::Fit)
+    }
+
+    pub fn newest() -> Self {
+        Self::Strategy(AutoscrollStrategy::Newest)
+    }
+
+    pub fn center() -> Self {
+        Self::Strategy(AutoscrollStrategy::Center)
+    }
+}
+
+#[derive(PartialEq, Eq, Default)]
+pub enum AutoscrollStrategy {
+    Fit,
+    Newest,
+    #[default]
+    Center,
+    Top,
+    Bottom,
+}
+
+impl AutoscrollStrategy {
+    fn next(&self) -> Self {
+        match self {
+            AutoscrollStrategy::Center => AutoscrollStrategy::Top,
+            AutoscrollStrategy::Top => AutoscrollStrategy::Bottom,
+            _ => AutoscrollStrategy::Center,
+        }
+    }
+}
+
+impl Editor {
+    pub fn autoscroll_vertically(
+        &mut self,
+        viewport_height: f32,
+        line_height: f32,
+        cx: &mut ViewContext<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();
+    }
+}

crates/editor2/src/scroll/scroll_amount.rs 🔗

@@ -0,0 +1,29 @@
+use crate::Editor;
+use serde::Deserialize;
+
+#[derive(Clone, PartialEq, Deserialize)]
+pub enum ScrollAmount {
+    // Scroll N lines (positive is towards the end of the document)
+    Line(f32),
+    // Scroll N pages (positive is towards the end of the document)
+    Page(f32),
+}
+
+impl ScrollAmount {
+    pub fn lines(&self, editor: &mut Editor) -> f32 {
+        todo!()
+        // match self {
+        //     Self::Line(count) => *count,
+        //     Self::Page(count) => editor
+        //         .visible_line_count()
+        //         .map(|mut l| {
+        //             // for full pages subtract one to leave an anchor line
+        //             if count.abs() == 1.0 {
+        //                 l -= 1.0
+        //             }
+        //             (l * count).trunc()
+        //         })
+        //         .unwrap_or(0.),
+        // }
+    }
+}

crates/editor2/src/selections_collection.rs 🔗

@@ -0,0 +1,887 @@
+use std::{
+    cell::Ref,
+    iter, mem,
+    ops::{Deref, DerefMut, Range, Sub},
+    sync::Arc,
+};
+
+use collections::HashMap;
+use gpui::{AppContext, Model, Pixels};
+use itertools::Itertools;
+use language::{Bias, Point, Selection, SelectionGoal, TextDimension, ToPoint};
+use util::post_inc;
+
+use crate::{
+    display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
+    movement::TextLayoutDetails,
+    Anchor, DisplayPoint, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode, ToOffset,
+};
+
+#[derive(Debug, Clone)]
+pub struct PendingSelection {
+    pub selection: Selection<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))
+}

crates/editor2/src/test.rs 🔗

@@ -0,0 +1,81 @@
+pub mod editor_lsp_test_context;
+pub mod editor_test_context;
+
+// todo!()
+// use crate::{
+//     display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
+//     DisplayPoint, Editor, EditorMode, MultiBuffer,
+// };
+
+// use gpui::{Model, ViewContext};
+
+// use project::Project;
+// use util::test::{marked_text_offsets, marked_text_ranges};
+
+// #[cfg(test)]
+// #[ctor::ctor]
+// fn init_logger() {
+//     if std::env::var("RUST_LOG").is_ok() {
+//         env_logger::init();
+//     }
+// }
+
+// // Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
+// pub fn marked_display_snapshot(
+//     text: &str,
+//     cx: &mut gpui::AppContext,
+// ) -> (DisplaySnapshot, Vec<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)
+// }

crates/editor2/src/test/editor_lsp_test_context.rs 🔗

@@ -0,0 +1,297 @@
+// use std::{
+//     borrow::Cow,
+//     ops::{Deref, DerefMut, Range},
+//     sync::Arc,
+// };
+
+// use anyhow::Result;
+
+// use crate::{Editor, ToPoint};
+// use collections::HashSet;
+// use futures::Future;
+// use gpui::{json, View, ViewContext};
+// use indoc::indoc;
+// use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries};
+// use lsp::{notification, request};
+// use multi_buffer::ToPointUtf16;
+// use project::Project;
+// use smol::stream::StreamExt;
+// use workspace::{AppState, Workspace, WorkspaceHandle};
+
+// use super::editor_test_context::EditorTestContext;
+
+// pub struct EditorLspTestContext<'a> {
+//     pub cx: EditorTestContext<'a>,
+//     pub lsp: lsp::FakeLanguageServer,
+//     pub workspace: View<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
+//     }
+// }

crates/editor2/src/test/editor_test_context.rs 🔗

@@ -0,0 +1,331 @@
+use crate::{
+    display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
+};
+use futures::Future;
+use gpui::{
+    AnyWindowHandle, AppContext, ForegroundExecutor, Keystroke, ModelContext, View, ViewContext,
+};
+use indoc::indoc;
+use language::{Buffer, BufferSnapshot};
+use project::{FakeFs, Project};
+use std::{
+    any::TypeId,
+    ops::{Deref, DerefMut, Range},
+};
+use util::{
+    assert_set_eq,
+    test::{generate_marked_text, marked_text_ranges},
+};
+
+// use super::build_editor_with_project;
+
+// pub struct EditorTestContext<'a> {
+//     pub cx: &'a mut gpui::TestAppContext,
+//     pub window: AnyWindowHandle,
+//     pub editor: View<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
+//     }
+// }

crates/gpui2/Cargo.toml 🔗

@@ -15,7 +15,6 @@ doctest = false
 
 [dependencies]
 collections = { path = "../collections" }
-gpui_macros = { path = "../gpui_macros" }
 gpui2_macros = { path = "../gpui2_macros" }
 util = { path = "../util" }
 sum_tree = { path = "../sum_tree" }

crates/gpui2/src/app.rs 🔗

@@ -46,13 +46,17 @@ pub struct AppCell {
 }
 
 impl AppCell {
+    #[track_caller]
     pub fn borrow(&self) -> AppRef {
+        let thread_id = std::thread::current().id();
+        eprintln!("borrowed {thread_id:?}");
         AppRef(self.app.borrow())
     }
 
+    #[track_caller]
     pub fn borrow_mut(&self) -> AppRefMut {
-        // let thread_id = std::thread::current().id();
-        // dbg!("borrowed {thread_id:?}");
+        let thread_id = std::thread::current().id();
+        eprintln!("borrowed {thread_id:?}");
         AppRefMut(self.app.borrow_mut())
     }
 }
@@ -373,6 +377,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 } => {

crates/gpui2/src/app/test_context.rs 🔗

@@ -189,3 +189,22 @@ impl TestAppContext {
         .unwrap();
     }
 }
+
+impl<T: Send + EventEmitter> Model<T> {
+    pub fn next_event(&self, cx: &mut TestAppContext) -> T::Event
+    where
+        T::Event: Send + Clone,
+    {
+        let (tx, mut rx) = futures::channel::mpsc::unbounded();
+        let _subscription = self.update(cx, |_, cx| {
+            cx.subscribe(self, move |_, _, event, _| {
+                tx.unbounded_send(event.clone()).ok();
+            })
+        });
+
+        cx.executor().run_until_parked();
+        rx.try_next()
+            .expect("no event received")
+            .expect("model was dropped")
+    }
+}

crates/gpui2/src/executor.rs 🔗

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

crates/gpui2/src/geometry.rs 🔗

@@ -755,6 +755,10 @@ impl Pixels {
     pub fn pow(&self, exponent: f32) -> Self {
         Self(self.0.powf(exponent))
     }
+
+    pub fn abs(&self) -> Self {
+        Self(self.0.abs())
+    }
 }
 
 impl Mul<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,
 )]

crates/gpui2/src/style.rs 🔗

@@ -167,6 +167,15 @@ impl TextStyle {
         Ok(self)
     }
 
+    pub fn font(&self) -> Font {
+        Font {
+            family: self.font_family.clone(),
+            features: self.font_features.clone(),
+            weight: self.font_weight,
+            style: self.font_style,
+        }
+    }
+
     pub fn to_run(&self, len: usize) -> TextRun {
         TextRun {
             len,

crates/gpui2/src/text_system.rs 🔗

@@ -7,7 +7,7 @@ use anyhow::anyhow;
 pub use font_features::*;
 pub use line::*;
 pub use line_layout::*;
-use line_wrapper::*;
+pub use line_wrapper::*;
 use smallvec::SmallVec;
 
 use crate::{
@@ -151,7 +151,7 @@ impl TextSystem {
 
     pub fn layout_text(
         &self,
-        text: &SharedString,
+        text: &str,
         font_size: Pixels,
         runs: &[TextRun],
         wrap_width: Option<Pixels>,

crates/gpui2/src/text_system/line.rs 🔗

@@ -2,6 +2,7 @@ use crate::{
     black, point, px, size, BorrowWindow, Bounds, Hsla, Pixels, Point, Result, Size,
     UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
 };
+use derive_more::{Deref, DerefMut};
 use smallvec::SmallVec;
 use std::sync::Arc;
 
@@ -12,8 +13,10 @@ pub struct DecorationRun {
     pub underline: Option<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()
     }

crates/gpui2/src/text_system/line_layout.rs 🔗

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

crates/gpui2/src/view.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
     private::Sealed, AnyBox, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace,
-    BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, LayoutId,
-    Model, Pixels, Size, ViewContext, VisualContext, WeakModel, WindowContext,
+    Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, LayoutId, Model, Pixels,
+    Size, ViewContext, VisualContext, WeakModel, WindowContext,
 };
 use anyhow::{Context, Result};
 use std::{
@@ -196,31 +196,9 @@ impl<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))
+        })
+    }
+}

crates/gpui2/src/window.rs 🔗

@@ -1655,6 +1655,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         }
     }
 
+    // todo!("change this to return a reference");
     pub fn view(&self) -> View<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);

crates/gpui2_macros/src/test.rs 🔗

@@ -175,6 +175,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
                                 inner_fn_args.extend(quote!(&mut #cx_varname_lock,));
                                 cx_teardowns.extend(quote!(
                                     #cx_varname_lock.quit();
+                                    drop(#cx_varname_lock);
                                     dispatcher.run_until_parked();
                                 ));
                                 continue;

crates/language2/Cargo.toml 🔗

@@ -58,6 +58,7 @@ unicase = "2.6"
 rand = { workspace = true, optional = true }
 tree-sitter-rust = { workspace = true, optional = true }
 tree-sitter-typescript = { workspace = true, optional = true }
+pulldown-cmark = { version = "0.9.2", default-features = false }
 
 [dev-dependencies]
 client = { package = "client2", path = "../client2", features = ["test-support"] }

crates/language2/src/buffer.rs 🔗

@@ -1,6 +1,7 @@
 pub use crate::{
     diagnostic_set::DiagnosticSet,
     highlight_map::{HighlightId, HighlightMap},
+    markdown::ParsedMarkdown,
     proto, BracketPair, Grammar, Language, LanguageConfig, LanguageRegistry, PLAIN_TEXT,
 };
 use crate::{

crates/language2/src/language2.rs 🔗

@@ -8,6 +8,7 @@ mod syntax_map;
 
 #[cfg(test)]
 mod buffer_tests;
+pub mod markdown;
 
 use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;

crates/language2/src/markdown.rs 🔗

@@ -0,0 +1,301 @@
+use std::sync::Arc;
+use std::{ops::Range, path::PathBuf};
+
+use crate::{HighlightId, Language, LanguageRegistry};
+use gpui::{px, FontStyle, FontWeight, HighlightStyle, UnderlineStyle};
+use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
+
+#[derive(Debug, Clone)]
+pub struct ParsedMarkdown {
+    pub text: String,
+    pub highlights: Vec<(Range<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) = &current_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("  ");
+    }
+}

crates/rpc2/proto/zed.proto 🔗

@@ -89,88 +89,96 @@ message Envelope {
         FormatBuffersResponse format_buffers_response = 70;
         GetCompletions get_completions = 71;
         GetCompletionsResponse get_completions_response = 72;
-        ApplyCompletionAdditionalEdits apply_completion_additional_edits = 73;
-        ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 74;
-        GetCodeActions get_code_actions = 75;
-        GetCodeActionsResponse get_code_actions_response = 76;
-        GetHover get_hover = 77;
-        GetHoverResponse get_hover_response = 78;
-        ApplyCodeAction apply_code_action = 79;
-        ApplyCodeActionResponse apply_code_action_response = 80;
-        PrepareRename prepare_rename = 81;
-        PrepareRenameResponse prepare_rename_response = 82;
-        PerformRename perform_rename = 83;
-        PerformRenameResponse perform_rename_response = 84;
-        SearchProject search_project = 85;
-        SearchProjectResponse search_project_response = 86;
-
-        UpdateContacts update_contacts = 87;
-        UpdateInviteInfo update_invite_info = 88;
-        ShowContacts show_contacts = 89;
-
-        GetUsers get_users = 90;
-        FuzzySearchUsers fuzzy_search_users = 91;
-        UsersResponse users_response = 92;
-        RequestContact request_contact = 93;
-        RespondToContactRequest respond_to_contact_request = 94;
-        RemoveContact remove_contact = 95;
-
-        Follow follow = 96;
-        FollowResponse follow_response = 97;
-        UpdateFollowers update_followers = 98;
-        Unfollow unfollow = 99;
-        GetPrivateUserInfo get_private_user_info = 100;
-        GetPrivateUserInfoResponse get_private_user_info_response = 101;
-        UpdateDiffBase update_diff_base = 102;
-
-        OnTypeFormatting on_type_formatting = 103;
-        OnTypeFormattingResponse on_type_formatting_response = 104;
-
-        UpdateWorktreeSettings update_worktree_settings = 105;
-
-        InlayHints inlay_hints = 106;
-        InlayHintsResponse inlay_hints_response = 107;
-        ResolveInlayHint resolve_inlay_hint = 108;
-        ResolveInlayHintResponse resolve_inlay_hint_response = 109;
-        RefreshInlayHints refresh_inlay_hints = 110;
-
-        CreateChannel create_channel = 111;
-        CreateChannelResponse create_channel_response = 112;
-        InviteChannelMember invite_channel_member = 113;
-        RemoveChannelMember remove_channel_member = 114;
-        RespondToChannelInvite respond_to_channel_invite = 115;
-        UpdateChannels update_channels = 116;
-        JoinChannel join_channel = 117;
-        DeleteChannel delete_channel = 118;
-        GetChannelMembers get_channel_members = 119;
-        GetChannelMembersResponse get_channel_members_response = 120;
-        SetChannelMemberAdmin set_channel_member_admin = 121;
-        RenameChannel rename_channel = 122;
-        RenameChannelResponse rename_channel_response = 123;
-
-        JoinChannelBuffer join_channel_buffer = 124;
-        JoinChannelBufferResponse join_channel_buffer_response = 125;
-        UpdateChannelBuffer update_channel_buffer = 126;
-        LeaveChannelBuffer leave_channel_buffer = 127;
-        UpdateChannelBufferCollaborators update_channel_buffer_collaborators = 128;
-        RejoinChannelBuffers rejoin_channel_buffers = 129;
-        RejoinChannelBuffersResponse rejoin_channel_buffers_response = 130;
-        AckBufferOperation ack_buffer_operation = 143;
-
-        JoinChannelChat join_channel_chat = 131;
-        JoinChannelChatResponse join_channel_chat_response = 132;
-        LeaveChannelChat leave_channel_chat = 133;
-        SendChannelMessage send_channel_message = 134;
-        SendChannelMessageResponse send_channel_message_response = 135;
-        ChannelMessageSent channel_message_sent = 136;
-        GetChannelMessages get_channel_messages = 137;
-        GetChannelMessagesResponse get_channel_messages_response = 138;
-        RemoveChannelMessage remove_channel_message = 139;
-        AckChannelMessage ack_channel_message = 144;
-
-        LinkChannel link_channel = 140;
-        UnlinkChannel unlink_channel = 141;
-        MoveChannel move_channel = 142; // current max: 144
+        ResolveCompletionDocumentation resolve_completion_documentation = 73;
+        ResolveCompletionDocumentationResponse resolve_completion_documentation_response = 74;
+        ApplyCompletionAdditionalEdits apply_completion_additional_edits = 75;
+        ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 76;
+        GetCodeActions get_code_actions = 77;
+        GetCodeActionsResponse get_code_actions_response = 78;
+        GetHover get_hover = 79;
+        GetHoverResponse get_hover_response = 80;
+        ApplyCodeAction apply_code_action = 81;
+        ApplyCodeActionResponse apply_code_action_response = 82;
+        PrepareRename prepare_rename = 83;
+        PrepareRenameResponse prepare_rename_response = 84;
+        PerformRename perform_rename = 85;
+        PerformRenameResponse perform_rename_response = 86;
+        SearchProject search_project = 87;
+        SearchProjectResponse search_project_response = 88;
+
+        UpdateContacts update_contacts = 89;
+        UpdateInviteInfo update_invite_info = 90;
+        ShowContacts show_contacts = 91;
+
+        GetUsers get_users = 92;
+        FuzzySearchUsers fuzzy_search_users = 93;
+        UsersResponse users_response = 94;
+        RequestContact request_contact = 95;
+        RespondToContactRequest respond_to_contact_request = 96;
+        RemoveContact remove_contact = 97;
+
+        Follow follow = 98;
+        FollowResponse follow_response = 99;
+        UpdateFollowers update_followers = 100;
+        Unfollow unfollow = 101;
+        GetPrivateUserInfo get_private_user_info = 102;
+        GetPrivateUserInfoResponse get_private_user_info_response = 103;
+        UpdateDiffBase update_diff_base = 104;
+
+        OnTypeFormatting on_type_formatting = 105;
+        OnTypeFormattingResponse on_type_formatting_response = 106;
+
+        UpdateWorktreeSettings update_worktree_settings = 107;
+
+        InlayHints inlay_hints = 108;
+        InlayHintsResponse inlay_hints_response = 109;
+        ResolveInlayHint resolve_inlay_hint = 110;
+        ResolveInlayHintResponse resolve_inlay_hint_response = 111;
+        RefreshInlayHints refresh_inlay_hints = 112;
+
+        CreateChannel create_channel = 113;
+        CreateChannelResponse create_channel_response = 114;
+        InviteChannelMember invite_channel_member = 115;
+        RemoveChannelMember remove_channel_member = 116;
+        RespondToChannelInvite respond_to_channel_invite = 117;
+        UpdateChannels update_channels = 118;
+        JoinChannel join_channel = 119;
+        DeleteChannel delete_channel = 120;
+        GetChannelMembers get_channel_members = 121;
+        GetChannelMembersResponse get_channel_members_response = 122;
+        SetChannelMemberRole set_channel_member_role = 123;
+        RenameChannel rename_channel = 124;
+        RenameChannelResponse rename_channel_response = 125;
+
+        JoinChannelBuffer join_channel_buffer = 126;
+        JoinChannelBufferResponse join_channel_buffer_response = 127;
+        UpdateChannelBuffer update_channel_buffer = 128;
+        LeaveChannelBuffer leave_channel_buffer = 129;
+        UpdateChannelBufferCollaborators update_channel_buffer_collaborators = 130;
+        RejoinChannelBuffers rejoin_channel_buffers = 131;
+        RejoinChannelBuffersResponse rejoin_channel_buffers_response = 132;
+        AckBufferOperation ack_buffer_operation = 133;
+
+        JoinChannelChat join_channel_chat = 134;
+        JoinChannelChatResponse join_channel_chat_response = 135;
+        LeaveChannelChat leave_channel_chat = 136;
+        SendChannelMessage send_channel_message = 137;
+        SendChannelMessageResponse send_channel_message_response = 138;
+        ChannelMessageSent channel_message_sent = 139;
+        GetChannelMessages get_channel_messages = 140;
+        GetChannelMessagesResponse get_channel_messages_response = 141;
+        RemoveChannelMessage remove_channel_message = 142;
+        AckChannelMessage ack_channel_message = 143;
+        GetChannelMessagesById get_channel_messages_by_id = 144;
+
+        MoveChannel move_channel = 147;
+        SetChannelVisibility set_channel_visibility = 148;
+
+        AddNotification add_notification = 149;
+        GetNotifications get_notifications = 150;
+        GetNotificationsResponse get_notifications_response = 151;
+        DeleteNotification delete_notification = 152;
+        MarkNotificationRead mark_notification_read = 153; // Current max
     }
 }
 
@@ -332,6 +340,7 @@ message RoomUpdated {
 message LiveKitConnectionInfo {
     string server_url = 1;
     string token = 2;
+    bool can_publish = 3;
 }
 
 message ShareProject {
@@ -832,6 +841,17 @@ message ResolveState {
     }
 }
 
+message ResolveCompletionDocumentation {
+    uint64 project_id = 1;
+    uint64 language_server_id = 2;
+    bytes lsp_completion = 3;
+}
+
+message ResolveCompletionDocumentationResponse {
+    string text = 1;
+    bool is_markdown = 2;
+}
+
 message ResolveInlayHint {
     uint64 project_id = 1;
     uint64 buffer_id = 2;
@@ -950,13 +970,10 @@ message LspDiskBasedDiagnosticsUpdated {}
 
 message UpdateChannels {
     repeated Channel channels = 1;
-    repeated ChannelEdge insert_edge = 2;
-    repeated ChannelEdge delete_edge = 3;
     repeated uint64 delete_channels = 4;
     repeated Channel channel_invitations = 5;
     repeated uint64 remove_channel_invitations = 6;
     repeated ChannelParticipants channel_participants = 7;
-    repeated ChannelPermission channel_permissions = 8;
     repeated UnseenChannelMessage unseen_channel_messages = 9;
     repeated UnseenChannelBufferChange unseen_channel_buffer_changes = 10;
 }
@@ -972,14 +989,9 @@ message UnseenChannelBufferChange {
     repeated VectorClockEntry version = 3;
 }
 
-message ChannelEdge {
-    uint64 channel_id = 1;
-    uint64 parent_id = 2;
-}
-
 message ChannelPermission {
     uint64 channel_id = 1;
-    bool is_admin = 2;
+    ChannelRole role = 3;
 }
 
 message ChannelParticipants {
@@ -1005,8 +1017,8 @@ message GetChannelMembersResponse {
 
 message ChannelMember {
     uint64 user_id = 1;
-    bool admin = 2;
     Kind kind = 3;
+    ChannelRole role = 4;
 
     enum Kind {
         Member = 0;
@@ -1028,7 +1040,7 @@ message CreateChannelResponse {
 message InviteChannelMember {
     uint64 channel_id = 1;
     uint64 user_id = 2;
-    bool admin = 3;
+    ChannelRole role = 4;
 }
 
 message RemoveChannelMember {
@@ -1036,10 +1048,22 @@ message RemoveChannelMember {
     uint64 user_id = 2;
 }
 
-message SetChannelMemberAdmin {
+enum ChannelRole {
+    Admin = 0;
+    Member = 1;
+    Guest = 2;
+    Banned = 3;
+}
+
+message SetChannelMemberRole {
     uint64 channel_id = 1;
     uint64 user_id = 2;
-    bool admin = 3;
+    ChannelRole role = 3;
+}
+
+message SetChannelVisibility {
+    uint64 channel_id = 1;
+    ChannelVisibility visibility = 2;
 }
 
 message RenameChannel {
@@ -1068,6 +1092,7 @@ message SendChannelMessage {
     uint64 channel_id = 1;
     string body = 2;
     Nonce nonce = 3;
+    repeated ChatMention mentions = 4;
 }
 
 message RemoveChannelMessage {
@@ -1099,20 +1124,13 @@ message GetChannelMessagesResponse {
     bool done = 2;
 }
 
-message LinkChannel {
-    uint64 channel_id = 1;
-    uint64 to = 2;
-}
-
-message UnlinkChannel {
-    uint64 channel_id = 1;
-    uint64 from = 2;
+message GetChannelMessagesById {
+    repeated uint64 message_ids = 1;
 }
 
 message MoveChannel {
     uint64 channel_id = 1;
-    uint64 from = 2;
-    uint64 to = 3;
+    optional uint64 to = 2;
 }
 
 message JoinChannelBuffer {
@@ -1125,6 +1143,12 @@ message ChannelMessage {
     uint64 timestamp = 3;
     uint64 sender_id = 4;
     Nonce nonce = 5;
+    repeated ChatMention mentions = 6;
+}
+
+message ChatMention {
+    Range range = 1;
+    uint64 user_id = 2;
 }
 
 message RejoinChannelBuffers {
@@ -1216,7 +1240,6 @@ message ShowContacts {}
 
 message IncomingContactRequest {
     uint64 requester_id = 1;
-    bool should_notify = 2;
 }
 
 message UpdateDiagnostics {
@@ -1533,16 +1556,23 @@ message Nonce {
     uint64 lower_half = 2;
 }
 
+enum ChannelVisibility {
+    Public = 0;
+    Members = 1;
+}
+
 message Channel {
     uint64 id = 1;
     string name = 2;
+    ChannelVisibility visibility = 3;
+    ChannelRole role = 4;
+    repeated uint64 parent_path = 5;
 }
 
 message Contact {
     uint64 user_id = 1;
     bool online = 2;
     bool busy = 3;
-    bool should_notify = 4;
 }
 
 message WorktreeMetadata {
@@ -1557,3 +1587,34 @@ message UpdateDiffBase {
     uint64 buffer_id = 2;
     optional string diff_base = 3;
 }
+
+message GetNotifications {
+    optional uint64 before_id = 1;
+}
+
+message AddNotification {
+    Notification notification = 1;
+}
+
+message GetNotificationsResponse {
+    repeated Notification notifications = 1;
+    bool done = 2;
+}
+
+message DeleteNotification {
+    uint64 notification_id = 1;
+}
+
+message MarkNotificationRead {
+    uint64 notification_id = 1;
+}
+
+message Notification {
+    uint64 id = 1;
+    uint64 timestamp = 2;
+    string kind = 3;
+    optional uint64 entity_id = 4;
+    string content = 5;
+    bool is_read = 6;
+    optional bool response = 7;
+}

crates/rpc2/src/proto.rs 🔗

@@ -133,6 +133,9 @@ impl fmt::Display for PeerId {
 
 messages!(
     (Ack, Foreground),
+    (AckBufferOperation, Background),
+    (AckChannelMessage, Background),
+    (AddNotification, Foreground),
     (AddProjectCollaborator, Foreground),
     (ApplyCodeAction, Background),
     (ApplyCodeActionResponse, Background),
@@ -143,57 +146,74 @@ messages!(
     (Call, Foreground),
     (CallCanceled, Foreground),
     (CancelCall, Foreground),
+    (ChannelMessageSent, Foreground),
     (CopyProjectEntry, Foreground),
     (CreateBufferForPeer, Foreground),
     (CreateChannel, Foreground),
     (CreateChannelResponse, Foreground),
-    (ChannelMessageSent, Foreground),
     (CreateProjectEntry, Foreground),
     (CreateRoom, Foreground),
     (CreateRoomResponse, Foreground),
     (DeclineCall, Foreground),
+    (DeleteChannel, Foreground),
+    (DeleteNotification, Foreground),
     (DeleteProjectEntry, Foreground),
     (Error, Foreground),
     (ExpandProjectEntry, Foreground),
+    (ExpandProjectEntryResponse, Foreground),
     (Follow, Foreground),
     (FollowResponse, Foreground),
     (FormatBuffers, Foreground),
     (FormatBuffersResponse, Foreground),
     (FuzzySearchUsers, Foreground),
-    (GetCodeActions, Background),
-    (GetCodeActionsResponse, Background),
-    (GetHover, Background),
-    (GetHoverResponse, Background),
+    (GetChannelMembers, Foreground),
+    (GetChannelMembersResponse, Foreground),
     (GetChannelMessages, Background),
+    (GetChannelMessagesById, Background),
     (GetChannelMessagesResponse, Background),
-    (SendChannelMessage, Background),
-    (SendChannelMessageResponse, Background),
+    (GetCodeActions, Background),
+    (GetCodeActionsResponse, Background),
     (GetCompletions, Background),
     (GetCompletionsResponse, Background),
     (GetDefinition, Background),
     (GetDefinitionResponse, Background),
-    (GetTypeDefinition, Background),
-    (GetTypeDefinitionResponse, Background),
     (GetDocumentHighlights, Background),
     (GetDocumentHighlightsResponse, Background),
-    (GetReferences, Background),
-    (GetReferencesResponse, Background),
+    (GetHover, Background),
+    (GetHoverResponse, Background),
+    (GetNotifications, Foreground),
+    (GetNotificationsResponse, Foreground),
+    (GetPrivateUserInfo, Foreground),
+    (GetPrivateUserInfoResponse, Foreground),
     (GetProjectSymbols, Background),
     (GetProjectSymbolsResponse, Background),
+    (GetReferences, Background),
+    (GetReferencesResponse, Background),
+    (GetTypeDefinition, Background),
+    (GetTypeDefinitionResponse, Background),
     (GetUsers, Foreground),
     (Hello, Foreground),
     (IncomingCall, Foreground),
+    (InlayHints, Background),
+    (InlayHintsResponse, Background),
     (InviteChannelMember, Foreground),
-    (UsersResponse, Foreground),
+    (JoinChannel, Foreground),
+    (JoinChannelBuffer, Foreground),
+    (JoinChannelBufferResponse, Foreground),
+    (JoinChannelChat, Foreground),
+    (JoinChannelChatResponse, Foreground),
     (JoinProject, Foreground),
     (JoinProjectResponse, Foreground),
     (JoinRoom, Foreground),
     (JoinRoomResponse, Foreground),
-    (JoinChannelChat, Foreground),
-    (JoinChannelChatResponse, Foreground),
+    (LeaveChannelBuffer, Background),
     (LeaveChannelChat, Foreground),
     (LeaveProject, Foreground),
     (LeaveRoom, Foreground),
+    (MarkNotificationRead, Foreground),
+    (MoveChannel, Foreground),
+    (OnTypeFormatting, Background),
+    (OnTypeFormattingResponse, Background),
     (OpenBufferById, Background),
     (OpenBufferByPath, Background),
     (OpenBufferForSymbol, Background),
@@ -201,58 +221,56 @@ messages!(
     (OpenBufferResponse, Background),
     (PerformRename, Background),
     (PerformRenameResponse, Background),
-    (OnTypeFormatting, Background),
-    (OnTypeFormattingResponse, Background),
-    (InlayHints, Background),
-    (InlayHintsResponse, Background),
-    (ResolveInlayHint, Background),
-    (ResolveInlayHintResponse, Background),
-    (RefreshInlayHints, Foreground),
     (Ping, Foreground),
     (PrepareRename, Background),
     (PrepareRenameResponse, Background),
-    (ExpandProjectEntryResponse, Foreground),
     (ProjectEntryResponse, Foreground),
+    (RefreshInlayHints, Foreground),
+    (RejoinChannelBuffers, Foreground),
+    (RejoinChannelBuffersResponse, Foreground),
     (RejoinRoom, Foreground),
     (RejoinRoomResponse, Foreground),
-    (RemoveContact, Foreground),
-    (RemoveChannelMember, Foreground),
-    (RemoveChannelMessage, Foreground),
     (ReloadBuffers, Foreground),
     (ReloadBuffersResponse, Foreground),
+    (RemoveChannelMember, Foreground),
+    (RemoveChannelMessage, Foreground),
+    (RemoveContact, Foreground),
     (RemoveProjectCollaborator, Foreground),
+    (RenameChannel, Foreground),
+    (RenameChannelResponse, Foreground),
     (RenameProjectEntry, Foreground),
     (RequestContact, Foreground),
-    (RespondToContactRequest, Foreground),
+    (ResolveCompletionDocumentation, Background),
+    (ResolveCompletionDocumentationResponse, Background),
+    (ResolveInlayHint, Background),
+    (ResolveInlayHintResponse, Background),
     (RespondToChannelInvite, Foreground),
-    (JoinChannel, Foreground),
+    (RespondToContactRequest, Foreground),
     (RoomUpdated, Foreground),
     (SaveBuffer, Foreground),
-    (RenameChannel, Foreground),
-    (RenameChannelResponse, Foreground),
-    (SetChannelMemberAdmin, Foreground),
+    (SetChannelMemberRole, Foreground),
+    (SetChannelVisibility, Foreground),
     (SearchProject, Background),
     (SearchProjectResponse, Background),
+    (SendChannelMessage, Background),
+    (SendChannelMessageResponse, Background),
     (ShareProject, Foreground),
     (ShareProjectResponse, Foreground),
     (ShowContacts, Foreground),
     (StartLanguageServer, Foreground),
     (SynchronizeBuffers, Foreground),
     (SynchronizeBuffersResponse, Foreground),
-    (RejoinChannelBuffers, Foreground),
-    (RejoinChannelBuffersResponse, Foreground),
     (Test, Foreground),
     (Unfollow, Foreground),
     (UnshareProject, Foreground),
     (UpdateBuffer, Foreground),
     (UpdateBufferFile, Foreground),
-    (UpdateContacts, Foreground),
-    (DeleteChannel, Foreground),
-    (MoveChannel, Foreground),
-    (LinkChannel, Foreground),
-    (UnlinkChannel, Foreground),
+    (UpdateChannelBuffer, Foreground),
+    (UpdateChannelBufferCollaborators, Foreground),
     (UpdateChannels, Foreground),
+    (UpdateContacts, Foreground),
     (UpdateDiagnosticSummary, Foreground),
+    (UpdateDiffBase, Foreground),
     (UpdateFollowers, Foreground),
     (UpdateInviteInfo, Foreground),
     (UpdateLanguageServer, Foreground),
@@ -261,18 +279,7 @@ messages!(
     (UpdateProjectCollaborator, Foreground),
     (UpdateWorktree, Foreground),
     (UpdateWorktreeSettings, Foreground),
-    (UpdateDiffBase, Foreground),
-    (GetPrivateUserInfo, Foreground),
-    (GetPrivateUserInfoResponse, Foreground),
-    (GetChannelMembers, Foreground),
-    (GetChannelMembersResponse, Foreground),
-    (JoinChannelBuffer, Foreground),
-    (JoinChannelBufferResponse, Foreground),
-    (LeaveChannelBuffer, Background),
-    (UpdateChannelBuffer, Foreground),
-    (UpdateChannelBufferCollaborators, Foreground),
-    (AckBufferOperation, Background),
-    (AckChannelMessage, Background),
+    (UsersResponse, Foreground),
 );
 
 request_messages!(
@@ -284,72 +291,78 @@ request_messages!(
     (Call, Ack),
     (CancelCall, Ack),
     (CopyProjectEntry, ProjectEntryResponse),
+    (CreateChannel, CreateChannelResponse),
     (CreateProjectEntry, ProjectEntryResponse),
     (CreateRoom, CreateRoomResponse),
-    (CreateChannel, CreateChannelResponse),
     (DeclineCall, Ack),
+    (DeleteChannel, Ack),
     (DeleteProjectEntry, ProjectEntryResponse),
     (ExpandProjectEntry, ExpandProjectEntryResponse),
     (Follow, FollowResponse),
     (FormatBuffers, FormatBuffersResponse),
+    (FuzzySearchUsers, UsersResponse),
+    (GetChannelMembers, GetChannelMembersResponse),
+    (GetChannelMessages, GetChannelMessagesResponse),
+    (GetChannelMessagesById, GetChannelMessagesResponse),
     (GetCodeActions, GetCodeActionsResponse),
-    (GetHover, GetHoverResponse),
     (GetCompletions, GetCompletionsResponse),
     (GetDefinition, GetDefinitionResponse),
-    (GetTypeDefinition, GetTypeDefinitionResponse),
     (GetDocumentHighlights, GetDocumentHighlightsResponse),
-    (GetReferences, GetReferencesResponse),
+    (GetHover, GetHoverResponse),
+    (GetNotifications, GetNotificationsResponse),
     (GetPrivateUserInfo, GetPrivateUserInfoResponse),
     (GetProjectSymbols, GetProjectSymbolsResponse),
-    (FuzzySearchUsers, UsersResponse),
+    (GetReferences, GetReferencesResponse),
+    (GetTypeDefinition, GetTypeDefinitionResponse),
     (GetUsers, UsersResponse),
+    (IncomingCall, Ack),
+    (InlayHints, InlayHintsResponse),
     (InviteChannelMember, Ack),
+    (JoinChannel, JoinRoomResponse),
+    (JoinChannelBuffer, JoinChannelBufferResponse),
+    (JoinChannelChat, JoinChannelChatResponse),
     (JoinProject, JoinProjectResponse),
     (JoinRoom, JoinRoomResponse),
-    (JoinChannelChat, JoinChannelChatResponse),
+    (LeaveChannelBuffer, Ack),
     (LeaveRoom, Ack),
-    (RejoinRoom, RejoinRoomResponse),
-    (IncomingCall, Ack),
+    (MarkNotificationRead, Ack),
+    (MoveChannel, Ack),
+    (OnTypeFormatting, OnTypeFormattingResponse),
     (OpenBufferById, OpenBufferResponse),
     (OpenBufferByPath, OpenBufferResponse),
     (OpenBufferForSymbol, OpenBufferForSymbolResponse),
-    (Ping, Ack),
     (PerformRename, PerformRenameResponse),
+    (Ping, Ack),
     (PrepareRename, PrepareRenameResponse),
-    (OnTypeFormatting, OnTypeFormattingResponse),
-    (InlayHints, InlayHintsResponse),
-    (ResolveInlayHint, ResolveInlayHintResponse),
     (RefreshInlayHints, Ack),
+    (RejoinChannelBuffers, RejoinChannelBuffersResponse),
+    (RejoinRoom, RejoinRoomResponse),
     (ReloadBuffers, ReloadBuffersResponse),
-    (RequestContact, Ack),
     (RemoveChannelMember, Ack),
-    (RemoveContact, Ack),
-    (RespondToContactRequest, Ack),
-    (RespondToChannelInvite, Ack),
-    (SetChannelMemberAdmin, Ack),
-    (SendChannelMessage, SendChannelMessageResponse),
-    (GetChannelMessages, GetChannelMessagesResponse),
-    (GetChannelMembers, GetChannelMembersResponse),
-    (JoinChannel, JoinRoomResponse),
     (RemoveChannelMessage, Ack),
-    (DeleteChannel, Ack),
-    (RenameProjectEntry, ProjectEntryResponse),
+    (RemoveContact, Ack),
     (RenameChannel, RenameChannelResponse),
-    (LinkChannel, Ack),
-    (UnlinkChannel, Ack),
-    (MoveChannel, Ack),
+    (RenameProjectEntry, ProjectEntryResponse),
+    (RequestContact, Ack),
+    (
+        ResolveCompletionDocumentation,
+        ResolveCompletionDocumentationResponse
+    ),
+    (ResolveInlayHint, ResolveInlayHintResponse),
+    (RespondToChannelInvite, Ack),
+    (RespondToContactRequest, Ack),
     (SaveBuffer, BufferSaved),
     (SearchProject, SearchProjectResponse),
+    (SendChannelMessage, SendChannelMessageResponse),
+    (SetChannelMemberRole, Ack),
+    (SetChannelVisibility, Ack),
     (ShareProject, ShareProjectResponse),
     (SynchronizeBuffers, SynchronizeBuffersResponse),
-    (RejoinChannelBuffers, RejoinChannelBuffersResponse),
     (Test, Test),
     (UpdateBuffer, Ack),
     (UpdateParticipantLocation, Ack),
     (UpdateProject, Ack),
     (UpdateWorktree, Ack),
-    (JoinChannelBuffer, JoinChannelBufferResponse),
-    (LeaveChannelBuffer, Ack)
 );
 
 entity_messages!(
@@ -368,25 +381,26 @@ entity_messages!(
     GetCodeActions,
     GetCompletions,
     GetDefinition,
-    GetTypeDefinition,
     GetDocumentHighlights,
     GetHover,
-    GetReferences,
     GetProjectSymbols,
+    GetReferences,
+    GetTypeDefinition,
+    InlayHints,
     JoinProject,
     LeaveProject,
+    OnTypeFormatting,
     OpenBufferById,
     OpenBufferByPath,
     OpenBufferForSymbol,
     PerformRename,
-    OnTypeFormatting,
-    InlayHints,
-    ResolveInlayHint,
-    RefreshInlayHints,
     PrepareRename,
+    RefreshInlayHints,
     ReloadBuffers,
     RemoveProjectCollaborator,
     RenameProjectEntry,
+    ResolveCompletionDocumentation,
+    ResolveInlayHint,
     SaveBuffer,
     SearchProject,
     StartLanguageServer,
@@ -395,19 +409,19 @@ entity_messages!(
     UpdateBuffer,
     UpdateBufferFile,
     UpdateDiagnosticSummary,
+    UpdateDiffBase,
     UpdateLanguageServer,
     UpdateProject,
     UpdateProjectCollaborator,
     UpdateWorktree,
     UpdateWorktreeSettings,
-    UpdateDiffBase
 );
 
 entity_messages!(
     channel_id,
     ChannelMessageSent,
-    UpdateChannelBuffer,
     RemoveChannelMessage,
+    UpdateChannelBuffer,
     UpdateChannelBufferCollaborators,
 );
 

crates/text2/src/selection.rs 🔗

@@ -5,7 +5,7 @@ use std::ops::Range;
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum SelectionGoal {
     None,
-    HorizontalPosition(f32),
+    HorizontalPosition(f32), // todo!("Can we use pixels here without adding a runtime gpui dependency?")
     HorizontalRange { start: f32, end: f32 },
     WrappedHorizontalPosition((u32, f32)),
 }

crates/theme2/src/colors.rs 🔗

@@ -48,7 +48,7 @@ pub struct GitStatusColors {
     pub renamed: Hsla,
 }
 
-#[derive(Refineable, Clone, Debug, Default)]
+#[derive(Refineable, Clone, Debug)]
 #[refineable(debug)]
 pub struct ThemeColors {
     pub border: Hsla,
@@ -111,6 +111,8 @@ pub struct ThemeColors {
 #[derive(Refineable, Clone)]
 pub struct ThemeStyles {
     pub system: SystemColors,
+
+    #[refineable]
     pub colors: ThemeColors,
     pub status: StatusColors,
     pub git: GitStatusColors,

crates/workspace2/src/item.rs 🔗

@@ -12,10 +12,9 @@ use client2::{
     Client,
 };
 use gpui::{
-    AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, HighlightStyle, Model, Pixels,
-    Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext,
+    AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, HighlightStyle,
+    Model, Pixels, Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext,
 };
-use parking_lot::Mutex;
 use project2::{Project, ProjectEntryId, ProjectPath};
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
@@ -23,8 +22,10 @@ use settings2::Settings;
 use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
+    cell::RefCell,
     ops::Range,
     path::PathBuf,
+    rc::Rc,
     sync::{
         atomic::{AtomicBool, Ordering},
         Arc,
@@ -90,7 +91,8 @@ pub struct BreadcrumbText {
     pub highlights: Option<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 {

crates/workspace2/src/pane.rs 🔗

@@ -9,8 +9,8 @@ use crate::{
 use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
 use gpui::{
-    AppContext, AsyncWindowContext, Component, Div, EntityId, EventEmitter, Model, PromptLevel,
-    Render, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
+    AppContext, AsyncWindowContext, Component, Div, EntityId, EventEmitter, FocusHandle, Model,
+    PromptLevel, Render, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
 };
 use parking_lot::Mutex;
 use project2::{Project, ProjectEntryId, ProjectPath};
@@ -171,6 +171,7 @@ impl fmt::Debug for Event {
 }
 
 pub struct Pane {
+    focus_handle: FocusHandle,
     items: Vec<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>) {

crates/workspace2/src/workspace2.rs 🔗

@@ -14,9 +14,12 @@ mod status_bar;
 mod toolbar;
 mod workspace_settings;
 
-use crate::persistence::model::{
-    DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup,
-    SerializedWorkspace,
+pub use crate::persistence::{
+    model::{
+        DockData, DockStructure, ItemId, SerializedItem, SerializedPane, SerializedPaneGroup,
+        SerializedWorkspace,
+    },
+    WorkspaceDb,
 };
 use anyhow::{anyhow, Context as _, Result};
 use call2::ActiveCall;
@@ -33,10 +36,10 @@ use futures::{
 };
 use gpui::{
     div, point, size, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext,
-    AsyncWindowContext, Bounds, Component, Div, EntityId, EventEmitter, GlobalPixels, Model,
-    ModelContext, ParentElement, Point, Render, Size, StatefulInteractive, Styled, Subscription,
-    Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle,
-    WindowOptions,
+    AsyncWindowContext, Bounds, Component, Div, Entity, EntityId, EventEmitter, FocusHandle,
+    GlobalPixels, Model, ModelContext, ParentElement, Point, Render, Size, StatefulInteractive,
+    Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds,
+    WindowContext, WindowHandle, WindowOptions,
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
 use itertools::Itertools;
@@ -46,15 +49,13 @@ use node_runtime::NodeRuntime;
 use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
 pub use pane::*;
 pub use pane_group::*;
-use persistence::{
-    model::{ItemId, WorkspaceLocation},
-    DB,
-};
+use persistence::{model::WorkspaceLocation, DB};
 use postage::stream::Stream;
 use project2::{Project, ProjectEntryId, ProjectPath, Worktree};
 use serde::Deserialize;
 use settings2::Settings;
 use status_bar::StatusBar;
+pub use status_bar::StatusItemView;
 use std::{
     any::TypeId,
     borrow::Cow,
@@ -415,18 +416,17 @@ type ItemDeserializers = HashMap<
     ) -> Task<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 {
@@ -544,10 +544,12 @@ impl DelayedDebouncedEditAction {
 pub enum Event {
     PaneAdded(View<Pane>),
     ContactRequestedJoin(u64),
+    WorkspaceCreated(WeakView<Workspace>),
 }
 
 pub struct Workspace {
     weak_self: WeakView<Self>,
+    focus_handle: FocusHandle,
     //     modal: Option<ActiveModal>,
     zoomed: Option<AnyWeakView>,
     zoomed_position: Option<DockPosition>,
@@ -697,8 +699,7 @@ impl Workspace {
             Ok(())
         });
 
-        // todo!("replace with a different mechanism")
-        // cx.emit_global(WorkspaceCreated(weak_handle.clone()));
+        cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
 
         let left_dock = cx.build_view(|_| Dock::new(DockPosition::Left));
         let bottom_dock = cx.build_view(|_| Dock::new(DockPosition::Bottom));
@@ -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 {
@@ -4250,7 +4253,7 @@ impl ViewId {
 //     }
 // }
 
-// pub struct WorkspaceCreated(pub WeakView<Workspace>);
+pub struct WorkspaceCreated(pub WeakView<Workspace>);
 
 pub fn activate_workspace_for_project(
     cx: &mut AppContext,

crates/zed2/Cargo.toml 🔗

@@ -34,7 +34,7 @@ copilot = { package = "copilot2", path = "../copilot2" }
 # copilot_button = { path = "../copilot_button" }
 # diagnostics = { path = "../diagnostics" }
 db = { package = "db2", path = "../db2" }
-# editor = { path = "../editor" }
+editor = { package="editor2", path = "../editor2" }
 # feedback = { path = "../feedback" }
 # file_finder = { path = "../file_finder" }
 # search = { path = "../search" }
@@ -70,7 +70,7 @@ theme = { package = "theme2", path = "../theme2" }
 util = { path = "../util" }
 # semantic_index = { path = "../semantic_index" }
 # vim = { path = "../vim" }
-workspace2 = { path = "../workspace2" }
+workspace = { package = "workspace2", path = "../workspace2" }
 # welcome = { path = "../welcome" }
 # zed-actions = {path = "../zed-actions"}
 anyhow.workspace = true

crates/zed2/src/main.rs 🔗

@@ -4,18 +4,13 @@
 // Allow binary to be called Zed for a nice application menu when running executable directly
 #![allow(non_snake_case)]
 
-use crate::open_listener::{OpenListener, OpenRequest};
 use anyhow::{anyhow, Context as _, Result};
 use backtrace::Backtrace;
-use cli::{
-    ipc::{self, IpcSender},
-    CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME,
-};
+use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
 use client::UserStore;
-use collections::HashMap;
 use db::kvp::KEY_VALUE_STORE;
 use fs::RealFs;
-use futures::{channel::mpsc, SinkExt, StreamExt};
+use futures::StreamExt;
 use gpui::{Action, App, AppContext, AsyncAppContext, Context, SemanticVersion, Task};
 use isahc::{prelude::Configurable, Request};
 use language::LanguageRegistry;
@@ -35,7 +30,7 @@ use std::{
     fs::OpenOptions,
     io::{IsTerminal, Write},
     panic,
-    path::Path,
+    path::{Path, PathBuf},
     sync::{
         atomic::{AtomicU32, Ordering},
         Arc,
@@ -43,18 +38,18 @@ use std::{
     thread,
     time::{SystemTime, UNIX_EPOCH},
 };
-use text::Point;
 use util::{
     async_maybe,
     channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
     http::{self, HttpClient},
-    paths::{self, PathLikeWithPosition},
-    ResultExt,
+    paths, ResultExt,
 };
 use uuid::Uuid;
-use workspace2::{AppState, WorkspaceStore};
-use zed2::{build_window_options, initialize_workspace, languages};
-use zed2::{ensure_only_instance, Assets, IsOnlyInstance};
+use workspace::{AppState, WorkspaceStore};
+use zed2::{
+    build_window_options, ensure_only_instance, handle_cli_connection, initialize_workspace,
+    languages, Assets, IsOnlyInstance, OpenListener, OpenRequest,
+};
 
 mod open_listener;
 
@@ -143,7 +138,7 @@ fn main() {
         client::init(&client, cx);
         // command_palette::init(cx);
         language::init(cx);
-        // editor::init(cx);
+        editor::init(cx);
         // go_to_line::init(cx);
         // file_finder::init(cx);
         // outline::init(cx);
@@ -194,7 +189,7 @@ fn main() {
         // audio::init(Assets, cx);
         // auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx);
 
-        workspace2::init(app_state.clone(), cx);
+        workspace::init(app_state.clone(), cx);
         // recent_projects::init(cx);
 
         // journal2::init(app_state.clone(), cx);
@@ -213,6 +208,7 @@ fn main() {
         if stdout_is_a_pty() {
             cx.activate(true);
             let urls = collect_url_args();
+            dbg!(&urls);
             if !urls.is_empty() {
                 listener.open_urls(urls)
             }
@@ -230,9 +226,27 @@ fn main() {
 
         let mut _triggered_authentication = false;
 
+        fn open_paths_and_log_errs(
+            paths: &[PathBuf],
+            app_state: &Arc<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),

crates/zed2/src/open_listener.rs 🔗

@@ -1,15 +1,26 @@
-use anyhow::anyhow;
+use anyhow::{anyhow, Context, Result};
+use cli::{ipc, IpcHandshake};
 use cli::{ipc::IpcSender, CliRequest, CliResponse};
-use futures::channel::mpsc;
+use editor::scroll::autoscroll::Autoscroll;
+use editor::Editor;
 use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
+use futures::channel::{mpsc, oneshot};
+use futures::{FutureExt, SinkExt, StreamExt};
+use gpui::AsyncAppContext;
+use language::{Bias, Point};
+use std::collections::HashMap;
 use std::ffi::OsStr;
 use std::os::unix::prelude::OsStrExt;
+use std::path::Path;
 use std::sync::atomic::Ordering;
+use std::sync::Arc;
+use std::thread;
+use std::time::Duration;
 use std::{path::PathBuf, sync::atomic::AtomicBool};
 use util::channel::parse_zed_link;
+use util::paths::PathLikeWithPosition;
 use util::ResultExt;
-
-use crate::connect_to_cli;
+use workspace::AppState;
 
 pub enum OpenRequest {
     Paths {
@@ -21,6 +32,9 @@ pub enum OpenRequest {
     JoinChannel {
         channel_id: u64,
     },
+    OpenChannelNotes {
+        channel_id: u64,
+    },
 }
 
 pub struct OpenListener {
@@ -74,7 +88,11 @@ impl OpenListener {
             if let Some(slug) = parts.next() {
                 if let Some(id_str) = slug.split("-").last() {
                     if let Ok(channel_id) = id_str.parse::<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();
+            }
+        }
+    }
+}

crates/zed2/src/zed2.rs 🔗

@@ -7,216 +7,17 @@ mod only_instance;
 mod open_listener;
 
 pub use assets::*;
-use collections::HashMap;
 use gpui::{
-    point, px, AppContext, AsyncAppContext, AsyncWindowContext, Point, Task, TitlebarOptions,
-    WeakView, WindowBounds, WindowKind, WindowOptions,
+    point, px, AppContext, AsyncWindowContext, Task, TitlebarOptions, WeakView, WindowBounds,
+    WindowKind, WindowOptions,
 };
 pub use only_instance::*;
 pub use open_listener::*;
 
-use anyhow::{Context, Result};
-use cli::{
-    ipc::{self, IpcSender},
-    CliRequest, CliResponse, IpcHandshake,
-};
-use futures::{
-    channel::{mpsc, oneshot},
-    FutureExt, SinkExt, StreamExt,
-};
-use std::{path::Path, sync::Arc, thread, time::Duration};
-use util::{paths::PathLikeWithPosition, ResultExt};
+use anyhow::Result;
+use std::sync::Arc;
 use uuid::Uuid;
-use workspace2::{AppState, Workspace};
-
-pub fn connect_to_cli(
-    server_name: &str,
-) -> Result<(mpsc::Receiver<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!()