Merge branch 'main' into breadcrumbs

Mikayla Maki created

Change summary

Cargo.lock                                 |   1 
Procfile                                   |   2 
assets/settings/default.json               |   2 
crates/editor/src/blink_manager.rs         | 110 +++++++
crates/editor/src/display_map/block_map.rs |   1 
crates/editor/src/display_map/fold_map.rs  |   1 
crates/editor/src/display_map/wrap_map.rs  |   1 
crates/editor/src/editor.rs                |  86 +----
crates/editor/src/element.rs               | 368 ++++++++++++-----------
crates/editor/src/items.rs                 |   1 
crates/editor/src/multi_buffer.rs          |  11 
crates/git/src/diff.rs                     |   3 
crates/language/src/buffer.rs              |  21 +
crates/language/src/buffer_tests.rs        |   4 
crates/language/src/proto.rs               |  27 +
crates/rpc/proto/zed.proto                 |   9 
crates/rpc/src/rpc.rs                      |   2 
crates/settings/src/settings.rs            |   6 
crates/terminal/Cargo.toml                 |  13 
crates/terminal/src/terminal.rs            |  76 ++--
crates/terminal/src/terminal_element.rs    |   3 
crates/vim/src/state.rs                    |   2 
crates/vim/src/vim.rs                      |   3 
23 files changed, 445 insertions(+), 308 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -5828,6 +5828,7 @@ dependencies = [
  "futures 0.3.24",
  "gpui",
  "itertools",
+ "language",
  "lazy_static",
  "libc",
  "mio-extras",

Procfile 🔗

@@ -1,2 +1,2 @@
-web: cd ../zed.dev && PORT=3000 npx next dev
+web: cd ../zed.dev && PORT=3000 npx vercel dev
 collab: cd crates/collab && cargo run

assets/settings/default.json 🔗

@@ -10,6 +10,8 @@
     // Whether to show the informational hover box when moving the mouse
     // over symbols in the editor.
     "hover_popover_enabled": true,
+    // Whether the cursor blinks in the editor.
+    "cursor_blink": true,
     // Whether to pop the completions menu while typing in an editor without
     // explicitly requesting it.
     "show_completions_on_input": true,
@@ -0,0 +1,110 @@
+use std::time::Duration;
+
+use gpui::{Entity, ModelContext};
+use settings::Settings;
+use smol::Timer;
+
+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 {
+        let weak_handle = cx.weak_handle();
+        cx.observe_global::<Settings, _>(move |_, cx| {
+            if let Some(this) = weak_handle.upgrade(cx) {
+                // Make sure we blink the cursors if the setting is re-enabled
+                this.update(cx, |this, cx| this.blink_cursors(this.blink_epoch, cx));
+            }
+        })
+        .detach();
+
+        Self {
+            blink_interval,
+
+            blink_epoch: 0,
+            blinking_paused: false,
+            visible: true,
+            enabled: true,
+        }
+    }
+
+    fn next_blink_epoch(&mut self) -> usize {
+        self.blink_epoch += 1;
+        self.blink_epoch
+    }
+
+    pub fn pause_blinking(&mut self, cx: &mut ModelContext<Self>) {
+        if !self.visible {
+            self.visible = true;
+            cx.notify();
+        }
+
+        let epoch = self.next_blink_epoch();
+        let interval = self.blink_interval;
+        cx.spawn(|this, mut cx| {
+            let this = this.downgrade();
+            async move {
+                Timer::after(interval).await;
+                if let Some(this) = this.upgrade(&cx) {
+                    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 cx.global::<Settings>().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| {
+                    let this = this.downgrade();
+                    async move {
+                        Timer::after(interval).await;
+                        if let Some(this) = this.upgrade(&cx) {
+                            this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx));
+                        }
+                    }
+                })
+                .detach();
+            }
+        } else if !self.visible {
+            self.visible = true;
+            cx.notify();
+        }
+    }
+
+    pub fn enable(&mut self, cx: &mut ModelContext<Self>) {
+        self.enabled = true;
+        self.blink_cursors(self.blink_epoch, cx);
+    }
+
+    pub fn disable(&mut self, _: &mut ModelContext<Self>) {
+        self.enabled = true;
+    }
+
+    pub fn visible(&self) -> bool {
+        self.visible
+    }
+}
+
+impl Entity for BlinkManager {
+    type Event = ();
+}

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

@@ -157,6 +157,7 @@ pub struct BlockChunks<'a> {
     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>,

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

@@ -987,6 +987,7 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
     }
 }
 
+#[derive(Clone)]
 pub struct FoldBufferRows<'a> {
     cursor: Cursor<'a, Transform, (FoldPoint, Point)>,
     input_buffer_rows: MultiBufferRows<'a>,

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

@@ -62,6 +62,7 @@ pub struct WrapChunks<'a> {
     transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
 }
 
+#[derive(Clone)]
 pub struct WrapBufferRows<'a> {
     input_buffer_rows: fold_map::FoldBufferRows<'a>,
     input_buffer_row: Option<u32>,

crates/editor/src/editor.rs 🔗

@@ -1,3 +1,4 @@
+mod blink_manager;
 pub mod display_map;
 mod element;
 mod highlight_matching_bracket;
@@ -16,6 +17,7 @@ pub mod test;
 
 use aho_corasick::AhoCorasick;
 use anyhow::Result;
+use blink_manager::BlinkManager;
 use clock::ReplicaId;
 use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
 pub use display_map::DisplayPoint;
@@ -42,9 +44,9 @@ use hover_popover::{hide_hover, HoverState};
 pub use items::MAX_TAB_TITLE_LEN;
 pub use language::{char_kind, CharKind};
 use language::{
-    AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, Diagnostic,
-    DiagnosticSeverity, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Point,
-    Selection, SelectionGoal, TransactionId,
+    AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape,
+    Diagnostic, DiagnosticSeverity, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16,
+    Point, Selection, SelectionGoal, TransactionId,
 };
 use link_go_to_definition::{hide_link_definition, LinkGoToDefinitionState};
 pub use multi_buffer::{
@@ -447,12 +449,10 @@ pub struct Editor {
     override_text_style: Option<Box<OverrideTextStyle>>,
     project: Option<ModelHandle<Project>>,
     focused: bool,
-    show_local_cursors: bool,
+    blink_manager: ModelHandle<BlinkManager>,
     show_local_selections: bool,
     show_scrollbars: bool,
     hide_scrollbar_task: Option<Task<()>>,
-    blink_epoch: usize,
-    blinking_paused: bool,
     mode: EditorMode,
     vertical_scroll_margin: f32,
     placeholder_text: Option<Arc<str>>,
@@ -1076,6 +1076,8 @@ impl Editor {
 
         let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
 
+        let blink_manager = cx.add_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
+
         let mut this = Self {
             handle: cx.weak_handle(),
             buffer: buffer.clone(),
@@ -1097,12 +1099,10 @@ impl Editor {
             scroll_top_anchor: Anchor::min(),
             autoscroll_request: None,
             focused: false,
-            show_local_cursors: false,
+            blink_manager: blink_manager.clone(),
             show_local_selections: true,
             show_scrollbars: true,
             hide_scrollbar_task: None,
-            blink_epoch: 0,
-            blinking_paused: false,
             mode,
             vertical_scroll_margin: 3.0,
             placeholder_text: None,
@@ -1130,6 +1130,7 @@ impl Editor {
                 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()),
             ],
         };
         this.end_selection(cx);
@@ -1478,6 +1479,7 @@ impl Editor {
                 buffer.set_active_selections(
                     &self.selections.disjoint_anchors(),
                     self.selections.line_mode,
+                    self.cursor_shape,
                     cx,
                 )
             });
@@ -1541,7 +1543,7 @@ impl Editor {
             refresh_matching_bracket_highlights(self, cx);
         }
 
-        self.pause_cursor_blinking(cx);
+        self.blink_manager.update(cx, BlinkManager::pause_blinking);
         cx.emit(Event::SelectionsChanged { local });
         cx.notify();
     }
@@ -6110,60 +6112,8 @@ impl Editor {
         highlights
     }
 
-    fn next_blink_epoch(&mut self) -> usize {
-        self.blink_epoch += 1;
-        self.blink_epoch
-    }
-
-    fn pause_cursor_blinking(&mut self, cx: &mut ViewContext<Self>) {
-        if !self.focused {
-            return;
-        }
-
-        self.show_local_cursors = true;
-        cx.notify();
-
-        let epoch = self.next_blink_epoch();
-        cx.spawn(|this, mut cx| {
-            let this = this.downgrade();
-            async move {
-                Timer::after(CURSOR_BLINK_INTERVAL).await;
-                if let Some(this) = this.upgrade(&cx) {
-                    this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx))
-                }
-            }
-        })
-        .detach();
-    }
-
-    fn resume_cursor_blinking(&mut self, epoch: usize, cx: &mut ViewContext<Self>) {
-        if epoch == self.blink_epoch {
-            self.blinking_paused = false;
-            self.blink_cursors(epoch, cx);
-        }
-    }
-
-    fn blink_cursors(&mut self, epoch: usize, cx: &mut ViewContext<Self>) {
-        if epoch == self.blink_epoch && self.focused && !self.blinking_paused {
-            self.show_local_cursors = !self.show_local_cursors;
-            cx.notify();
-
-            let epoch = self.next_blink_epoch();
-            cx.spawn(|this, mut cx| {
-                let this = this.downgrade();
-                async move {
-                    Timer::after(CURSOR_BLINK_INTERVAL).await;
-                    if let Some(this) = this.upgrade(&cx) {
-                        this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx));
-                    }
-                }
-            })
-            .detach();
-        }
-    }
-
-    pub fn show_local_cursors(&self) -> bool {
-        self.show_local_cursors && self.focused
+    pub fn show_local_cursors(&self, cx: &AppContext) -> bool {
+        self.blink_manager.read(cx).visible() && self.focused
     }
 
     pub fn show_scrollbars(&self) -> bool {
@@ -6466,9 +6416,7 @@ impl View for Editor {
         }
 
         Stack::new()
-            .with_child(
-                EditorElement::new(self.handle.clone(), style.clone(), self.cursor_shape).boxed(),
-            )
+            .with_child(EditorElement::new(self.handle.clone(), style.clone()).boxed())
             .with_child(ChildView::new(&self.mouse_context_menu, cx).boxed())
             .boxed()
     }
@@ -6484,13 +6432,14 @@ impl View for Editor {
             cx.focus(&rename.editor);
         } else {
             self.focused = true;
-            self.blink_cursors(self.blink_epoch, cx);
+            self.blink_manager.update(cx, BlinkManager::enable);
             self.buffer.update(cx, |buffer, cx| {
                 buffer.finalize_last_transaction(cx);
                 if self.leader_replica_id.is_none() {
                     buffer.set_active_selections(
                         &self.selections.disjoint_anchors(),
                         self.selections.line_mode,
+                        self.cursor_shape,
                         cx,
                     );
                 }
@@ -6502,6 +6451,7 @@ impl View for Editor {
         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);

crates/editor/src/element.rs 🔗

@@ -12,11 +12,11 @@ use crate::{
         CmdShiftChanged, GoToFetchedDefinition, GoToFetchedTypeDefinition, UpdateGoToDefinitionLink,
     },
     mouse_context_menu::DeployMouseContextMenu,
-    EditorStyle,
+    AnchorRangeExt, EditorStyle,
 };
 use clock::ReplicaId;
 use collections::{BTreeMap, HashMap};
-use git::diff::{DiffHunk, DiffHunkStatus};
+use git::diff::DiffHunkStatus;
 use gpui::{
     color::Color,
     elements::*,
@@ -35,7 +35,7 @@ use gpui::{
     WeakViewHandle,
 };
 use json::json;
-use language::{Bias, DiagnosticSeverity, OffsetUtf16, Selection};
+use language::{Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, Point, Selection};
 use project::ProjectPath;
 use settings::{GitGutter, Settings};
 use smallvec::SmallVec;
@@ -46,10 +46,17 @@ use std::{
     ops::{DerefMut, Range},
     sync::Arc,
 };
-use theme::DiffStyle;
+
+#[derive(Debug)]
+struct DiffHunkLayout {
+    visual_range: Range<u32>,
+    status: DiffHunkStatus,
+    is_folded: bool,
+}
 
 struct SelectionLayout {
     head: DisplayPoint,
+    cursor_shape: CursorShape,
     range: Range<DisplayPoint>,
 }
 
@@ -57,6 +64,7 @@ impl SelectionLayout {
     fn new<T: ToPoint + ToDisplayPoint + Clone>(
         selection: Selection<T>,
         line_mode: bool,
+        cursor_shape: CursorShape,
         map: &DisplaySnapshot,
     ) -> Self {
         if line_mode {
@@ -64,6 +72,7 @@ impl SelectionLayout {
             let point_range = map.expand_to_line(selection.range());
             Self {
                 head: selection.head().to_display_point(map),
+                cursor_shape,
                 range: point_range.start.to_display_point(map)
                     ..point_range.end.to_display_point(map),
             }
@@ -71,6 +80,7 @@ impl SelectionLayout {
             let selection = selection.map(|p| p.to_display_point(map));
             Self {
                 head: selection.head(),
+                cursor_shape,
                 range: selection.range(),
             }
         }
@@ -81,19 +91,13 @@ impl SelectionLayout {
 pub struct EditorElement {
     view: WeakViewHandle<Editor>,
     style: Arc<EditorStyle>,
-    cursor_shape: CursorShape,
 }
 
 impl EditorElement {
-    pub fn new(
-        view: WeakViewHandle<Editor>,
-        style: EditorStyle,
-        cursor_shape: CursorShape,
-    ) -> Self {
+    pub fn new(view: WeakViewHandle<Editor>, style: EditorStyle) -> Self {
         Self {
             view,
             style: Arc::new(style),
-            cursor_shape,
         }
     }
 
@@ -525,85 +529,11 @@ impl EditorElement {
         layout: &mut LayoutState,
         cx: &mut PaintContext,
     ) {
-        struct GutterLayout {
-            line_height: f32,
-            // scroll_position: Vector2F,
-            scroll_top: f32,
-            bounds: RectF,
-        }
-
-        struct DiffLayout<'a> {
-            buffer_row: u32,
-            last_diff: Option<&'a DiffHunk<u32>>,
-        }
-
-        fn diff_quad(
-            hunk: &DiffHunk<u32>,
-            gutter_layout: &GutterLayout,
-            diff_style: &DiffStyle,
-        ) -> Quad {
-            let color = match hunk.status() {
-                DiffHunkStatus::Added => diff_style.inserted,
-                DiffHunkStatus::Modified => diff_style.modified,
-
-                //TODO: This rendering is entirely a horrible hack
-                DiffHunkStatus::Removed => {
-                    let row = hunk.buffer_range.start;
-
-                    let offset = gutter_layout.line_height / 2.;
-                    let start_y =
-                        row as f32 * gutter_layout.line_height + offset - gutter_layout.scroll_top;
-                    let end_y = start_y + gutter_layout.line_height;
-
-                    let width = diff_style.removed_width_em * gutter_layout.line_height;
-                    let highlight_origin = gutter_layout.bounds.origin() + vec2f(-width, start_y);
-                    let highlight_size = vec2f(width * 2., end_y - start_y);
-                    let highlight_bounds = RectF::new(highlight_origin, highlight_size);
-
-                    return Quad {
-                        bounds: highlight_bounds,
-                        background: Some(diff_style.deleted),
-                        border: Border::new(0., Color::transparent_black()),
-                        corner_radius: 1. * gutter_layout.line_height,
-                    };
-                }
-            };
-
-            let start_row = hunk.buffer_range.start;
-            let end_row = hunk.buffer_range.end;
-
-            let start_y = start_row as f32 * gutter_layout.line_height - gutter_layout.scroll_top;
-            let end_y = end_row as f32 * gutter_layout.line_height - gutter_layout.scroll_top;
-
-            let width = diff_style.width_em * gutter_layout.line_height;
-            let highlight_origin = gutter_layout.bounds.origin() + vec2f(-width, start_y);
-            let highlight_size = vec2f(width * 2., end_y - start_y);
-            let highlight_bounds = RectF::new(highlight_origin, highlight_size);
-
-            Quad {
-                bounds: highlight_bounds,
-                background: Some(color),
-                border: Border::new(0., Color::transparent_black()),
-                corner_radius: diff_style.corner_radius * gutter_layout.line_height,
-            }
-        }
+        let line_height = layout.position_map.line_height;
 
         let scroll_position = layout.position_map.snapshot.scroll_position();
-        let gutter_layout = {
-            let line_height = layout.position_map.line_height;
-            GutterLayout {
-                scroll_top: scroll_position.y() * line_height,
-                line_height,
-                bounds,
-            }
-        };
-
-        let mut diff_layout = DiffLayout {
-            buffer_row: scroll_position.y() as u32,
-            last_diff: None,
-        };
+        let scroll_top = scroll_position.y() * line_height;
 
-        let diff_style = &cx.global::<Settings>().theme.editor.diff.clone();
         let show_gutter = matches!(
             &cx.global::<Settings>()
                 .git_overrides
@@ -612,55 +542,104 @@ impl EditorElement {
             GitGutter::TrackedFiles
         );
 
-        // line is `None` when there's a line wrap
+        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 * gutter_layout.line_height
-                            - (gutter_layout.scroll_top % gutter_layout.line_height),
+                        ix as f32 * line_height - (scroll_top % line_height),
                     );
 
-                line.paint(line_origin, visible_bounds, gutter_layout.line_height, cx);
+                line.paint(line_origin, visible_bounds, line_height, cx);
+            }
+        }
+
+        if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() {
+            let mut x = bounds.width() - layout.gutter_padding;
+            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, cx);
+        }
+    }
+
+    fn paint_diff_hunks(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) {
+        let diff_style = &cx.global::<Settings>().theme.editor.diff.clone();
+        let line_height = layout.position_map.line_height;
 
-                if show_gutter {
-                    //This line starts a buffer line, so let's do the diff calculation
-                    let new_hunk = get_hunk(diff_layout.buffer_row, &layout.diff_hunks);
+        let scroll_position = layout.position_map.snapshot.scroll_position();
+        let scroll_top = scroll_position.y() * line_height;
 
-                    let (is_ending, is_starting) = match (diff_layout.last_diff, new_hunk) {
-                        (Some(old_hunk), Some(new_hunk)) if new_hunk == old_hunk => (false, false),
-                        (a, b) => (a.is_some(), b.is_some()),
-                    };
+        for hunk in &layout.hunk_layouts {
+            let color = match (hunk.status, hunk.is_folded) {
+                (DiffHunkStatus::Added, false) => diff_style.inserted,
+                (DiffHunkStatus::Modified, false) => diff_style.modified,
 
-                    if is_ending {
-                        let last_hunk = diff_layout.last_diff.take().unwrap();
-                        cx.scene
-                            .push_quad(diff_quad(last_hunk, &gutter_layout, diff_style));
-                    }
+                //TODO: This rendering is entirely a horrible hack
+                (DiffHunkStatus::Removed, false) => {
+                    let row = hunk.visual_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;
 
-                    if is_starting {
-                        let new_hunk = new_hunk.unwrap();
-                        diff_layout.last_diff = Some(new_hunk);
-                    };
+                    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 = RectF::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()),
+                        corner_radius: 1. * line_height,
+                    });
 
-                    diff_layout.buffer_row += 1;
+                    continue;
                 }
-            }
-        }
 
-        // If we ran out with a diff hunk still being prepped, paint it now
-        if let Some(last_hunk) = diff_layout.last_diff {
-            cx.scene
-                .push_quad(diff_quad(last_hunk, &gutter_layout, diff_style))
-        }
+                (_, true) => {
+                    let row = hunk.visual_range.start;
+                    let start_y = row as f32 * line_height - scroll_top;
+                    let end_y = start_y + line_height;
 
-        if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() {
-            let mut x = bounds.width() - layout.gutter_padding;
-            let mut y = *row as f32 * gutter_layout.line_height - gutter_layout.scroll_top;
-            x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
-            y += (gutter_layout.line_height - indicator.size().y()) / 2.;
-            indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx);
+                    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 = RectF::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()),
+                        corner_radius: 1. * line_height,
+                    });
+
+                    continue;
+                }
+            };
+
+            let start_row = hunk.visual_range.start;
+            let end_row = hunk.visual_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 = RectF::new(highlight_origin, highlight_size);
+
+            cx.scene.push_quad(Quad {
+                bounds: highlight_bounds,
+                background: Some(color),
+                border: Border::new(0., Color::transparent_black()),
+                corner_radius: diff_style.corner_radius * line_height,
+            });
         }
     }
 
@@ -726,7 +705,7 @@ impl EditorElement {
                     cx,
                 );
 
-                if view.show_local_cursors() || *replica_id != local_replica_id {
+                if view.show_local_cursors(cx) || *replica_id != local_replica_id {
                     let cursor_position = selection.head;
                     if layout
                         .visible_display_row_range
@@ -742,7 +721,7 @@ impl EditorElement {
                         if block_width == 0.0 {
                             block_width = layout.position_map.em_width;
                         }
-                        let block_text = if let CursorShape::Block = self.cursor_shape {
+                        let block_text = if let CursorShape::Block = selection.cursor_shape {
                             layout
                                 .position_map
                                 .snapshot
@@ -778,7 +757,7 @@ impl EditorElement {
                             block_width,
                             origin: vec2f(x, y),
                             line_height: layout.position_map.line_height,
-                            shape: self.cursor_shape,
+                            shape: selection.cursor_shape,
                             block_text,
                         });
                     }
@@ -1122,6 +1101,75 @@ impl EditorElement {
             .width()
     }
 
+    //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,
+        rows: Range<u32>,
+        snapshot: &EditorSnapshot,
+    ) -> Vec<DiffHunkLayout> {
+        let buffer_snapshot = &snapshot.buffer_snapshot;
+        let visual_start = DisplayPoint::new(rows.start, 0).to_point(snapshot).row;
+        let visual_end = DisplayPoint::new(rows.end, 0).to_point(snapshot).row;
+        let hunks = buffer_snapshot.git_diff_hunks_in_range(visual_start..visual_end);
+
+        let mut layouts = Vec::<DiffHunkLayout>::new();
+
+        for hunk in hunks {
+            let hunk_start_point = Point::new(hunk.buffer_range.start, 0);
+            let hunk_end_point = Point::new(hunk.buffer_range.end, 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(1), 0);
+            let folds_end = Point::new(hunk.buffer_range.end + 1, 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(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)
+            });
+
+            let visual_range = if let Some(fold) = containing_fold {
+                let row = fold.start.to_display_point(snapshot).row();
+                row..row
+            } else {
+                let start = hunk_start_point.to_display_point(snapshot).row();
+                let end = hunk_end_point.to_display_point(snapshot).row();
+                start..end
+            };
+
+            let has_existing_layout = match layouts.last() {
+                Some(e) => visual_range == e.visual_range && e.status == hunk.status(),
+                None => false,
+            };
+
+            if !has_existing_layout {
+                layouts.push(DiffHunkLayout {
+                    visual_range,
+                    status: hunk.status(),
+                    is_folded: containing_fold.is_some(),
+                });
+            }
+        }
+
+        layouts
+    }
+
     fn layout_line_numbers(
         &self,
         rows: Range<u32>,
@@ -1476,27 +1524,6 @@ impl EditorElement {
     }
 }
 
-/// Get the hunk that contains buffer_line, starting from start_idx
-/// Returns none if there is none found, and
-fn get_hunk(buffer_line: u32, hunks: &[DiffHunk<u32>]) -> Option<&DiffHunk<u32>> {
-    for i in 0..hunks.len() {
-        // Safety: Index out of bounds is handled by the check above
-        let hunk = hunks.get(i).unwrap();
-        if hunk.buffer_range.contains(&(buffer_line as u32)) {
-            return Some(hunk);
-        } else if hunk.status() == DiffHunkStatus::Removed && buffer_line == hunk.buffer_range.start
-        {
-            return Some(hunk);
-        } else if hunk.buffer_range.start > buffer_line as u32 {
-            // If we've passed the buffer_line, just stop
-            return None;
-        }
-    }
-
-    // We reached the end of the array without finding a hunk, just return none.
-    return None;
-}
-
 impl Element for EditorElement {
     type LayoutState = LayoutState;
     type PaintState = ();
@@ -1622,7 +1649,7 @@ impl Element for EditorElement {
             );
 
             let mut remote_selections = HashMap::default();
-            for (replica_id, line_mode, selection) in display_map
+            for (replica_id, line_mode, cursor_shape, selection) in display_map
                 .buffer_snapshot
                 .remote_selections_in_range(&(start_anchor.clone()..end_anchor.clone()))
             {
@@ -1633,7 +1660,12 @@ impl Element for EditorElement {
                 remote_selections
                     .entry(replica_id)
                     .or_insert(Vec::new())
-                    .push(SelectionLayout::new(selection, line_mode, &display_map));
+                    .push(SelectionLayout::new(
+                        selection,
+                        line_mode,
+                        cursor_shape,
+                        &display_map,
+                    ));
             }
             selections.extend(remote_selections);
 
@@ -1665,7 +1697,12 @@ impl Element for EditorElement {
                     local_selections
                         .into_iter()
                         .map(|selection| {
-                            SelectionLayout::new(selection, view.selections.line_mode, &display_map)
+                            SelectionLayout::new(
+                                selection,
+                                view.selections.line_mode,
+                                view.cursor_shape,
+                                &display_map,
+                            )
                         })
                         .collect(),
                 ));
@@ -1682,10 +1719,7 @@ impl Element for EditorElement {
         let line_number_layouts =
             self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx);
 
-        let diff_hunks = snapshot
-            .buffer_snapshot
-            .git_diff_hunks_in_range(start_row..end_row)
-            .collect();
+        let hunk_layouts = self.layout_git_gutters(start_row..end_row, &snapshot);
 
         let scrollbar_row_range = scroll_position.y()..(scroll_position.y() + height_in_lines);
 
@@ -1844,7 +1878,7 @@ impl Element for EditorElement {
                 highlighted_rows,
                 highlighted_ranges,
                 line_number_layouts,
-                diff_hunks,
+                hunk_layouts,
                 blocks,
                 selections,
                 context_menu,
@@ -1982,6 +2016,7 @@ pub struct LayoutState {
     active_rows: BTreeMap<u32, bool>,
     highlighted_rows: Option<Range<u32>>,
     line_number_layouts: Vec<Option<text_layout::Line>>,
+    hunk_layouts: Vec<DiffHunkLayout>,
     blocks: Vec<BlockLayout>,
     highlighted_ranges: Vec<(Range<DisplayPoint>, Color)>,
     selections: Vec<(ReplicaId, Vec<SelectionLayout>)>,
@@ -1989,7 +2024,6 @@ pub struct LayoutState {
     show_scrollbars: bool,
     max_row: u32,
     context_menu: Option<(DisplayPoint, ElementBox)>,
-    diff_hunks: Vec<DiffHunk<u32>>,
     code_actions_indicator: Option<(u32, ElementBox)>,
     hover_popovers: Option<(DisplayPoint, Vec<ElementBox>)>,
 }
@@ -2077,20 +2111,6 @@ fn layout_line(
     )
 }
 
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-pub enum CursorShape {
-    Bar,
-    Block,
-    Underscore,
-    Hollow,
-}
-
-impl Default for CursorShape {
-    fn default() -> Self {
-        CursorShape::Bar
-    }
-}
-
 #[derive(Debug)]
 pub struct Cursor {
     origin: Vector2F,
@@ -2331,11 +2351,7 @@ mod tests {
         let (window_id, editor) = cx.add_window(Default::default(), |cx| {
             Editor::new(EditorMode::Full, buffer, None, None, cx)
         });
-        let element = EditorElement::new(
-            editor.downgrade(),
-            editor.read(cx).style(cx),
-            CursorShape::Bar,
-        );
+        let element = EditorElement::new(editor.downgrade(), editor.read(cx).style(cx));
 
         let layouts = editor.update(cx, |editor, cx| {
             let snapshot = editor.snapshot(cx);
@@ -2371,11 +2387,7 @@ mod tests {
             cx.blur();
         });
 
-        let mut element = EditorElement::new(
-            editor.downgrade(),
-            editor.read(cx).style(cx),
-            CursorShape::Bar,
-        );
+        let mut element = EditorElement::new(editor.downgrade(), editor.read(cx).style(cx));
 
         let mut scene = Scene::new(1.0);
         let mut presenter = cx.build_presenter(window_id, 30., Default::default());

crates/editor/src/items.rs 🔗

@@ -120,6 +120,7 @@ impl FollowableItem for Editor {
                     buffer.set_active_selections(
                         &self.selections.disjoint_anchors(),
                         self.selections.line_mode,
+                        self.cursor_shape,
                         cx,
                     );
                 }

crates/editor/src/multi_buffer.rs 🔗

@@ -8,7 +8,7 @@ use git::diff::DiffHunk;
 use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
 pub use language::Completion;
 use language::{
-    char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk,
+    char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
     DiagnosticEntry, Event, File, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline,
     OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _,
     ToPoint as _, ToPointUtf16 as _, TransactionId,
@@ -143,6 +143,7 @@ struct ExcerptSummary {
     text: TextSummary,
 }
 
+#[derive(Clone)]
 pub struct MultiBufferRows<'a> {
     buffer_row_range: Range<u32>,
     excerpts: Cursor<'a, Excerpt, Point>,
@@ -603,6 +604,7 @@ impl MultiBuffer {
         &mut self,
         selections: &[Selection<Anchor>],
         line_mode: bool,
+        cursor_shape: CursorShape,
         cx: &mut ModelContext<Self>,
     ) {
         let mut selections_by_buffer: HashMap<usize, Vec<Selection<text::Anchor>>> =
@@ -667,7 +669,7 @@ impl MultiBuffer {
                         }
                         Some(selection)
                     }));
-                    buffer.set_active_selections(merged_selections, line_mode, cx);
+                    buffer.set_active_selections(merged_selections, line_mode, cursor_shape, cx);
                 });
         }
     }
@@ -2697,7 +2699,7 @@ impl MultiBufferSnapshot {
     pub fn remote_selections_in_range<'a>(
         &'a self,
         range: &'a Range<Anchor>,
-    ) -> impl 'a + Iterator<Item = (ReplicaId, bool, Selection<Anchor>)> {
+    ) -> impl 'a + Iterator<Item = (ReplicaId, bool, CursorShape, Selection<Anchor>)> {
         let mut cursor = self.excerpts.cursor::<Option<&ExcerptId>>();
         cursor.seek(&Some(&range.start.excerpt_id), Bias::Left, &());
         cursor
@@ -2714,7 +2716,7 @@ impl MultiBufferSnapshot {
                 excerpt
                     .buffer
                     .remote_selections_in_range(query_range)
-                    .flat_map(move |(replica_id, line_mode, selections)| {
+                    .flat_map(move |(replica_id, line_mode, cursor_shape, selections)| {
                         selections.map(move |selection| {
                             let mut start = Anchor {
                                 buffer_id: Some(excerpt.buffer_id),
@@ -2736,6 +2738,7 @@ impl MultiBufferSnapshot {
                             (
                                 replica_id,
                                 line_mode,
+                                cursor_shape,
                                 Selection {
                                     id: selection.id,
                                     start,

crates/git/src/diff.rs 🔗

@@ -190,7 +190,6 @@ impl BufferDiff {
             }
 
             if kind == GitDiffLineType::Deletion {
-                *buffer_row_divergence -= 1;
                 let end = content_offset + content_len;
 
                 match &mut head_byte_range {
@@ -203,6 +202,8 @@ impl BufferDiff {
                     let row = old_row as i64 + *buffer_row_divergence;
                     first_deletion_buffer_row = Some(row as u32);
                 }
+
+                *buffer_row_divergence -= 1;
             }
         }
 

crates/language/src/buffer.rs 🔗

@@ -111,9 +111,19 @@ pub enum IndentKind {
     Tab,
 }
 
+#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
+pub enum CursorShape {
+    #[default]
+    Bar,
+    Block,
+    Underscore,
+    Hollow,
+}
+
 #[derive(Clone, Debug)]
 struct SelectionSet {
     line_mode: bool,
+    cursor_shape: CursorShape,
     selections: Arc<[Selection<Anchor>]>,
     lamport_timestamp: clock::Lamport,
 }
@@ -161,6 +171,7 @@ pub enum Operation {
         selections: Arc<[Selection<Anchor>]>,
         lamport_timestamp: clock::Lamport,
         line_mode: bool,
+        cursor_shape: CursorShape,
     },
     UpdateCompletionTriggers {
         triggers: Vec<String>,
@@ -395,6 +406,7 @@ impl Buffer {
                 selections: set.selections.clone(),
                 lamport_timestamp: set.lamport_timestamp,
                 line_mode: set.line_mode,
+                cursor_shape: set.cursor_shape,
             })
         }));
         operations.push(proto::serialize_operation(&Operation::UpdateDiagnostics {
@@ -1227,6 +1239,7 @@ impl Buffer {
         &mut self,
         selections: Arc<[Selection<Anchor>]>,
         line_mode: bool,
+        cursor_shape: CursorShape,
         cx: &mut ModelContext<Self>,
     ) {
         let lamport_timestamp = self.text.lamport_clock.tick();
@@ -1236,6 +1249,7 @@ impl Buffer {
                 selections: selections.clone(),
                 lamport_timestamp,
                 line_mode,
+                cursor_shape,
             },
         );
         self.send_operation(
@@ -1243,13 +1257,14 @@ impl Buffer {
                 selections,
                 line_mode,
                 lamport_timestamp,
+                cursor_shape,
             },
             cx,
         );
     }
 
     pub fn remove_active_selections(&mut self, cx: &mut ModelContext<Self>) {
-        self.set_active_selections(Arc::from([]), false, cx);
+        self.set_active_selections(Arc::from([]), false, Default::default(), cx);
     }
 
     pub fn set_text<T>(&mut self, text: T, cx: &mut ModelContext<Self>) -> Option<clock::Local>
@@ -1474,6 +1489,7 @@ impl Buffer {
                 selections,
                 lamport_timestamp,
                 line_mode,
+                cursor_shape,
             } => {
                 if let Some(set) = self.remote_selections.get(&lamport_timestamp.replica_id) {
                     if set.lamport_timestamp > lamport_timestamp {
@@ -1487,6 +1503,7 @@ impl Buffer {
                         selections,
                         lamport_timestamp,
                         line_mode,
+                        cursor_shape,
                     },
                 );
                 self.text.lamport_clock.observe(lamport_timestamp);
@@ -2236,6 +2253,7 @@ impl BufferSnapshot {
         Item = (
             ReplicaId,
             bool,
+            CursorShape,
             impl Iterator<Item = &Selection<Anchor>> + '_,
         ),
     > + '_ {
@@ -2259,6 +2277,7 @@ impl BufferSnapshot {
                 (
                     *replica_id,
                     set.line_mode,
+                    set.cursor_shape,
                     set.selections[start_ix..end_ix].iter(),
                 )
             })

crates/language/src/buffer_tests.rs 🔗

@@ -1283,7 +1283,7 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) {
                         selections
                     );
                     active_selections.insert(replica_id, selections.clone());
-                    buffer.set_active_selections(selections, false, cx);
+                    buffer.set_active_selections(selections, false, Default::default(), cx);
                 });
                 mutation_count -= 1;
             }
@@ -1448,7 +1448,7 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) {
         let buffer = buffer.read(cx).snapshot();
         let actual_remote_selections = buffer
             .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
-            .map(|(replica_id, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
+            .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
             .collect::<Vec<_>>();
         let expected_remote_selections = active_selections
             .iter()

crates/language/src/proto.rs 🔗

@@ -1,5 +1,6 @@
 use crate::{
-    diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, Diagnostic, Language,
+    diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic,
+    Language,
 };
 use anyhow::{anyhow, Result};
 use clock::ReplicaId;
@@ -52,11 +53,13 @@ pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation {
                 selections,
                 line_mode,
                 lamport_timestamp,
+                cursor_shape,
             } => proto::operation::Variant::UpdateSelections(proto::operation::UpdateSelections {
                 replica_id: lamport_timestamp.replica_id as u32,
                 lamport_timestamp: lamport_timestamp.value,
                 selections: serialize_selections(selections),
                 line_mode: *line_mode,
+                cursor_shape: serialize_cursor_shape(cursor_shape) as i32,
             }),
             crate::Operation::UpdateDiagnostics {
                 diagnostics,
@@ -125,6 +128,24 @@ pub fn serialize_selection(selection: &Selection<Anchor>) -> proto::Selection {
     }
 }
 
+pub fn serialize_cursor_shape(cursor_shape: &CursorShape) -> proto::CursorShape {
+    match cursor_shape {
+        CursorShape::Bar => proto::CursorShape::CursorBar,
+        CursorShape::Block => proto::CursorShape::CursorBlock,
+        CursorShape::Underscore => proto::CursorShape::CursorUnderscore,
+        CursorShape::Hollow => proto::CursorShape::CursorHollow,
+    }
+}
+
+pub fn deserialize_cursor_shape(cursor_shape: proto::CursorShape) -> CursorShape {
+    match cursor_shape {
+        proto::CursorShape::CursorBar => CursorShape::Bar,
+        proto::CursorShape::CursorBlock => CursorShape::Block,
+        proto::CursorShape::CursorUnderscore => CursorShape::Underscore,
+        proto::CursorShape::CursorHollow => CursorShape::Hollow,
+    }
+}
+
 pub fn serialize_diagnostics<'a>(
     diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<Anchor>>,
 ) -> Vec<proto::Diagnostic> {
@@ -223,6 +244,10 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operati
                     },
                     selections: Arc::from(selections),
                     line_mode: message.line_mode,
+                    cursor_shape: deserialize_cursor_shape(
+                        proto::CursorShape::from_i32(message.cursor_shape)
+                            .ok_or_else(|| anyhow!("Missing cursor shape"))?,
+                    ),
                 }
             }
             proto::operation::Variant::UpdateDiagnostics(message) => {

crates/rpc/proto/zed.proto 🔗

@@ -909,6 +909,7 @@ message SelectionSet {
     repeated Selection selections = 2;
     uint32 lamport_timestamp = 3;
     bool line_mode = 4;
+    CursorShape cursor_shape = 5;
 }
 
 message Selection {
@@ -918,6 +919,13 @@ message Selection {
     bool reversed = 4;
 }
 
+enum CursorShape {
+    CursorBar = 0;
+    CursorBlock = 1;
+    CursorUnderscore = 2;
+    CursorHollow = 3;
+}
+
 message Anchor {
     uint32 replica_id = 1;
     uint32 local_timestamp = 2;
@@ -983,6 +991,7 @@ message Operation {
         uint32 lamport_timestamp = 2;
         repeated Selection selections = 3;
         bool line_mode = 4;
+        CursorShape cursor_shape = 5;
     }
 
     message UpdateCompletionTriggers {

crates/rpc/src/rpc.rs 🔗

@@ -6,4 +6,4 @@ pub use conn::Connection;
 pub use peer::*;
 mod macros;
 
-pub const PROTOCOL_VERSION: u32 = 37;
+pub const PROTOCOL_VERSION: u32 = 37;

crates/settings/src/settings.rs 🔗

@@ -28,6 +28,7 @@ pub struct Settings {
     pub buffer_font_family: FamilyId,
     pub default_buffer_font_size: f32,
     pub buffer_font_size: f32,
+    pub cursor_blink: bool,
     pub hover_popover_enabled: bool,
     pub show_completions_on_input: bool,
     pub vim_mode: bool,
@@ -234,6 +235,8 @@ pub struct SettingsFileContent {
     #[serde(default)]
     pub buffer_font_size: Option<f32>,
     #[serde(default)]
+    pub cursor_blink: Option<bool>,
+    #[serde(default)]
     pub hover_popover_enabled: Option<bool>,
     #[serde(default)]
     pub show_completions_on_input: Option<bool>,
@@ -292,6 +295,7 @@ impl Settings {
                 .unwrap(),
             buffer_font_size: defaults.buffer_font_size.unwrap(),
             default_buffer_font_size: defaults.buffer_font_size.unwrap(),
+            cursor_blink: defaults.cursor_blink.unwrap(),
             hover_popover_enabled: defaults.hover_popover_enabled.unwrap(),
             show_completions_on_input: defaults.show_completions_on_input.unwrap(),
             projects_online_by_default: defaults.projects_online_by_default.unwrap(),
@@ -346,6 +350,7 @@ impl Settings {
         );
         merge(&mut self.buffer_font_size, data.buffer_font_size);
         merge(&mut self.default_buffer_font_size, data.buffer_font_size);
+        merge(&mut self.cursor_blink, data.cursor_blink);
         merge(&mut self.hover_popover_enabled, data.hover_popover_enabled);
         merge(
             &mut self.show_completions_on_input,
@@ -436,6 +441,7 @@ impl Settings {
             buffer_font_family: cx.font_cache().load_family(&["Monaco"]).unwrap(),
             buffer_font_size: 14.,
             default_buffer_font_size: 14.,
+            cursor_blink: true,
             hover_popover_enabled: true,
             show_completions_on_input: true,
             vim_mode: false,

crates/terminal/Cargo.toml 🔗

@@ -8,16 +8,17 @@ path = "src/terminal.rs"
 doctest = false
 
 [dependencies]
-alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "a51dbe25d67e84d6ed4261e640d3954fbdd9be45" }
-procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
+context_menu = { path = "../context_menu" }
 editor = { path = "../editor" }
-util = { path = "../util" }
+language = { path = "../language" }
 gpui = { path = "../gpui" }
-theme = { path = "../theme" }
+project = { path = "../project" }
 settings = { path = "../settings" }
+theme = { path = "../theme" }
+util = { path = "../util" }
 workspace = { path = "../workspace" }
-project = { path = "../project" }
-context_menu = { path = "../context_menu" }
+alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "a51dbe25d67e84d6ed4261e640d3954fbdd9be45" }
+procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
 smallvec = { version = "1.6", features = ["union"] }
 smol = "1.2.5"
 mio-extras = "2.0.6"

crates/terminal/src/terminal.rs 🔗

@@ -1018,55 +1018,34 @@ impl Terminal {
             self.last_content.size,
             self.last_content.display_offset,
         );
-        // let side = mouse_side(position, self.last_content.size);
 
         if self.mouse_mode(e.shift) {
             if let Some(bytes) = mouse_button_report(point, e, true, self.last_content.mode) {
                 self.pty_tx.notify(bytes);
             }
         } else if e.button == MouseButton::Left {
-            self.left_click(e, origin)
-        }
-    }
-
-    pub fn left_click(&mut self, e: &DownRegionEvent, origin: Vector2F) {
-        let position = e.position.sub(origin);
-        if !self.mouse_mode(e.shift) {
-            //Hyperlinks
-            {
-                let mouse_cell_index = content_index_for_mouse(position, &self.last_content);
-                if let Some(link) = self.last_content.cells[mouse_cell_index].hyperlink() {
-                    open_uri(link.uri()).log_err();
-                } else {
-                    self.events
-                        .push_back(InternalEvent::FindHyperlink(position, true));
-                }
-            }
+            let position = e.position.sub(origin);
+            let point = grid_point(
+                position,
+                self.last_content.size,
+                self.last_content.display_offset,
+            );
+            let side = mouse_side(position, self.last_content.size);
 
-            // Selections
-            {
-                let point = grid_point(
-                    position,
-                    self.last_content.size,
-                    self.last_content.display_offset,
-                );
-                let side = mouse_side(position, self.last_content.size);
-
-                let selection_type = match e.click_count {
-                    0 => return, //This is a release
-                    1 => Some(SelectionType::Simple),
-                    2 => Some(SelectionType::Semantic),
-                    3 => Some(SelectionType::Lines),
-                    _ => None,
-                };
+            let selection_type = match e.click_count {
+                0 => return, //This is a release
+                1 => Some(SelectionType::Simple),
+                2 => Some(SelectionType::Semantic),
+                3 => Some(SelectionType::Lines),
+                _ => None,
+            };
 
-                let selection = selection_type
-                    .map(|selection_type| Selection::new(selection_type, point, side));
+            let selection =
+                selection_type.map(|selection_type| Selection::new(selection_type, point, side));
 
-                if let Some(sel) = selection {
-                    self.events
-                        .push_back(InternalEvent::SetSelection(Some((sel, point))));
-                }
+            if let Some(sel) = selection {
+                self.events
+                    .push_back(InternalEvent::SetSelection(Some((sel, point))));
             }
         }
     }
@@ -1094,8 +1073,21 @@ impl Terminal {
             if let Some(bytes) = mouse_button_report(point, e, false, self.last_content.mode) {
                 self.pty_tx.notify(bytes);
             }
-        } else if e.button == MouseButton::Left && copy_on_select {
-            self.copy();
+        } else {
+            if e.button == MouseButton::Left && copy_on_select {
+                self.copy();
+            }
+
+            //Hyperlinks
+            if self.selection_phase == SelectionPhase::Ended {
+                let mouse_cell_index = content_index_for_mouse(position, &self.last_content);
+                if let Some(link) = self.last_content.cells[mouse_cell_index].hyperlink() {
+                    open_uri(link.uri()).log_err();
+                } else {
+                    self.events
+                        .push_back(InternalEvent::FindHyperlink(position, true));
+                }
+            }
         }
 
         self.selection_phase = SelectionPhase::Ended;

crates/terminal/src/terminal_element.rs 🔗

@@ -4,7 +4,7 @@ use alacritty_terminal::{
     index::Point,
     term::{cell::Flags, TermMode},
 };
-use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine};
+use editor::{Cursor, HighlightedRange, HighlightedRangeLine};
 use gpui::{
     color::Color,
     elements::{Empty, Overlay},
@@ -20,6 +20,7 @@ use gpui::{
     WeakViewHandle,
 };
 use itertools::Itertools;
+use language::CursorShape;
 use ordered_float::OrderedFloat;
 use settings::Settings;
 use theme::TerminalStyle;

crates/vim/src/state.rs 🔗

@@ -1,5 +1,5 @@
-use editor::CursorShape;
 use gpui::keymap::Context;
+use language::CursorShape;
 use serde::{Deserialize, Serialize};
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]

crates/vim/src/vim.rs 🔗

@@ -12,8 +12,9 @@ mod visual;
 
 use collections::HashMap;
 use command_palette::CommandPaletteFilter;
-use editor::{Bias, Cancel, CursorShape, Editor};
+use editor::{Bias, Cancel, Editor};
 use gpui::{impl_actions, MutableAppContext, Subscription, ViewContext, WeakViewHandle};
+use language::CursorShape;
 use serde::Deserialize;
 
 use settings::Settings;