New excerpt controls (#24428)

Conrad Irwin , Nate Butler , and Zed AI created

Release Notes:

- Multibuffers now use less vertical space for excerpt boundaries.
Additionally the expand up/down arrows are hidden at the start and end
of the buffers

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
Co-authored-by: Zed AI <claude-3.5-sonnet@zed.dev>

Change summary

assets/icons/expand_down.svg                      |   4 
assets/icons/expand_up.svg                        |   4 
crates/assistant/src/inline_assistant.rs          |   3 
crates/assistant/src/terminal_inline_assistant.rs |   1 
crates/assistant2/src/active_thread.rs            |   1 
crates/assistant2/src/assistant_panel.rs          |   9 
crates/assistant2/src/inline_assistant.rs         |   2 
crates/assistant2/src/inline_prompt_editor.rs     |   2 
crates/auto_update_ui/src/auto_update_ui.rs       |   2 
crates/collab/src/tests/following_tests.rs        |   5 
crates/copilot/src/copilot_completion_provider.rs |  16 
crates/diagnostics/src/diagnostics.rs             |   9 
crates/diagnostics/src/diagnostics_tests.rs       |  89 +--
crates/editor/src/display_map.rs                  |  48 -
crates/editor/src/display_map/block_map.rs        | 136 ----
crates/editor/src/display_map/wrap_map.rs         |   1 
crates/editor/src/editor.rs                       |  28 
crates/editor/src/editor_tests.rs                 |  66 -
crates/editor/src/element.rs                      | 436 ++++++----------
crates/editor/src/inlay_hint_cache.rs             |   5 
crates/editor/src/items.rs                        |   9 
crates/editor/src/movement.rs                     |  46 
crates/editor/src/proposed_changes_editor.rs      |   3 
crates/editor/src/rust_analyzer_ext.rs            |   2 
crates/editor/src/test.rs                         |   6 
crates/git_ui/src/git_panel.rs                    |   1 
crates/git_ui/src/project_diff.rs                 |   9 
crates/multi_buffer/src/multi_buffer.rs           |  95 +++
crates/multi_buffer/src/multi_buffer_tests.rs     |  65 ++
crates/outline_panel/src/outline_panel.rs         |  47 -
crates/repl/src/notebook/cell.rs                  |   1 
crates/repl/src/outputs.rs                        |   8 
crates/search/src/buffer_search.rs                |   2 
crates/search/src/project_search.rs               |  35 
crates/ui/src/components/icon.rs                  |   2 
crates/vim/src/test.rs                            |   9 
crates/zed/src/zed.rs                             |  13 
37 files changed, 513 insertions(+), 707 deletions(-)

Detailed changes

assets/icons/expand_down.svg 🔗

@@ -0,0 +1,4 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M10.5 8.5L7.5 11.5M7.5 11.5L4.5 8.5M7.5 11.5L7.5 5.5" stroke="black" stroke-linecap="square"/>
+<path d="M5 3.5L10 3.5" stroke="black"/>
+</svg>

assets/icons/expand_up.svg 🔗

@@ -0,0 +1,4 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M4.5 6.5L7.5 3.5M7.5 3.5L10.5 6.5M7.5 3.5V9.5" stroke="black" stroke-linecap="square"/>
+<path d="M5 11.5H10" stroke="black"/>
+</svg>

crates/assistant/src/inline_assistant.rs 🔗

@@ -1246,7 +1246,7 @@ impl InlineAssistant {
                     });
 
                     enum DeletedLines {}
-                    let mut editor = Editor::for_multibuffer(multi_buffer, None, true, window, cx);
+                    let mut editor = Editor::for_multibuffer(multi_buffer, None, window, cx);
                     editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
                     editor.set_show_wrap_guides(false, cx);
                     editor.set_show_gutter(false, cx);
@@ -1693,7 +1693,6 @@ impl PromptEditor {
                 },
                 prompt_buffer,
                 None,
-                false,
                 window,
                 cx,
             );

crates/assistant2/src/assistant_panel.rs 🔗

@@ -456,13 +456,8 @@ impl AssistantPanel {
 
                 workspace.add_item_to_active_pane(
                     Box::new(cx.new(|cx| {
-                        let mut editor = Editor::for_multibuffer(
-                            buffer,
-                            Some(project.clone()),
-                            true,
-                            window,
-                            cx,
-                        );
+                        let mut editor =
+                            Editor::for_multibuffer(buffer, Some(project.clone()), window, cx);
                         editor.set_breadcrumb_header(thread_summary);
                         editor
                     })),

crates/assistant2/src/inline_assistant.rs 🔗

@@ -1341,7 +1341,7 @@ impl InlineAssistant {
                     });
 
                     enum DeletedLines {}
-                    let mut editor = Editor::for_multibuffer(multi_buffer, None, true, window, cx);
+                    let mut editor = Editor::for_multibuffer(multi_buffer, None, window, cx);
                     editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
                     editor.set_show_wrap_guides(false, cx);
                     editor.set_show_gutter(false, cx);

crates/assistant2/src/inline_prompt_editor.rs 🔗

@@ -843,7 +843,6 @@ impl PromptEditor<BufferCodegen> {
                 },
                 prompt_buffer,
                 None,
-                false,
                 window,
                 cx,
             );
@@ -1001,7 +1000,6 @@ impl PromptEditor<TerminalCodegen> {
                 },
                 prompt_buffer,
                 None,
-                false,
                 window,
                 cx,
             );

crates/auto_update_ui/src/auto_update_ui.rs 🔗

@@ -93,7 +93,7 @@ fn view_release_notes_locally(
 
                             let tab_description = SharedString::from(body.title.to_string());
                             let editor = cx.new(|cx| {
-                                Editor::for_multibuffer(buffer, Some(project), true, window, cx)
+                                Editor::for_multibuffer(buffer, Some(project), window, cx)
                             });
                             let workspace_handle = workspace.weak_handle();
                             let markdown_preview: Entity<MarkdownPreviewView> =

crates/collab/src/tests/following_tests.rs 🔗

@@ -313,9 +313,8 @@ async fn test_basic_following(
         result
     });
     let multibuffer_editor_a = workspace_a.update_in(cx_a, |workspace, window, cx| {
-        let editor = cx.new(|cx| {
-            Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), true, window, cx)
-        });
+        let editor = cx
+            .new(|cx| Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), window, cx));
         workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
         editor
     });

crates/copilot/src/copilot_completion_provider.rs 🔗

@@ -745,8 +745,8 @@ mod tests {
             );
             multibuffer
         });
-        let editor = cx
-            .add_window(|window, cx| Editor::for_multibuffer(multibuffer, None, true, window, cx));
+        let editor =
+            cx.add_window(|window, cx| Editor::for_multibuffer(multibuffer, None, window, cx));
         editor
             .update(cx, |editor, window, cx| {
                 use gpui::Focusable;
@@ -781,7 +781,7 @@ mod tests {
             assert!(editor.has_active_inline_completion());
             assert_eq!(
                 editor.display_text(cx),
-                "\n\n\na = 1\nb = 2 + a\n\n\n\n\n\nc = 3\nd = 4\n\n"
+                "\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");
         });
@@ -803,7 +803,7 @@ mod tests {
             assert!(!editor.has_active_inline_completion());
             assert_eq!(
                 editor.display_text(cx),
-                "\n\n\na = 1\nb = 2\n\n\n\n\n\nc = 3\nd = 4\n\n"
+                "\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");
 
@@ -812,7 +812,7 @@ mod tests {
             assert!(!editor.has_active_inline_completion());
             assert_eq!(
                 editor.display_text(cx),
-                "\n\n\na = 1\nb = 2\n\n\n\n\n\nc = 3\nd = 4 \n\n"
+                "\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");
         });
@@ -823,7 +823,7 @@ mod tests {
             assert!(editor.has_active_inline_completion());
             assert_eq!(
                 editor.display_text(cx),
-                "\n\n\na = 1\nb = 2\n\n\n\n\n\nc = 3\nd = 4 + c\n\n"
+                "\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");
         });
@@ -997,8 +997,8 @@ mod tests {
             );
             multibuffer
         });
-        let editor = cx
-            .add_window(|window, cx| Editor::for_multibuffer(multibuffer, None, true, window, cx));
+        let editor =
+            cx.add_window(|window, cx| Editor::for_multibuffer(multibuffer, None, window, cx));
         editor
             .update(cx, |editor, window, cx| {
                 use gpui::Focusable;

crates/diagnostics/src/diagnostics.rs 🔗

@@ -198,13 +198,8 @@ impl ProjectDiagnosticsEditor {
 
         let excerpts = cx.new(|cx| MultiBuffer::new(project_handle.read(cx).capability()));
         let editor = cx.new(|cx| {
-            let mut editor = Editor::for_multibuffer(
-                excerpts.clone(),
-                Some(project_handle.clone()),
-                true,
-                window,
-                cx,
-            );
+            let mut editor =
+                Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), window, cx);
             editor.set_vertical_scroll_margin(5, cx);
             editor.disable_inline_diagnostics();
             editor

crates/diagnostics/src/diagnostics_tests.rs 🔗

@@ -169,10 +169,10 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
         editor_blocks(&editor, cx),
         [
             (DisplayRow(0), FILE_HEADER.into()),
-            (DisplayRow(3), DIAGNOSTIC_HEADER.into()),
-            (DisplayRow(16), EXCERPT_HEADER.into()),
-            (DisplayRow(18), DIAGNOSTIC_HEADER.into()),
-            (DisplayRow(27), EXCERPT_HEADER.into()),
+            (DisplayRow(2), DIAGNOSTIC_HEADER.into()),
+            (DisplayRow(15), EXCERPT_HEADER.into()),
+            (DisplayRow(16), DIAGNOSTIC_HEADER.into()),
+            (DisplayRow(25), EXCERPT_HEADER.into()),
         ]
     );
     assert_eq!(
@@ -186,7 +186,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
             // diagnostic group 1
             "\n", // primary message
             "\n", // padding
-            "\n", // expand
             "    let x = vec![];\n",
             "    let y = vec![];\n",
             "\n", // supporting diagnostic
@@ -198,7 +197,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
             "    c(y);\n",
             "\n", // supporting diagnostic
             "    d(x);\n",
-            "\n", // expand
             "\n", // context ellipsis
             // diagnostic group 2
             "\n", // primary message
@@ -210,13 +208,11 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
             "    a(x);\n",
             "\n", // supporting diagnostic
             "    b(y);\n",
-            "\n", // expand
             "\n", // context ellipsis
             "    c(y);\n",
             "    d(x);\n",
             "\n", // supporting diagnostic
             "}",
-            "\n", // expand
         )
     );
 
@@ -224,7 +220,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
     editor.update(cx, |editor, cx| {
         assert_eq!(
             editor.selections.display_ranges(cx),
-            [DisplayPoint::new(DisplayRow(13), 6)..DisplayPoint::new(DisplayRow(13), 6)]
+            [DisplayPoint::new(DisplayRow(12), 6)..DisplayPoint::new(DisplayRow(12), 6)]
         );
     });
 
@@ -260,12 +256,12 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
         editor_blocks(&editor, cx),
         [
             (DisplayRow(0), FILE_HEADER.into()),
-            (DisplayRow(3), DIAGNOSTIC_HEADER.into()),
-            (DisplayRow(8), FILE_HEADER.into()),
-            (DisplayRow(12), DIAGNOSTIC_HEADER.into()),
-            (DisplayRow(25), EXCERPT_HEADER.into()),
-            (DisplayRow(27), DIAGNOSTIC_HEADER.into()),
-            (DisplayRow(36), EXCERPT_HEADER.into()),
+            (DisplayRow(2), DIAGNOSTIC_HEADER.into()),
+            (DisplayRow(7), FILE_HEADER.into()),
+            (DisplayRow(9), DIAGNOSTIC_HEADER.into()),
+            (DisplayRow(22), EXCERPT_HEADER.into()),
+            (DisplayRow(23), DIAGNOSTIC_HEADER.into()),
+            (DisplayRow(32), EXCERPT_HEADER.into()),
         ]
     );
 
@@ -280,7 +276,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
             // diagnostic group 1
             "\n", // primary message
             "\n", // padding
-            "\n", // expand
             "const a: i32 = 'a';\n",
             "\n", // supporting diagnostic
             "const b: i32 = c;\n",
@@ -292,8 +287,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
             // diagnostic group 1
             "\n", // primary message
             "\n", // padding
-            "\n", // expand
-            "\n", // expand
             "    let x = vec![];\n",
             "    let y = vec![];\n",
             "\n", // supporting diagnostic
@@ -309,7 +302,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
             // diagnostic group 2
             "\n", // primary message
             "\n", // filename
-            "\n", // expand
             "fn main() {\n",
             "    let x = vec![];\n",
             "\n", // supporting diagnostic
@@ -317,13 +309,11 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
             "    a(x);\n",
             "\n", // supporting diagnostic
             "    b(y);\n",
-            "\n", // expand
             "\n", // context ellipsis
             "    c(y);\n",
             "    d(x);\n",
             "\n", // supporting diagnostic
             "}",
-            "\n", // expand
         )
     );
 
@@ -331,7 +321,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
     editor.update(cx, |editor, cx| {
         assert_eq!(
             editor.selections.display_ranges(cx),
-            [DisplayPoint::new(DisplayRow(22), 6)..DisplayPoint::new(DisplayRow(22), 6)]
+            [DisplayPoint::new(DisplayRow(19), 6)..DisplayPoint::new(DisplayRow(19), 6)]
         );
     });
 
@@ -380,14 +370,14 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
         editor_blocks(&editor, cx),
         [
             (DisplayRow(0), FILE_HEADER.into()),
-            (DisplayRow(3), DIAGNOSTIC_HEADER.into()),
-            (DisplayRow(8), EXCERPT_HEADER.into()),
-            (DisplayRow(10), DIAGNOSTIC_HEADER.into()),
-            (DisplayRow(15), FILE_HEADER.into()),
-            (DisplayRow(19), DIAGNOSTIC_HEADER.into()),
-            (DisplayRow(32), EXCERPT_HEADER.into()),
-            (DisplayRow(34), DIAGNOSTIC_HEADER.into()),
-            (DisplayRow(43), EXCERPT_HEADER.into()),
+            (DisplayRow(2), DIAGNOSTIC_HEADER.into()),
+            (DisplayRow(7), EXCERPT_HEADER.into()),
+            (DisplayRow(8), DIAGNOSTIC_HEADER.into()),
+            (DisplayRow(13), FILE_HEADER.into()),
+            (DisplayRow(15), DIAGNOSTIC_HEADER.into()),
+            (DisplayRow(28), EXCERPT_HEADER.into()),
+            (DisplayRow(29), DIAGNOSTIC_HEADER.into()),
+            (DisplayRow(38), EXCERPT_HEADER.into()),
         ]
     );
 
@@ -402,7 +392,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
             // diagnostic group 1
             "\n", // primary message
             "\n", // padding
-            "\n", // expand
             "const a: i32 = 'a';\n",
             "\n", // supporting diagnostic
             "const b: i32 = c;\n",
@@ -410,7 +399,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
             // diagnostic group 2
             "\n", // primary message
             "\n", // padding
-            "\n", // expand
             "const a: i32 = 'a';\n",
             "const b: i32 = c;\n",
             "\n", // supporting diagnostic
@@ -422,8 +410,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
             // diagnostic group 1
             "\n", // primary message
             "\n", // padding
-            "\n", // expand
-            "\n", // expand
             "    let x = vec![];\n",
             "    let y = vec![];\n",
             "\n", // supporting diagnostic
@@ -439,7 +425,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
             // diagnostic group 2
             "\n", // primary message
             "\n", // filename
-            "\n", // expand
             "fn main() {\n",
             "    let x = vec![];\n",
             "\n", // supporting diagnostic
@@ -447,13 +432,11 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
             "    a(x);\n",
             "\n", // supporting diagnostic
             "    b(y);\n",
-            "\n", // expand
             "\n", // context ellipsis
             "    c(y);\n",
             "    d(x);\n",
             "\n", // supporting diagnostic
             "}",
-            "\n", // expand
         )
     );
 }
@@ -535,7 +518,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
         editor_blocks(&editor, cx),
         [
             (DisplayRow(0), FILE_HEADER.into()),
-            (DisplayRow(3), DIAGNOSTIC_HEADER.into()),
+            (DisplayRow(2), DIAGNOSTIC_HEADER.into()),
         ]
     );
     assert_eq!(
@@ -546,9 +529,8 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
             // diagnostic group 1
             "\n",     // primary message
             "\n",     // padding
-            "\n",     // expand
             "a();\n", //
-            "b();", "\n", // expand
+            "b();",
         )
     );
 
@@ -584,9 +566,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
         editor_blocks(&editor, cx),
         [
             (DisplayRow(0), FILE_HEADER.into()),
-            (DisplayRow(3), DIAGNOSTIC_HEADER.into()),
-            (DisplayRow(7), EXCERPT_HEADER.into()),
-            (DisplayRow(9), DIAGNOSTIC_HEADER.into()),
+            (DisplayRow(2), DIAGNOSTIC_HEADER.into()),
+            (DisplayRow(6), EXCERPT_HEADER.into()),
+            (DisplayRow(7), DIAGNOSTIC_HEADER.into()),
         ]
     );
     assert_eq!(
@@ -597,10 +579,8 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
             // diagnostic group 1
             "\n",     // primary message
             "\n",     // padding
-            "\n",     // expand
             "a();\n", // location
             "b();\n", //
-            "\n",     // expand
             "\n",     // collapsed context
             // diagnostic group 2
             "\n",     // primary message
@@ -608,7 +588,6 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
             "a();\n", // context
             "b();\n", //
             "c();",   // context
-            "\n",     // expand
         )
     );
 
@@ -655,9 +634,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
         editor_blocks(&editor, cx),
         [
             (DisplayRow(0), FILE_HEADER.into()),
-            (DisplayRow(3), DIAGNOSTIC_HEADER.into()),
-            (DisplayRow(8), EXCERPT_HEADER.into()),
-            (DisplayRow(10), DIAGNOSTIC_HEADER.into()),
+            (DisplayRow(2), DIAGNOSTIC_HEADER.into()),
+            (DisplayRow(7), EXCERPT_HEADER.into()),
+            (DisplayRow(8), DIAGNOSTIC_HEADER.into()),
         ]
     );
     assert_eq!(
@@ -668,11 +647,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
             // diagnostic group 1
             "\n",     // primary message
             "\n",     // padding
-            "\n",     // expand
             "a();\n", // location
             "b();\n", //
             "c();\n", // context
-            "\n",     // expand
             "\n",     // collapsed context
             // diagnostic group 2
             "\n",     // primary message
@@ -680,7 +657,6 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
             "b();\n", // context
             "c();\n", //
             "d();",   // context
-            "\n",     // expand
         )
     );
 
@@ -716,9 +692,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
         editor_blocks(&editor, cx),
         [
             (DisplayRow(0), FILE_HEADER.into()),
-            (DisplayRow(3), DIAGNOSTIC_HEADER.into()),
-            (DisplayRow(8), EXCERPT_HEADER.into()),
-            (DisplayRow(10), DIAGNOSTIC_HEADER.into()),
+            (DisplayRow(2), DIAGNOSTIC_HEADER.into()),
+            (DisplayRow(7), EXCERPT_HEADER.into()),
+            (DisplayRow(8), DIAGNOSTIC_HEADER.into()),
         ]
     );
     assert_eq!(
@@ -729,11 +705,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
             // diagnostic group 1
             "\n",     // primary message
             "\n",     // padding
-            "\n",     // expand
             "b();\n", // location
             "c();\n", //
             "d();\n", // context
-            "\n",     // expand
             "\n",     // collapsed context
             // diagnostic group 2
             "\n",     // primary message
@@ -741,7 +715,6 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
             "c();\n", // context
             "d();\n", //
             "e();",   // context
-            "\n",     // expand
         )
     );
 }

crates/editor/src/display_map.rs 🔗

@@ -118,10 +118,8 @@ impl DisplayMap {
         font: Font,
         font_size: Pixels,
         wrap_width: Option<Pixels>,
-        show_excerpt_controls: bool,
         buffer_header_height: u32,
         excerpt_header_height: u32,
-        excerpt_footer_height: u32,
         fold_placeholder: FoldPlaceholder,
         cx: &mut Context<Self>,
     ) -> Self {
@@ -134,13 +132,7 @@ impl DisplayMap {
         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,
-            show_excerpt_controls,
-            buffer_header_height,
-            excerpt_header_height,
-            excerpt_footer_height,
-        );
+        let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height);
 
         cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
 
@@ -555,10 +547,6 @@ impl DisplayMap {
     pub fn is_rewrapping(&self, cx: &gpui::App) -> bool {
         self.wrap_map.read(cx).is_rewrapping()
     }
-
-    pub fn show_excerpt_controls(&self) -> bool {
-        self.block_map.show_excerpt_controls()
-    }
 }
 
 #[derive(Debug, Default)]
@@ -1102,8 +1090,8 @@ impl DisplaySnapshot {
             .map(|(row, block)| (DisplayRow(row), block))
     }
 
-    pub fn sticky_header_excerpt(&self, row: DisplayRow) -> Option<StickyHeaderExcerpt<'_>> {
-        self.block_snapshot.sticky_header_excerpt(row.0)
+    pub fn sticky_header_excerpt(&self, row: f32) -> Option<StickyHeaderExcerpt<'_>> {
+        self.block_snapshot.sticky_header_excerpt(row)
     }
 
     pub fn block_for_id(&self, id: BlockId) -> Option<Block> {
@@ -1301,10 +1289,6 @@ impl DisplaySnapshot {
         self.block_snapshot.buffer_header_height
     }
 
-    pub fn excerpt_footer_height(&self) -> u32 {
-        self.block_snapshot.excerpt_footer_height
-    }
-
     pub fn excerpt_header_height(&self) -> u32 {
         self.block_snapshot.excerpt_header_height
     }
@@ -1514,10 +1498,8 @@ pub mod tests {
                 font,
                 font_size,
                 wrap_width,
-                true,
                 buffer_start_excerpt_header_height,
                 excerpt_header_height,
-                0,
                 FoldPlaceholder::test(),
                 cx,
             )
@@ -1764,10 +1746,8 @@ pub mod tests {
                     font("Helvetica"),
                     font_size,
                     wrap_width,
-                    true,
                     1,
                     1,
-                    0,
                     FoldPlaceholder::test(),
                     cx,
                 )
@@ -1875,10 +1855,8 @@ pub mod tests {
                 font("Helvetica"),
                 font_size,
                 None,
-                true,
                 1,
                 1,
-                0,
                 FoldPlaceholder::test(),
                 cx,
             )
@@ -1938,8 +1916,6 @@ pub mod tests {
                 font("Helvetica"),
                 font_size,
                 None,
-                true,
-                1,
                 1,
                 1,
                 FoldPlaceholder::test(),
@@ -2032,8 +2008,6 @@ pub mod tests {
                 font("Helvetica"),
                 font_size,
                 None,
-                true,
-                1,
                 1,
                 1,
                 FoldPlaceholder::test(),
@@ -2134,10 +2108,8 @@ pub mod tests {
                 font("Courier"),
                 px(16.0),
                 None,
-                true,
                 1,
                 1,
-                0,
                 FoldPlaceholder::test(),
                 cx,
             )
@@ -2239,10 +2211,8 @@ pub mod tests {
                 font("Courier"),
                 px(16.0),
                 None,
-                true,
                 1,
                 1,
-                0,
                 FoldPlaceholder::test(),
                 cx,
             )
@@ -2328,10 +2298,8 @@ pub mod tests {
                 font("Courier"),
                 px(16.0),
                 None,
-                true,
                 1,
                 1,
-                0,
                 FoldPlaceholder::test(),
                 cx,
             )
@@ -2472,10 +2440,8 @@ pub mod tests {
                 font("Courier"),
                 font_size,
                 Some(px(40.0)),
-                true,
                 1,
                 1,
-                0,
                 FoldPlaceholder::test(),
                 cx,
             )
@@ -2556,8 +2522,6 @@ pub mod tests {
                 font("Courier"),
                 font_size,
                 None,
-                true,
-                1,
                 1,
                 1,
                 FoldPlaceholder::test(),
@@ -2682,10 +2646,8 @@ pub mod tests {
                 font("Helvetica"),
                 font_size,
                 None,
-                true,
                 1,
                 1,
-                0,
                 FoldPlaceholder::test(),
                 cx,
             );
@@ -2721,10 +2683,8 @@ pub mod tests {
                 font("Helvetica"),
                 font_size,
                 None,
-                true,
                 1,
                 1,
-                0,
                 FoldPlaceholder::test(),
                 cx,
             )
@@ -2798,10 +2758,8 @@ pub mod tests {
                 font("Helvetica"),
                 font_size,
                 None,
-                true,
                 1,
                 1,
-                0,
                 FoldPlaceholder::test(),
                 cx,
             )

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

@@ -37,10 +37,8 @@ pub struct BlockMap {
     custom_blocks: Vec<Arc<CustomBlock>>,
     custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
     transforms: RefCell<SumTree<Transform>>,
-    show_excerpt_controls: bool,
     buffer_header_height: u32,
     excerpt_header_height: u32,
-    excerpt_footer_height: u32,
     pub(super) folded_buffers: HashSet<BufferId>,
 }
 
@@ -58,7 +56,6 @@ pub struct BlockSnapshot {
     custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
     pub(super) buffer_header_height: u32,
     pub(super) excerpt_header_height: u32,
-    pub(super) excerpt_footer_height: u32,
 }
 
 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -285,14 +282,12 @@ pub enum Block {
         first_excerpt: ExcerptInfo,
         prev_excerpt: Option<ExcerptInfo>,
         height: u32,
-        show_excerpt_controls: bool,
     },
     ExcerptBoundary {
         prev_excerpt: Option<ExcerptInfo>,
         next_excerpt: Option<ExcerptInfo>,
         height: u32,
         starts_new_buffer: bool,
-        show_excerpt_controls: bool,
     },
 }
 
@@ -362,13 +357,11 @@ impl Debug for Block {
                 first_excerpt,
                 prev_excerpt,
                 height,
-                show_excerpt_controls,
             } => f
                 .debug_struct("FoldedBuffer")
                 .field("first_excerpt", &first_excerpt)
                 .field("prev_excerpt", prev_excerpt)
                 .field("height", height)
-                .field("show_excerpt_controls", show_excerpt_controls)
                 .finish(),
             Self::ExcerptBoundary {
                 starts_new_buffer,
@@ -413,10 +406,8 @@ pub struct BlockRows<'a> {
 impl BlockMap {
     pub fn new(
         wrap_snapshot: WrapSnapshot,
-        show_excerpt_controls: bool,
         buffer_header_height: u32,
         excerpt_header_height: u32,
-        excerpt_footer_height: u32,
     ) -> Self {
         let row_count = wrap_snapshot.max_point().row() + 1;
         let mut transforms = SumTree::default();
@@ -428,10 +419,8 @@ impl BlockMap {
             folded_buffers: HashSet::default(),
             transforms: RefCell::new(transforms),
             wrap_snapshot: RefCell::new(wrap_snapshot.clone()),
-            show_excerpt_controls,
             buffer_header_height,
             excerpt_header_height,
-            excerpt_footer_height,
         };
         map.sync(
             &wrap_snapshot,
@@ -454,7 +443,6 @@ impl BlockMap {
                 custom_blocks_by_id: self.custom_blocks_by_id.clone(),
                 buffer_header_height: self.buffer_header_height,
                 excerpt_header_height: self.excerpt_header_height,
-                excerpt_footer_height: self.excerpt_footer_height,
             },
         }
     }
@@ -650,8 +638,6 @@ impl BlockMap {
 
             if buffer.show_headers() {
                 blocks_in_edit.extend(BlockMap::header_and_footer_blocks(
-                    self.show_excerpt_controls,
-                    self.excerpt_footer_height,
                     self.buffer_header_height,
                     self.excerpt_header_height,
                     buffer,
@@ -722,13 +708,7 @@ impl BlockMap {
         }
     }
 
-    pub fn show_excerpt_controls(&self) -> bool {
-        self.show_excerpt_controls
-    }
-
     fn header_and_footer_blocks<'a, R, T>(
-        show_excerpt_controls: bool,
-        excerpt_footer_height: u32,
         buffer_header_height: u32,
         excerpt_header_height: u32,
         buffer: &'a multi_buffer::MultiBufferSnapshot,
@@ -774,11 +754,6 @@ impl BlockMap {
                 .filter(|prev| !folded_buffers.contains(&prev.buffer_id));
 
             let mut height = 0;
-            if prev_excerpt.is_some() {
-                if show_excerpt_controls {
-                    height += excerpt_footer_height;
-                }
-            }
 
             if let Some(new_buffer_id) = new_buffer_id {
                 let first_excerpt = excerpt_boundary.next.clone().unwrap();
@@ -812,7 +787,6 @@ impl BlockMap {
                         Block::FoldedBuffer {
                             prev_excerpt,
                             height: height + buffer_header_height,
-                            show_excerpt_controls,
                             first_excerpt,
                         },
                     ));
@@ -822,9 +796,6 @@ impl BlockMap {
             if excerpt_boundary.next.is_some() {
                 if new_buffer_id.is_some() {
                     height += buffer_header_height;
-                    if show_excerpt_controls {
-                        height += excerpt_header_height;
-                    }
                 } else {
                     height += excerpt_header_height;
                 }
@@ -845,7 +816,6 @@ impl BlockMap {
                     next_excerpt: excerpt_boundary.next,
                     height,
                     starts_new_buffer: new_buffer_id.is_some(),
-                    show_excerpt_controls,
                 },
             ))
         })
@@ -1432,7 +1402,8 @@ impl BlockSnapshot {
         })
     }
 
-    pub fn sticky_header_excerpt(&self, top_row: u32) -> Option<StickyHeaderExcerpt<'_>> {
+    pub fn sticky_header_excerpt(&self, position: f32) -> Option<StickyHeaderExcerpt<'_>> {
+        let top_row = position as u32;
         let mut cursor = self.transforms.cursor::<BlockRow>(&());
         cursor.seek(&BlockRow(top_row), Bias::Left, &());
 
@@ -1445,19 +1416,13 @@ impl BlockSnapshot {
                     prev_excerpt,
                     next_excerpt,
                     starts_new_buffer,
-                    show_excerpt_controls,
                     ..
                 }) => {
-                    let matches_start = if *show_excerpt_controls && prev_excerpt.is_some() {
-                        start < top_row
-                    } else {
-                        start <= top_row
-                    };
+                    let matches_start = (start as f32) < position;
 
                     if matches_start && top_row <= end {
                         return next_excerpt.as_ref().map(|excerpt| StickyHeaderExcerpt {
                             next_buffer_row: None,
-                            next_excerpt_controls_present: *show_excerpt_controls,
                             excerpt,
                         });
                     }
@@ -1467,7 +1432,6 @@ impl BlockSnapshot {
                     return prev_excerpt.as_ref().map(|excerpt| StickyHeaderExcerpt {
                         excerpt,
                         next_buffer_row,
-                        next_excerpt_controls_present: *show_excerpt_controls,
                     });
                 }
                 Some(Block::FoldedBuffer {
@@ -1476,7 +1440,6 @@ impl BlockSnapshot {
                 }) if top_row <= start => {
                     return Some(StickyHeaderExcerpt {
                         next_buffer_row: Some(end),
-                        next_excerpt_controls_present: false,
                         excerpt,
                     });
                 }
@@ -1785,7 +1748,6 @@ impl BlockChunks<'_> {
 
 pub struct StickyHeaderExcerpt<'a> {
     pub excerpt: &'a ExcerptInfo,
-    pub next_excerpt_controls_present: bool,
     pub next_buffer_row: Option<u32>,
 }
 
@@ -2066,7 +2028,7 @@ mod tests {
         let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
         let (wrap_map, wraps_snapshot) =
             cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
-        let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
+        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![
@@ -2279,14 +2241,11 @@ mod tests {
         let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
         let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
 
-        let block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
+        let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
         let snapshot = block_map.read(wraps_snapshot, Default::default());
 
         // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
-        assert_eq!(
-            snapshot.text(),
-            "\n\nBuff\ner 1\n\n\n\nBuff\ner 2\n\n\n\nBuff\ner 3\n"
-        );
+        assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
 
         let blocks: Vec<_> = snapshot
             .blocks_in_range(0..u32::MAX)
@@ -2295,10 +2254,9 @@ mod tests {
         assert_eq!(
             blocks,
             vec![
-                (0..2, BlockId::ExcerptBoundary(Some(excerpt_ids[0]))), // path, header
-                (4..7, BlockId::ExcerptBoundary(Some(excerpt_ids[1]))), // footer, path, header
-                (9..12, BlockId::ExcerptBoundary(Some(excerpt_ids[2]))), // footer, path, header
-                (14..15, BlockId::ExcerptBoundary(None)),               // footer
+                (0..1, BlockId::ExcerptBoundary(Some(excerpt_ids[0]))), // path, header
+                (3..4, BlockId::ExcerptBoundary(Some(excerpt_ids[1]))), // path, header
+                (6..7, BlockId::ExcerptBoundary(Some(excerpt_ids[2]))), // path, header
             ]
         );
     }
@@ -2317,7 +2275,7 @@ mod tests {
         let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
         let (_wrap_map, wraps_snapshot) =
             cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
-        let mut block_map = BlockMap::new(wraps_snapshot.clone(), false, 1, 1, 0);
+        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![
@@ -2420,7 +2378,7 @@ mod tests {
         let (_, wraps_snapshot) = cx.update(|cx| {
             WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(60.)), cx)
         });
-        let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 0);
+        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![
@@ -2464,7 +2422,7 @@ mod tests {
         let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
         let (wrap_map, wraps_snapshot) =
             cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
-        let mut block_map = BlockMap::new(wraps_snapshot.clone(), false, 1, 1, 0);
+        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
 
         let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
         let replace_block_id = writer.insert(vec![BlockProperties {
@@ -2631,12 +2589,12 @@ mod tests {
         let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
         let (_, wrap_snapshot) =
             cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
-        let mut block_map = BlockMap::new(wrap_snapshot.clone(), true, 2, 1, 1);
+        let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
         let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
 
         assert_eq!(
             blocks_snapshot.text(),
-            "\n\n\n111\n\n\n\n\n222\n\n\n333\n\n\n444\n\n\n\n\n555\n\n\n666\n"
+            "\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
         );
         assert_eq!(
             blocks_snapshot
@@ -2644,30 +2602,21 @@ mod tests {
                 .map(|i| i.buffer_row)
                 .collect::<Vec<_>>(),
             vec![
-                None,
                 None,
                 None,
                 Some(0),
                 None,
                 None,
-                None,
-                None,
                 Some(1),
                 None,
-                None,
                 Some(2),
                 None,
-                None,
                 Some(3),
                 None,
                 None,
-                None,
-                None,
                 Some(4),
                 None,
-                None,
                 Some(5),
-                None,
             ]
         );
 
@@ -2715,7 +2664,7 @@ mod tests {
         let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
         assert_eq!(
             blocks_snapshot.text(),
-            "\n\n\n111\n\n\n\n\n\n222\n\n\n\n333\n\n\n444\n\n\n\n\n\n\n555\n\n\n666\n\n"
+            "\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
         );
         assert_eq!(
             blocks_snapshot
@@ -2723,35 +2672,26 @@ mod tests {
                 .map(|i| i.buffer_row)
                 .collect::<Vec<_>>(),
             vec![
-                None,
                 None,
                 None,
                 Some(0),
                 None,
                 None,
                 None,
-                None,
-                None,
                 Some(1),
                 None,
                 None,
-                None,
                 Some(2),
                 None,
-                None,
                 Some(3),
                 None,
                 None,
                 None,
                 None,
-                None,
-                None,
                 Some(4),
                 None,
-                None,
                 Some(5),
                 None,
-                None,
             ]
         );
 
@@ -2793,7 +2733,7 @@ mod tests {
         );
         assert_eq!(
             blocks_snapshot.text(),
-            "\n\n\n\n\n\n222\n\n\n\n333\n\n\n444\n\n\n\n\n\n\n555\n\n\n666\n\n"
+            "\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
         );
         assert_eq!(
             blocks_snapshot
@@ -2806,27 +2746,20 @@ mod tests {
                 None,
                 None,
                 None,
-                None,
                 Some(1),
                 None,
                 None,
-                None,
                 Some(2),
                 None,
-                None,
                 Some(3),
                 None,
                 None,
                 None,
                 None,
-                None,
-                None,
                 Some(4),
                 None,
-                None,
                 Some(5),
                 None,
-                None,
             ]
         );
 
@@ -2862,7 +2795,7 @@ mod tests {
                 .count(),
             "Should have two folded blocks, producing headers"
         );
-        assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n\n555\n\n\n666\n\n");
+        assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
         assert_eq!(
             blocks_snapshot
                 .row_infos(BlockRow(0))
@@ -2876,13 +2809,10 @@ mod tests {
                 None,
                 None,
                 None,
-                None,
                 Some(4),
                 None,
-                None,
                 Some(5),
                 None,
-                None,
             ]
         );
 
@@ -2917,7 +2847,7 @@ mod tests {
         );
         assert_eq!(
             blocks_snapshot.text(),
-            "\n\n\n\n111\n\n\n\n\n\n\n\n555\n\n\n666\n\n",
+            "\n\n\n111\n\n\n\n\n\n555\n\n666\n",
             "Should have extra newline for 111 buffer, due to a new block added when it was folded"
         );
         assert_eq!(
@@ -2929,21 +2859,16 @@ mod tests {
                 None,
                 None,
                 None,
-                None,
                 Some(0),
                 None,
                 None,
                 None,
                 None,
                 None,
-                None,
-                None,
                 Some(4),
                 None,
-                None,
                 Some(5),
                 None,
-                None,
             ]
         );
 
@@ -2974,7 +2899,7 @@ mod tests {
 
         assert_eq!(
             blocks_snapshot.text(),
-            "\n\n\n\n111\n\n\n\n\n",
+            "\n\n\n111\n\n\n\n",
             "Should have a single, first buffer left after folding"
         );
         assert_eq!(
@@ -2982,18 +2907,7 @@ mod tests {
                 .row_infos(BlockRow(0))
                 .map(|i| i.buffer_row)
                 .collect::<Vec<_>>(),
-            vec![
-                None,
-                None,
-                None,
-                None,
-                Some(0),
-                None,
-                None,
-                None,
-                None,
-                None,
-            ]
+            vec![None, None, None, Some(0), None, None, None, None,]
         );
     }
 
@@ -3020,10 +2934,10 @@ mod tests {
         let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
         let (_, wrap_snapshot) =
             cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
-        let mut block_map = BlockMap::new(wrap_snapshot.clone(), true, 2, 1, 1);
+        let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
         let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
 
-        assert_eq!(blocks_snapshot.text(), "\n\n\n111\n");
+        assert_eq!(blocks_snapshot.text(), "\n\n111");
 
         let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
         buffer.read_with(cx, |buffer, cx| {
@@ -3077,11 +2991,9 @@ mod tests {
         let font_size = px(14.0);
         let buffer_start_header_height = rng.gen_range(1..=5);
         let excerpt_header_height = rng.gen_range(1..=5);
-        let excerpt_footer_height = rng.gen_range(1..=5);
 
         log::info!("Wrap width: {:?}", wrap_width);
         log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
-        log::info!("Excerpt Footer Height: {:?}", excerpt_footer_height);
         let is_singleton = rng.gen();
         let buffer = if is_singleton {
             let len = rng.gen_range(0..10);
@@ -3108,10 +3020,8 @@ mod tests {
             cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
         let mut block_map = BlockMap::new(
             wraps_snapshot,
-            true,
             buffer_start_header_height,
             excerpt_header_height,
-            excerpt_footer_height,
         );
 
         for _ in 0..operations {
@@ -3329,8 +3239,6 @@ mod tests {
 
             // Note that this needs to be synced with the related section in BlockMap::sync
             expected_blocks.extend(BlockMap::header_and_footer_blocks(
-                true,
-                excerpt_footer_height,
                 buffer_start_header_height,
                 excerpt_header_height,
                 &buffer_snapshot,

crates/editor/src/editor.rs 🔗

@@ -196,7 +196,6 @@ use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
 
 pub const FILE_HEADER_HEIGHT: u32 = 2;
 pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
-pub const MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT: u32 = 1;
 pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
 const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
 const MAX_LINE_LEN: usize = 1024;
@@ -1092,7 +1091,6 @@ impl Editor {
             EditorMode::SingleLine { auto_width: false },
             buffer,
             None,
-            false,
             window,
             cx,
         )
@@ -1101,7 +1099,7 @@ impl Editor {
     pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
         let buffer = cx.new(|cx| Buffer::local("", cx));
         let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
-        Self::new(EditorMode::Full, buffer, None, false, window, cx)
+        Self::new(EditorMode::Full, buffer, None, window, cx)
     }
 
     pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
@@ -1111,7 +1109,6 @@ impl Editor {
             EditorMode::SingleLine { auto_width: true },
             buffer,
             None,
-            false,
             window,
             cx,
         )
@@ -1124,7 +1121,6 @@ impl Editor {
             EditorMode::AutoHeight { max_lines },
             buffer,
             None,
-            false,
             window,
             cx,
         )
@@ -1137,33 +1133,23 @@ impl Editor {
         cx: &mut Context<Self>,
     ) -> Self {
         let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
-        Self::new(EditorMode::Full, buffer, project, false, window, cx)
+        Self::new(EditorMode::Full, buffer, project, window, cx)
     }
 
     pub fn for_multibuffer(
         buffer: Entity<MultiBuffer>,
         project: Option<Entity<Project>>,
-        show_excerpt_controls: bool,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Self {
-        Self::new(
-            EditorMode::Full,
-            buffer,
-            project,
-            show_excerpt_controls,
-            window,
-            cx,
-        )
+        Self::new(EditorMode::Full, buffer, project, window, cx)
     }
 
     pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
-        let show_excerpt_controls = self.display_map.read(cx).show_excerpt_controls();
         let mut clone = Self::new(
             self.mode,
             self.buffer.clone(),
             self.project.clone(),
-            show_excerpt_controls,
             window,
             cx,
         );
@@ -1183,7 +1169,6 @@ impl Editor {
         mode: EditorMode,
         buffer: Entity<MultiBuffer>,
         project: Option<Entity<Project>>,
-        show_excerpt_controls: bool,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Self {
@@ -1228,10 +1213,8 @@ impl Editor {
                 style.font(),
                 font_size,
                 None,
-                show_excerpt_controls,
                 FILE_HEADER_HEIGHT,
                 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
-                MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT,
                 fold_placeholder,
                 cx,
             )
@@ -4693,8 +4676,8 @@ impl Editor {
 
         workspace.update_in(&mut cx, |workspace, window, cx| {
             let project = workspace.project().clone();
-            let editor = cx
-                .new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), true, window, cx));
+            let editor =
+                cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
             workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
             editor.update(cx, |editor, cx| {
                 editor.highlight_background::<Self>(
@@ -12381,7 +12364,6 @@ impl Editor {
             Editor::for_multibuffer(
                 excerpt_buffer,
                 Some(workspace.project().clone()),
-                true,
                 window,
                 cx,
             )

crates/editor/src/editor_tests.rs 🔗

@@ -7676,7 +7676,6 @@ async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
             EditorMode::Full,
             multi_buffer,
             Some(project.clone()),
-            true,
             window,
             cx,
         )
@@ -13501,7 +13500,6 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
             EditorMode::Full,
             multi_buffer,
             Some(project.clone()),
-            true,
             window,
             cx,
         )
@@ -13984,9 +13982,8 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
         multibuffer
     });
 
-    let editor = cx.add_window(|window, cx| {
-        Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
-    });
+    let editor =
+        cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
     editor
         .update(cx, |editor, _window, cx| {
             for (buffer, diff_base) in [
@@ -14105,9 +14102,8 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
         multibuffer
     });
 
-    let editor = cx.add_window(|window, cx| {
-        Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
-    });
+    let editor =
+        cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
     editor
         .update(cx, |editor, _window, cx| {
             let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
@@ -15663,14 +15659,7 @@ async fn test_display_diff_hunks(cx: &mut TestAppContext) {
     });
 
     let editor = cx.add_window(|window, cx| {
-        Editor::new(
-            EditorMode::Full,
-            multibuffer,
-            Some(project),
-            true,
-            window,
-            cx,
-        )
+        Editor::new(EditorMode::Full, multibuffer, Some(project), window, cx)
     });
     cx.run_until_parked();
 
@@ -15689,9 +15678,9 @@ async fn test_display_diff_hunks(cx: &mut TestAppContext) {
     assert_eq!(
         hunks,
         [
-            DisplayRow(3)..DisplayRow(5),
-            DisplayRow(10)..DisplayRow(12),
-            DisplayRow(17)..DisplayRow(19),
+            DisplayRow(2)..DisplayRow(4),
+            DisplayRow(7)..DisplayRow(9),
+            DisplayRow(12)..DisplayRow(14),
         ]
     );
 }
@@ -16122,7 +16111,6 @@ async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
             EditorMode::Full,
             multi_buffer,
             Some(project.clone()),
-            true,
             window,
             cx,
         )
@@ -16277,7 +16265,6 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
             EditorMode::Full,
             multi_buffer.clone(),
             Some(project.clone()),
-            true,
             window,
             cx,
         )
@@ -16285,7 +16272,7 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
 
     assert_eq!(
         multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
-        "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
+        "\n\naaaa\nbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
     );
 
     multi_buffer_editor.update(cx, |editor, cx| {
@@ -16293,7 +16280,7 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
     });
     assert_eq!(
         multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
-        "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
+        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
         "After folding the first buffer, its text should not be displayed"
     );
 
@@ -16302,7 +16289,7 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
     });
     assert_eq!(
         multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
-        "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
+        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
         "After folding the second buffer, its text should not be displayed"
     );
 
@@ -16327,7 +16314,7 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
     });
     assert_eq!(
         multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
-        "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
+        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
         "After unfolding the second buffer, its text should be displayed"
     );
 
@@ -16349,7 +16336,7 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
 
     assert_eq!(
         multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
-        "\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
+        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
         "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
     );
 
@@ -16358,7 +16345,7 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
     });
     assert_eq!(
         multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
-        "\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
+        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
         "After unfolding the all buffers, all original text should be displayed"
     );
 }
@@ -16444,13 +16431,12 @@ async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
             EditorMode::Full,
             multi_buffer,
             Some(project.clone()),
-            true,
             window,
             cx,
         )
     });
 
-    let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
+    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
     assert_eq!(
         multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
         full_text,
@@ -16461,7 +16447,7 @@ async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
     });
     assert_eq!(
         multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
-        "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
+        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
         "After folding the first buffer, its text should not be displayed"
     );
 
@@ -16471,7 +16457,7 @@ async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
 
     assert_eq!(
         multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
-        "\n\n\n\n\n\n\n7777\n8888\n9999\n",
+        "\n\n\n\n\n\n7777\n8888\n9999",
         "After folding the second buffer, its text should not be displayed"
     );
 
@@ -16489,7 +16475,7 @@ async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
     });
     assert_eq!(
         multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
-        "\n\n\n\n\n4444\n5555\n6666\n\n\n",
+        "\n\n\n\n4444\n5555\n6666\n\n",
         "After unfolding the second buffer, its text should be displayed"
     );
 
@@ -16498,7 +16484,7 @@ async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
     });
     assert_eq!(
         multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
-        "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
+        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
         "After unfolding the first buffer, its text should be displayed"
     );
 
@@ -16564,7 +16550,6 @@ async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut Test
             EditorMode::Full,
             multi_buffer,
             Some(project.clone()),
-            true,
             window,
             cx,
         )
@@ -16583,7 +16568,7 @@ async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut Test
         editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
     });
 
-    let full_text = format!("\n\n\n{sample_text}\n");
+    let full_text = format!("\n\n{sample_text}");
     assert_eq!(
         multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
         full_text,
@@ -16612,14 +16597,7 @@ async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContex
             ],
             cx,
         );
-        let mut editor = Editor::new(
-            EditorMode::Full,
-            multi_buffer.clone(),
-            None,
-            true,
-            window,
-            cx,
-        );
+        let mut editor = Editor::new(EditorMode::Full, multi_buffer.clone(), None, window, cx);
 
         let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
         // fold all but the second buffer, so that we test navigating between two
@@ -16931,7 +16909,7 @@ async fn assert_highlighted_edits(
 ) {
     let window = cx.add_window(|window, cx| {
         let buffer = MultiBuffer::build_simple(text, cx);
-        Editor::new(EditorMode::Full, buffer, None, true, window, cx)
+        Editor::new(EditorMode::Full, buffer, None, window, cx)
     });
     let cx = &mut VisualTestContext::from_window(*window, cx);
 

crates/editor/src/element.rs 🔗

@@ -18,12 +18,12 @@ use crate::{
     scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
     BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayDiffHunk, DisplayPoint,
     DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
-    EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
-    GoToPreviousHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
-    InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineHighlight, LineUp,
-    OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight,
-    Selection, SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS,
-    CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
+    EditorSettings, EditorSnapshot, EditorStyle, FocusedBlock, GoToHunk, GoToPreviousHunk,
+    GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, InlayHintRefreshReason,
+    InlineCompletion, JumpData, LineDown, LineHighlight, LineUp, OpenExcerpts, PageDown, PageUp,
+    Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap,
+    StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR,
+    FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
     MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
 };
 use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
@@ -33,10 +33,10 @@ use file_icons::FileIcons;
 use git::{blame::BlameEntry, status::FileStatus, Oid};
 use gpui::{
     anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad,
-    relative, size, solid_background, svg, transparent_black, Action, AnyElement, App,
-    AvailableSpace, Axis, Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners,
-    CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _,
-    FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Keystroke, Length,
+    relative, size, solid_background, transparent_black, Action, AnyElement, App, AvailableSpace,
+    Axis, Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle,
+    DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId,
+    GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Keystroke, Length,
     ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
     ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
     StatefulInteractiveElement, Style, Styled, Subscription, TextRun, TextStyleRefinement, Window,
@@ -52,8 +52,8 @@ use language::{
 };
 use lsp::DiagnosticSeverity;
 use multi_buffer::{
-    Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow,
-    RowInfo,
+    Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, ExpandInfo, MultiBufferPoint,
+    MultiBufferRow, RowInfo,
 };
 use project::project_settings::{self, GitGutterSetting, ProjectSettings};
 use settings::Settings;
@@ -72,7 +72,7 @@ use sum_tree::Bias;
 use text::BufferId;
 use theme::{ActiveTheme, Appearance, BufferLineHeight, PlayerColor};
 use ui::{
-    h_flex, prelude::*, ButtonLike, ButtonStyle, ContextMenu, IconButtonShape, KeyBinding, Tooltip,
+    h_flex, prelude::*, ButtonLike, ContextMenu, IconButtonShape, KeyBinding, Tooltip,
     POPOVER_Y_PADDING,
 };
 use unicode_segmentation::UnicodeSegmentation;
@@ -1515,6 +1515,19 @@ impl EditorElement {
         }
     }
 
+    fn prepaint_expand_toggles(
+        &self,
+        expand_toggles: &mut [Option<(AnyElement, gpui::Point<Pixels>)>],
+        window: &mut Window,
+        cx: &mut App,
+    ) {
+        for (expand_toggle, origin) in expand_toggles.iter_mut().flatten() {
+            let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
+            expand_toggle.layout_as_root(available_space, window, cx);
+            expand_toggle.prepaint_as_root(*origin, available_space, window, cx);
+        }
+    }
+
     fn prepaint_crease_trailers(
         &self,
         trailers: Vec<Option<AnyElement>>,
@@ -2009,6 +2022,7 @@ impl EditorElement {
         &self,
         line_height: Pixels,
         range: Range<DisplayRow>,
+        row_infos: &[RowInfo],
         scroll_pixel_position: gpui::Point<Pixels>,
         gutter_dimensions: &GutterDimensions,
         gutter_hitbox: &Hitbox,
@@ -2074,6 +2088,12 @@ impl EditorElement {
                         }
                     }
                     let display_row = multibuffer_point.to_display_point(snapshot).row();
+                    if row_infos
+                        .get((display_row - range.start).0 as usize)
+                        .is_some_and(|row_info| row_info.expand_info.is_some())
+                    {
+                        return None;
+                    }
                     let button = editor.render_run_indicator(
                         &self.style,
                         Some(display_row) == active_task_indicator_row,
@@ -2098,6 +2118,82 @@ impl EditorElement {
         })
     }
 
+    fn layout_excerpt_gutter(
+        &self,
+        gutter_hitbox: &Hitbox,
+        line_height: Pixels,
+        scroll_position: gpui::Point<f32>,
+        buffer_rows: &[RowInfo],
+        window: &mut Window,
+        cx: &mut App,
+    ) -> Vec<Option<(AnyElement, gpui::Point<Pixels>)>> {
+        let editor_font_size = self.style.text.font_size.to_pixels(window.rem_size()) * 1.2;
+
+        let icon_size = editor_font_size.round();
+        let button_h_padding = ((icon_size - px(1.0)) / 2.0).round() - px(2.0);
+
+        let scroll_top = scroll_position.y * line_height;
+
+        let elements = buffer_rows
+            .into_iter()
+            .enumerate()
+            .map(|(ix, row_info)| {
+                let ExpandInfo {
+                    excerpt_id,
+                    direction,
+                } = row_info.expand_info?;
+
+                let icon_name = match direction {
+                    ExpandExcerptDirection::Up => IconName::ExpandUp,
+                    ExpandExcerptDirection::Down => IconName::ExpandDown,
+                    ExpandExcerptDirection::UpAndDown => IconName::ExpandVertical,
+                };
+
+                let editor = self.editor.clone();
+                let max_row = self
+                    .editor
+                    .read(cx)
+                    .buffer()
+                    .read(cx)
+                    .snapshot(cx)
+                    .widest_line_number();
+                let is_wide = max_row > 999
+                    && row_info
+                        .buffer_row
+                        .is_some_and(|row| row.ilog10() == max_row.ilog10());
+
+                let toggle = IconButton::new(("expand", ix), icon_name)
+                    .icon_color(Color::Custom(cx.theme().colors().editor_line_number))
+                    .selected_icon_color(Color::Custom(cx.theme().colors().editor_foreground))
+                    .icon_size(IconSize::Custom(rems(editor_font_size / window.rem_size())))
+                    .width((icon_size + button_h_padding * 2).into())
+                    .when(is_wide, |el| {
+                        el.width((icon_size + button_h_padding).into())
+                    })
+                    .on_click(move |_, _, cx| {
+                        editor.update(cx, |editor, cx| {
+                            editor.expand_excerpt(excerpt_id, direction, cx);
+                        });
+                    })
+                    .tooltip(Tooltip::for_action_title(
+                        "Expand excerpt",
+                        &crate::actions::ExpandExcerpts::default(),
+                    ))
+                    .into_any_element();
+
+                let position = point(
+                    px(1.),
+                    ix as f32 * line_height - (scroll_top % line_height) + px(1.),
+                );
+                let origin = gutter_hitbox.origin + position;
+
+                Some((toggle, origin))
+            })
+            .collect();
+
+        elements
+    }
+
     fn layout_code_actions_indicator(
         &self,
         line_height: Pixels,
@@ -2315,6 +2411,9 @@ impl EditorElement {
                 .into_iter()
                 .enumerate()
                 .map(|(ix, info)| {
+                    if info.expand_info.is_some() {
+                        return None;
+                    }
                     let row = info.multibuffer_row?;
                     let display_row = DisplayRow(rows.start.0 + ix as u32);
                     let active = active_rows.contains_key(&display_row);
@@ -2337,6 +2436,9 @@ impl EditorElement {
         buffer_rows
             .into_iter()
             .map(|row_info| {
+                if row_info.expand_info.is_some() {
+                    return None;
+                }
                 if let Some(row) = row_info.multibuffer_row {
                     snapshot.render_crease_trailer(row, window, cx)
                 } else {
@@ -2514,25 +2616,11 @@ impl EditorElement {
 
             Block::FoldedBuffer {
                 first_excerpt,
-                prev_excerpt,
-                show_excerpt_controls,
                 height,
+                ..
             } => {
                 let selected = selected_buffer_ids.contains(&first_excerpt.buffer_id);
-                let mut result = v_flex().id(block_id).w_full();
-
-                if let Some(prev_excerpt) = prev_excerpt {
-                    if *show_excerpt_controls {
-                        result = result.child(self.render_expand_excerpt_control(
-                            block_id,
-                            ExpandExcerptDirection::Down,
-                            prev_excerpt.id,
-                            gutter_dimensions,
-                            window,
-                            cx,
-                        ));
-                    }
-                }
+                let result = v_flex().id(block_id).w_full();
 
                 let jump_data = header_jump_data(snapshot, block_row_start, *height, first_excerpt);
                 result
@@ -2540,6 +2628,7 @@ impl EditorElement {
                         first_excerpt,
                         true,
                         selected,
+                        false,
                         jump_data,
                         window,
                         cx,
@@ -2548,28 +2637,14 @@ impl EditorElement {
             }
 
             Block::ExcerptBoundary {
-                prev_excerpt,
                 next_excerpt,
-                show_excerpt_controls,
                 height,
                 starts_new_buffer,
+                ..
             } => {
                 let color = cx.theme().colors().clone();
                 let mut result = v_flex().id(block_id).w_full();
 
-                if let Some(prev_excerpt) = prev_excerpt {
-                    if *show_excerpt_controls {
-                        result = result.child(self.render_expand_excerpt_control(
-                            block_id,
-                            ExpandExcerptDirection::Down,
-                            prev_excerpt.id,
-                            gutter_dimensions,
-                            window,
-                            cx,
-                        ));
-                    }
-                }
-
                 if let Some(next_excerpt) = next_excerpt {
                     let jump_data =
                         header_jump_data(snapshot, block_row_start, *height, next_excerpt);
@@ -2582,6 +2657,7 @@ impl EditorElement {
                                 next_excerpt,
                                 false,
                                 selected,
+                                false,
                                 jump_data,
                                 window,
                                 cx,
@@ -2590,40 +2666,17 @@ impl EditorElement {
                             result = result
                                 .child(div().h(FILE_HEADER_HEIGHT as f32 * window.line_height()));
                         }
-
-                        if *show_excerpt_controls {
-                            result = result.child(self.render_expand_excerpt_control(
-                                block_id,
-                                ExpandExcerptDirection::Up,
-                                next_excerpt.id,
-                                gutter_dimensions,
-                                window,
-                                cx,
-                            ));
-                        }
                     } else {
-                        if *show_excerpt_controls {
-                            result = result.child(
-                                h_flex()
-                                    .relative()
-                                    .child(
-                                        div()
-                                            .top(px(0.))
-                                            .absolute()
-                                            .w_full()
-                                            .h_px()
-                                            .bg(color.border_variant),
-                                    )
-                                    .child(self.render_expand_excerpt_control(
-                                        block_id,
-                                        ExpandExcerptDirection::Up,
-                                        next_excerpt.id,
-                                        gutter_dimensions,
-                                        window,
-                                        cx,
-                                    )),
-                            );
-                        }
+                        result = result.child(
+                            h_flex().relative().child(
+                                div()
+                                    .top(line_height / 2.)
+                                    .absolute()
+                                    .w_full()
+                                    .h_px()
+                                    .bg(color.border_variant),
+                            ),
+                        );
                     };
                 }
 
@@ -2662,6 +2715,7 @@ impl EditorElement {
         for_excerpt: &ExcerptInfo,
         is_folded: bool,
         is_selected: bool,
+        _is_sticky: bool,
         jump_data: JumpData,
         window: &mut Window,
         cx: &mut App,
@@ -2708,7 +2762,7 @@ impl EditorElement {
                     .pl_0p5()
                     .pr_5()
                     .rounded_sm()
-                    .shadow_md()
+                    // .when(is_sticky, |el| el.shadow_md())
                     .border_1()
                     .map(|div| {
                         let border_color = if is_selected
@@ -2847,95 +2901,6 @@ impl EditorElement {
             )
     }
 
-    fn render_expand_excerpt_control(
-        &self,
-        block_id: BlockId,
-        direction: ExpandExcerptDirection,
-        excerpt_id: ExcerptId,
-        gutter_dimensions: &GutterDimensions,
-        window: &Window,
-        cx: &mut App,
-    ) -> impl IntoElement {
-        let color = cx.theme().colors().clone();
-        let hover_color = color.border_variant.opacity(0.5);
-        let focus_handle = self.editor.focus_handle(cx).clone();
-
-        let icon_offset =
-            gutter_dimensions.width - (gutter_dimensions.left_padding + gutter_dimensions.margin);
-        let header_height = MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * window.line_height();
-        let group_name = if direction == ExpandExcerptDirection::Down {
-            "expand-down"
-        } else {
-            "expand-up"
-        };
-
-        let expand_area = |id: SharedString| {
-            h_flex()
-                .id(id)
-                .w_full()
-                .cursor_pointer()
-                .block_mouse_down()
-                .on_mouse_move(|_, _, cx| cx.stop_propagation())
-                .hover(|style| style.bg(hover_color))
-                .tooltip({
-                    let focus_handle = focus_handle.clone();
-                    move |window, cx| {
-                        Tooltip::for_action_in(
-                            "Expand Excerpt",
-                            &ExpandExcerpts { lines: 0 },
-                            &focus_handle,
-                            window,
-                            cx,
-                        )
-                    }
-                })
-        };
-
-        expand_area(
-            format!(
-                "block-{}-{}",
-                block_id,
-                if direction == ExpandExcerptDirection::Down {
-                    "down"
-                } else {
-                    "up"
-                }
-            )
-            .into(),
-        )
-        .group(group_name)
-        .child(
-            h_flex()
-                .w(icon_offset)
-                .h(header_height)
-                .flex_none()
-                .justify_end()
-                .child(
-                    ButtonLike::new("expand-icon")
-                        .style(ButtonStyle::Transparent)
-                        .child(
-                            svg()
-                                .path(if direction == ExpandExcerptDirection::Down {
-                                    IconName::ArrowDownFromLine.path()
-                                } else {
-                                    IconName::ArrowUpFromLine.path()
-                                })
-                                .size(IconSize::XSmall.rems())
-                                .text_color(cx.theme().colors().editor_line_number)
-                                .group_hover(group_name, |style| {
-                                    style.text_color(cx.theme().colors().editor_active_line_number)
-                                }),
-                        ),
-                ),
-        )
-        .on_click(window.listener_for(&self.editor, {
-            move |editor, _, _, cx| {
-                editor.expand_excerpt(excerpt_id, direction, cx);
-                cx.stop_propagation();
-            }
-        }))
-    }
-
     fn render_blocks(
         &self,
         rows: Range<DisplayRow>,
@@ -3167,7 +3132,6 @@ impl EditorElement {
         &self,
         StickyHeaderExcerpt {
             excerpt,
-            next_excerpt_controls_present,
             next_buffer_row,
         }: StickyHeaderExcerpt<'_>,
         scroll_position: f32,
@@ -3204,7 +3168,7 @@ impl EditorElement {
                     .top_0(),
             )
             .child(
-                self.render_buffer_header(excerpt, false, selected, jump_data, window, cx)
+                self.render_buffer_header(excerpt, false, selected, true, jump_data, window, cx)
                     .into_any_element(),
             )
             .into_any_element();
@@ -3214,11 +3178,7 @@ impl EditorElement {
         if let Some(next_buffer_row) = next_buffer_row {
             // Push up the sticky header when the excerpt is getting close to the top of the viewport
 
-            let mut max_row = next_buffer_row - FILE_HEADER_HEIGHT * 2;
-
-            if next_excerpt_controls_present {
-                max_row -= MULTI_BUFFER_EXCERPT_HEADER_HEIGHT;
-            }
+            let max_row = next_buffer_row - FILE_HEADER_HEIGHT * 2;
 
             let offset = scroll_position - max_row as f32;
 
@@ -4514,6 +4474,12 @@ impl EditorElement {
                 }
             });
 
+            window.with_element_namespace("expand_toggles", |window| {
+                for (expand_toggle, _) in layout.expand_toggles.iter_mut().flatten() {
+                    expand_toggle.paint(window, cx);
+                }
+            });
+
             for test_indicator in layout.test_indicators.iter_mut() {
                 test_indicator.paint(window, cx);
             }
@@ -6891,6 +6857,18 @@ impl Element for EditorElement {
                         cx,
                     );
 
+                    let mut expand_toggles =
+                        window.with_element_namespace("expand_toggles", |window| {
+                            self.layout_excerpt_gutter(
+                                &gutter_hitbox,
+                                line_height,
+                                scroll_position,
+                                &row_infos,
+                                window,
+                                cx,
+                            )
+                        });
+
                     let mut crease_toggles =
                         window.with_element_namespace("crease_toggles", |window| {
                             self.layout_crease_toggles(
@@ -6988,7 +6966,7 @@ impl Element for EditorElement {
                     let mut scroll_width = scroll_range_bounds.size.width;
 
                     let sticky_header_excerpt = if snapshot.buffer_snapshot.show_headers() {
-                        snapshot.sticky_header_excerpt(start_row)
+                        snapshot.sticky_header_excerpt(scroll_position.y)
                     } else {
                         None
                     };
@@ -7305,7 +7283,14 @@ impl Element for EditorElement {
                                             .tasks
                                             .contains_key(&(buffer_id, row));
 
-                                        if !has_test_indicator {
+                                        let has_expand_indicator = row_infos
+                                            .get(
+                                                (newest_selection_head.row() - start_row).0
+                                                    as usize,
+                                            )
+                                            .is_some_and(|row_info| row_info.expand_info.is_some());
+
+                                        if !has_test_indicator && !has_expand_indicator {
                                             code_actions_indicator = self
                                                 .layout_code_actions_indicator(
                                                     line_height,
@@ -7338,6 +7323,7 @@ impl Element for EditorElement {
                         self.layout_run_indicators(
                             line_height,
                             start_row..end_row,
+                            &row_infos,
                             scroll_pixel_position,
                             &gutter_dimensions,
                             &gutter_hitbox,
@@ -7400,6 +7386,10 @@ impl Element for EditorElement {
                         )
                     });
 
+                    window.with_element_namespace("expand_toggles", |window| {
+                        self.prepaint_expand_toggles(&mut expand_toggles, window, cx)
+                    });
+
                     let invisible_symbol_font_size = font_size / 2.;
                     let tab_invisible = window
                         .text_system()
@@ -7501,6 +7491,7 @@ impl Element for EditorElement {
                         tab_invisible,
                         space_invisible,
                         sticky_buffer_header,
+                        expand_toggles,
                     }
                 })
             })
@@ -7674,6 +7665,7 @@ pub struct EditorLayout {
     code_actions_indicator: Option<AnyElement>,
     test_indicators: Vec<AnyElement>,
     crease_toggles: Vec<Option<AnyElement>>,
+    expand_toggles: Vec<Option<(AnyElement, gpui::Point<Pixels>)>>,
     diff_hunk_controls: Vec<AnyElement>,
     crease_trailers: Vec<Option<CreaseTrailerLayout>>,
     inline_completion_popover: Option<AnyElement>,
@@ -8307,7 +8299,7 @@ mod tests {
         init_test(cx, |_| {});
         let window = cx.add_window(|window, cx| {
             let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
-            Editor::new(EditorMode::Full, buffer, None, true, window, cx)
+            Editor::new(EditorMode::Full, buffer, None, window, cx)
         });
 
         let editor = window.root(cx).unwrap();
@@ -8407,7 +8399,7 @@ mod tests {
 
         let window = cx.add_window(|window, cx| {
             let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
-            Editor::new(EditorMode::Full, buffer, None, true, window, cx)
+            Editor::new(EditorMode::Full, buffer, None, window, cx)
         });
         let cx = &mut VisualTestContext::from_window(*window, cx);
         let editor = window.root(cx).unwrap();
@@ -8470,90 +8462,6 @@ mod tests {
             state.active_rows.keys().cloned().collect::<Vec<_>>(),
             vec![DisplayRow(0), DisplayRow(3), DisplayRow(5), DisplayRow(6)]
         );
-
-        // multi-buffer support
-        // in DisplayPoint coordinates, this is what we're dealing with:
-        //  0: [[file
-        //  1:   header
-        //  2:   section]]
-        //  3: aaaaaa
-        //  4: bbbbbb
-        //  5: cccccc
-        //  6:
-        //  7: [[footer]]
-        //  8: [[header]]
-        //  9: ffffff
-        // 10: gggggg
-        // 11: hhhhhh
-        // 12:
-        // 13: [[footer]]
-        // 14: [[file
-        // 15:   header
-        // 16:   section]]
-        // 17: bbbbbb
-        // 18: cccccc
-        // 19: dddddd
-        // 20: [[footer]]
-        let window = cx.add_window(|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, true, window, cx)
-        });
-        let editor = window.root(cx).unwrap();
-        let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
-        let _state = window.update(cx, |editor, window, cx| {
-            editor.cursor_shape = CursorShape::Block;
-            editor.change_selections(None, window, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(7), 0),
-                    DisplayPoint::new(DisplayRow(10), 0)..DisplayPoint::new(DisplayRow(13), 0),
-                ]);
-            });
-        });
-
-        let (_, state) = cx.draw(
-            point(px(500.), px(500.)),
-            size(px(500.), px(500.)),
-            |_, _| EditorElement::new(&editor, style),
-        );
-        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(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(7), 0)
-        );
-        assert_eq!(
-            local_selections[0].head,
-            DisplayPoint::new(DisplayRow(6), 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(DisplayRow(10), 0)..DisplayPoint::new(DisplayRow(13), 0)
-        );
-        assert_eq!(
-            local_selections[1].head,
-            DisplayPoint::new(DisplayRow(12), 0)
-        );
     }
 
     #[gpui::test]
@@ -8562,7 +8470,7 @@ mod tests {
 
         let window = cx.add_window(|window, cx| {
             let buffer = MultiBuffer::build_simple("", cx);
-            Editor::new(EditorMode::Full, buffer, None, true, window, cx)
+            Editor::new(EditorMode::Full, buffer, None, window, cx)
         });
         let cx = &mut VisualTestContext::from_window(*window, cx);
         let editor = window.root(cx).unwrap();
@@ -8788,7 +8696,7 @@ mod tests {
         );
         let window = cx.add_window(|window, cx| {
             let buffer = MultiBuffer::build_simple(input_text, cx);
-            Editor::new(editor_mode, buffer, None, true, window, cx)
+            Editor::new(editor_mode, buffer, None, window, cx)
         });
         let cx = &mut VisualTestContext::from_window(*window, cx);
         let editor = window.root(cx).unwrap();

crates/editor/src/inlay_hint_cache.rs 🔗

@@ -2618,7 +2618,7 @@ pub mod tests {
 
         cx.executor().run_until_parked();
         let editor = cx.add_window(|window, cx| {
-            Editor::for_multibuffer(multibuffer, Some(project.clone()), true, window, cx)
+            Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx)
         });
 
         let editor_edited = Arc::new(AtomicBool::new(false));
@@ -2830,7 +2830,6 @@ pub mod tests {
                     "main hint #5".to_string(),
                     "other hint(edited) #0".to_string(),
                     "other hint(edited) #1".to_string(),
-                    "other hint(edited) #2".to_string(),
                 ];
                 assert_eq!(
                     expected_hints,
@@ -2921,7 +2920,7 @@ pub mod tests {
 
         cx.executor().run_until_parked();
         let editor = cx.add_window(|window, cx| {
-            Editor::for_multibuffer(multibuffer, Some(project.clone()), true, window, cx)
+            Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx)
         });
         let editor_edited = Arc::new(AtomicBool::new(false));
         let fake_server = fake_servers.next().await.unwrap();

crates/editor/src/items.rs 🔗

@@ -129,13 +129,8 @@ impl FollowableItem for Editor {
                 });
 
                 cx.new(|cx| {
-                    let mut editor = Editor::for_multibuffer(
-                        multibuffer,
-                        Some(project.clone()),
-                        true,
-                        window,
-                        cx,
-                    );
+                    let mut editor =
+                        Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx);
                     editor.remote_id = Some(remote_id);
                     editor
                 })

crates/editor/src/movement.rs 🔗

@@ -893,8 +893,6 @@ mod tests {
                 font,
                 font_size,
                 None,
-                true,
-                1,
                 1,
                 1,
                 FoldPlaceholder::test(),
@@ -1110,138 +1108,136 @@ mod tests {
                     font,
                     px(14.0),
                     None,
-                    true,
                     0,
                     2,
-                    0,
                     FoldPlaceholder::test(),
                     cx,
                 )
             });
             let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
 
-            assert_eq!(snapshot.text(), "\n\nabc\ndefg\n\n\nhijkl\nmn");
+            assert_eq!(snapshot.text(), "abc\ndefg\nhijkl\nmn");
 
             let col_2_x = snapshot
-                .x_for_display_point(DisplayPoint::new(DisplayRow(2), 2), &text_layout_details);
+                .x_for_display_point(DisplayPoint::new(DisplayRow(0), 2), &text_layout_details);
 
             // Can't move up into the first excerpt's header
             assert_eq!(
                 up(
                     &snapshot,
-                    DisplayPoint::new(DisplayRow(2), 2),
+                    DisplayPoint::new(DisplayRow(0), 2),
                     SelectionGoal::HorizontalPosition(col_2_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(DisplayRow(2), 0),
+                    DisplayPoint::new(DisplayRow(0), 0),
                     SelectionGoal::HorizontalPosition(col_2_x.0),
                 ),
             );
             assert_eq!(
                 up(
                     &snapshot,
-                    DisplayPoint::new(DisplayRow(2), 0),
+                    DisplayPoint::new(DisplayRow(0), 0),
                     SelectionGoal::None,
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(DisplayRow(2), 0),
+                    DisplayPoint::new(DisplayRow(0), 0),
                     SelectionGoal::HorizontalPosition(0.0),
                 ),
             );
 
             let col_4_x = snapshot
-                .x_for_display_point(DisplayPoint::new(DisplayRow(3), 4), &text_layout_details);
+                .x_for_display_point(DisplayPoint::new(DisplayRow(1), 4), &text_layout_details);
 
             // Move up and down within first excerpt
             assert_eq!(
                 up(
                     &snapshot,
-                    DisplayPoint::new(DisplayRow(3), 4),
+                    DisplayPoint::new(DisplayRow(1), 4),
                     SelectionGoal::HorizontalPosition(col_4_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(DisplayRow(2), 3),
+                    DisplayPoint::new(DisplayRow(0), 3),
                     SelectionGoal::HorizontalPosition(col_4_x.0)
                 ),
             );
             assert_eq!(
                 down(
                     &snapshot,
-                    DisplayPoint::new(DisplayRow(2), 3),
+                    DisplayPoint::new(DisplayRow(0), 3),
                     SelectionGoal::HorizontalPosition(col_4_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(DisplayRow(3), 4),
+                    DisplayPoint::new(DisplayRow(1), 4),
                     SelectionGoal::HorizontalPosition(col_4_x.0)
                 ),
             );
 
             let col_5_x = snapshot
-                .x_for_display_point(DisplayPoint::new(DisplayRow(6), 5), &text_layout_details);
+                .x_for_display_point(DisplayPoint::new(DisplayRow(2), 5), &text_layout_details);
 
             // Move up and down across second excerpt's header
             assert_eq!(
                 up(
                     &snapshot,
-                    DisplayPoint::new(DisplayRow(6), 5),
+                    DisplayPoint::new(DisplayRow(2), 5),
                     SelectionGoal::HorizontalPosition(col_5_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(DisplayRow(3), 4),
+                    DisplayPoint::new(DisplayRow(1), 4),
                     SelectionGoal::HorizontalPosition(col_5_x.0)
                 ),
             );
             assert_eq!(
                 down(
                     &snapshot,
-                    DisplayPoint::new(DisplayRow(3), 4),
+                    DisplayPoint::new(DisplayRow(1), 4),
                     SelectionGoal::HorizontalPosition(col_5_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(DisplayRow(6), 5),
+                    DisplayPoint::new(DisplayRow(2), 5),
                     SelectionGoal::HorizontalPosition(col_5_x.0)
                 ),
             );
 
             let max_point_x = snapshot
-                .x_for_display_point(DisplayPoint::new(DisplayRow(7), 2), &text_layout_details);
+                .x_for_display_point(DisplayPoint::new(DisplayRow(3), 2), &text_layout_details);
 
             // Can't move down off the end, and attempting to do so leaves the selection goal unchanged
             assert_eq!(
                 down(
                     &snapshot,
-                    DisplayPoint::new(DisplayRow(7), 0),
+                    DisplayPoint::new(DisplayRow(3), 0),
                     SelectionGoal::HorizontalPosition(0.0),
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(DisplayRow(7), 2),
+                    DisplayPoint::new(DisplayRow(3), 2),
                     SelectionGoal::HorizontalPosition(0.0)
                 ),
             );
             assert_eq!(
                 down(
                     &snapshot,
-                    DisplayPoint::new(DisplayRow(7), 2),
+                    DisplayPoint::new(DisplayRow(3), 2),
                     SelectionGoal::HorizontalPosition(max_point_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(DisplayRow(7), 2),
+                    DisplayPoint::new(DisplayRow(3), 2),
                     SelectionGoal::HorizontalPosition(max_point_x.0)
                 ),
             );

crates/editor/src/proposed_changes_editor.rs 🔗

@@ -62,8 +62,7 @@ impl ProposedChangesEditor {
         let (recalculate_diffs_tx, mut recalculate_diffs_rx) = mpsc::unbounded();
         let mut this = Self {
             editor: cx.new(|cx| {
-                let mut editor =
-                    Editor::for_multibuffer(multibuffer.clone(), project, true, window, cx);
+                let mut editor = Editor::for_multibuffer(multibuffer.clone(), project, window, cx);
                 editor.set_expand_all_diff_hunks(cx);
                 editor.set_completion_provider(None);
                 editor.clear_code_action_providers();

crates/editor/src/rust_analyzer_ext.rs 🔗

@@ -86,7 +86,7 @@ pub fn expand_macro_recursively(
                 cx.new(|cx| MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name));
             workspace.add_item_to_active_pane(
                 Box::new(cx.new(|cx| {
-                    let mut editor = Editor::for_multibuffer(multibuffer, None, false, window, cx);
+                    let mut editor = Editor::for_multibuffer(multibuffer, None, window, cx);
                     editor.set_read_only(true);
                     editor
                 })),

crates/editor/src/test.rs 🔗

@@ -61,8 +61,6 @@ pub fn marked_display_snapshot(
             font,
             font_size,
             None,
-            true,
-            1,
             1,
             1,
             FoldPlaceholder::test(),
@@ -108,7 +106,7 @@ pub(crate) fn build_editor(
     window: &mut Window,
     cx: &mut Context<Editor>,
 ) -> Editor {
-    Editor::new(EditorMode::Full, buffer, None, true, window, cx)
+    Editor::new(EditorMode::Full, buffer, None, window, cx)
 }
 
 pub(crate) fn build_editor_with_project(
@@ -117,5 +115,5 @@ pub(crate) fn build_editor_with_project(
     window: &mut Window,
     cx: &mut Context<Editor>,
 ) -> Editor {
-    Editor::new(EditorMode::Full, buffer, Some(project), true, window, cx)
+    Editor::new(EditorMode::Full, buffer, Some(project), window, cx)
 }

crates/git_ui/src/git_panel.rs 🔗

@@ -358,7 +358,6 @@ pub(crate) fn commit_message_editor(
         EditorMode::AutoHeight { max_lines },
         buffer,
         None,
-        false,
         window,
         cx,
     );

crates/git_ui/src/project_diff.rs 🔗

@@ -141,13 +141,8 @@ impl ProjectDiff {
         let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 
         let editor = cx.new(|cx| {
-            let mut diff_display_editor = Editor::for_multibuffer(
-                multibuffer.clone(),
-                Some(project.clone()),
-                true,
-                window,
-                cx,
-            );
+            let mut diff_display_editor =
+                Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx);
             diff_display_editor.disable_inline_diagnostics();
             diff_display_editor.set_expand_all_diff_hunks(cx);
             diff_display_editor.register_addon(GitPanelAddon {

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -360,12 +360,19 @@ impl ExcerptBoundary {
     }
 }
 
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct ExpandInfo {
+    pub direction: ExpandExcerptDirection,
+    pub excerpt_id: ExcerptId,
+}
+
 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
 pub struct RowInfo {
     pub buffer_id: Option<BufferId>,
     pub buffer_row: Option<u32>,
     pub multibuffer_row: Option<MultiBufferRow>,
     pub diff_status: Option<buffer_diff::DiffHunkStatus>,
+    pub expand_info: Option<ExpandInfo>,
 }
 
 /// A slice into a [`Buffer`] that is being edited in a [`MultiBuffer`].
@@ -438,6 +445,7 @@ pub struct DiffTransformSummary {
 pub struct MultiBufferRows<'a> {
     point: Point,
     is_empty: bool,
+    is_singleton: bool,
     cursor: MultiBufferCursor<'a, Point>,
 }
 
@@ -4034,6 +4042,7 @@ impl MultiBufferSnapshot {
         let mut result = MultiBufferRows {
             point: Point::new(0, 0),
             is_empty: self.excerpts.is_empty(),
+            is_singleton: self.is_singleton(),
             cursor,
         };
         result.seek(start_row);
@@ -6216,6 +6225,22 @@ where
         self.cached_region.clone()
     }
 
+    fn is_at_start_of_excerpt(&mut self) -> bool {
+        if self.diff_transforms.start().1 > *self.excerpts.start() {
+            return false;
+        } else if self.diff_transforms.start().1 < *self.excerpts.start() {
+            return true;
+        }
+
+        self.diff_transforms.prev(&());
+        let prev_transform = self.diff_transforms.item();
+        self.diff_transforms.next(&());
+
+        prev_transform.map_or(true, |next_transform| {
+            matches!(next_transform, DiffTransform::BufferContent { .. })
+        })
+    }
+
     fn is_at_end_of_excerpt(&mut self) -> bool {
         if self.diff_transforms.end(&()).1 < self.excerpts.end(&()) {
             return false;
@@ -7092,6 +7117,7 @@ impl Iterator for MultiBufferRows<'_> {
                 buffer_row: Some(0),
                 multibuffer_row: Some(MultiBufferRow(0)),
                 diff_status: None,
+                expand_info: None,
             });
         }
 
@@ -7103,7 +7129,6 @@ impl Iterator for MultiBufferRows<'_> {
             } else {
                 if self.point == self.cursor.diff_transforms.end(&()).0 .0 {
                     let multibuffer_row = MultiBufferRow(self.point.row);
-                    self.point += Point::new(1, 0);
                     let last_excerpt = self
                         .cursor
                         .excerpts
@@ -7115,11 +7140,43 @@ impl Iterator for MultiBufferRows<'_> {
                         .end
                         .to_point(&last_excerpt.buffer)
                         .row;
+
+                    let first_row = last_excerpt
+                        .range
+                        .context
+                        .start
+                        .to_point(&last_excerpt.buffer)
+                        .row;
+
+                    let expand_info = if self.is_singleton {
+                        None
+                    } else {
+                        let needs_expand_up = first_row == last_row
+                            && last_row > 0
+                            && !region.diff_hunk_status.is_some_and(|d| d.is_deleted());
+                        let needs_expand_down = last_row < last_excerpt.buffer.max_point().row;
+
+                        if needs_expand_up && needs_expand_down {
+                            Some(ExpandExcerptDirection::UpAndDown)
+                        } else if needs_expand_up {
+                            Some(ExpandExcerptDirection::Up)
+                        } else if needs_expand_down {
+                            Some(ExpandExcerptDirection::Down)
+                        } else {
+                            None
+                        }
+                        .map(|direction| ExpandInfo {
+                            direction,
+                            excerpt_id: last_excerpt.id,
+                        })
+                    };
+                    self.point += Point::new(1, 0);
                     return Some(RowInfo {
                         buffer_id: Some(last_excerpt.buffer_id),
                         buffer_row: Some(last_row),
                         multibuffer_row: Some(multibuffer_row),
                         diff_status: None,
+                        expand_info,
                     });
                 } else {
                     return None;
@@ -7129,6 +7186,41 @@ impl Iterator for MultiBufferRows<'_> {
 
         let overshoot = self.point - region.range.start;
         let buffer_point = region.buffer_range.start + overshoot;
+        // dbg!(
+        //     buffer_point.row,
+        //     region.range.end.column,
+        //     self.point.row,
+        //     region.range.end.row,
+        //     self.cursor.is_at_end_of_excerpt(),
+        //     region.buffer.max_point().row
+        // );
+        let expand_info = if self.is_singleton {
+            None
+        } else {
+            let needs_expand_up = self.point.row == region.range.start.row
+                && self.cursor.is_at_start_of_excerpt()
+                && buffer_point.row > 0;
+            let needs_expand_down = (region.excerpt.has_trailing_newline
+                && self.point.row + 1 == region.range.end.row
+                || !region.excerpt.has_trailing_newline && self.point.row == region.range.end.row)
+                && self.cursor.is_at_end_of_excerpt()
+                && buffer_point.row < region.buffer.max_point().row;
+
+            if needs_expand_up && needs_expand_down {
+                Some(ExpandExcerptDirection::UpAndDown)
+            } else if needs_expand_up {
+                Some(ExpandExcerptDirection::Up)
+            } else if needs_expand_down {
+                Some(ExpandExcerptDirection::Down)
+            } else {
+                None
+            }
+            .map(|direction| ExpandInfo {
+                direction,
+                excerpt_id: region.excerpt.id,
+            })
+        };
+
         let result = Some(RowInfo {
             buffer_id: Some(region.buffer.remote_id()),
             buffer_row: Some(buffer_point.row),
@@ -7136,6 +7228,7 @@ impl Iterator for MultiBufferRows<'_> {
             diff_status: region
                 .diff_hunk_status
                 .filter(|_| self.point < region.range.end),
+            expand_info,
         });
         self.point += Point::new(1, 0);
         result

crates/multi_buffer/src/multi_buffer_tests.rs 🔗

@@ -29,7 +29,8 @@ fn test_empty_singleton(cx: &mut App) {
             buffer_id: Some(buffer_id),
             buffer_row: Some(0),
             multibuffer_row: Some(MultiBufferRow(0)),
-            diff_status: None
+            diff_status: None,
+            expand_info: None,
         }]
     );
 }
@@ -2118,6 +2119,7 @@ struct ReferenceRegion {
     range: Range<usize>,
     buffer_start: Option<Point>,
     status: Option<DiffHunkStatus>,
+    excerpt_id: Option<ExcerptId>,
 }
 
 impl ReferenceMultibuffer {
@@ -2274,6 +2276,7 @@ impl ReferenceMultibuffer {
                         range: len..text.len(),
                         buffer_start: Some(buffer.offset_to_point(offset)),
                         status: None,
+                        excerpt_id: Some(excerpt.id),
                     });
 
                     // Add the deleted text for the hunk.
@@ -2293,6 +2296,7 @@ impl ReferenceMultibuffer {
                                 base_buffer.offset_to_point(hunk.diff_base_byte_range.start),
                             ),
                             status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
+                            excerpt_id: Some(excerpt.id),
                         });
                     }
 
@@ -2308,6 +2312,7 @@ impl ReferenceMultibuffer {
                         range: len..text.len(),
                         buffer_start: Some(buffer.offset_to_point(offset)),
                         status: Some(DiffHunkStatus::added(hunk.secondary_status)),
+                        excerpt_id: Some(excerpt.id),
                     });
                     offset = hunk_range.end;
                 }
@@ -2322,6 +2327,7 @@ impl ReferenceMultibuffer {
                 range: len..text.len(),
                 buffer_start: Some(buffer.offset_to_point(offset)),
                 status: None,
+                excerpt_id: Some(excerpt.id),
             });
         }
 
@@ -2332,6 +2338,7 @@ impl ReferenceMultibuffer {
                 range: 0..1,
                 buffer_start: Some(Point::new(0, 0)),
                 status: None,
+                excerpt_id: None,
             });
         } else {
             text.pop();
@@ -2345,12 +2352,58 @@ impl ReferenceMultibuffer {
             .map(|line| {
                 let row_info = regions
                     .iter()
-                    .find(|region| region.range.contains(&ix))
-                    .map_or(RowInfo::default(), |region| {
+                    .position(|region| region.range.contains(&ix))
+                    .map_or(RowInfo::default(), |region_ix| {
+                        let region = &regions[region_ix];
                         let buffer_row = region.buffer_start.map(|start_point| {
                             start_point.row
                                 + text[region.range.start..ix].matches('\n').count() as u32
                         });
+                        let is_excerpt_start = region_ix == 0
+                            || &regions[region_ix - 1].excerpt_id != &region.excerpt_id
+                            || regions[region_ix - 1].range.is_empty();
+                        let mut is_excerpt_end = region_ix == regions.len() - 1
+                            || &regions[region_ix + 1].excerpt_id != &region.excerpt_id;
+                        let is_start = !text[region.range.start..ix].contains('\n');
+                        let mut is_end = if region.range.end > text.len() {
+                            !text[ix..].contains('\n')
+                        } else {
+                            text[ix..region.range.end.min(text.len())]
+                                .matches('\n')
+                                .count()
+                                == 1
+                        };
+                        if region_ix < regions.len() - 1
+                            && !text[ix..].contains("\n")
+                            && region.status == Some(DiffHunkStatus::added_none())
+                            && regions[region_ix + 1].excerpt_id == region.excerpt_id
+                            && regions[region_ix + 1].range.start == text.len()
+                        {
+                            is_end = true;
+                            is_excerpt_end = true;
+                        }
+                        let mut expand_direction = None;
+                        if let Some(buffer) = &self
+                            .excerpts
+                            .iter()
+                            .find(|e| e.id == region.excerpt_id.unwrap())
+                            .map(|e| e.buffer.clone())
+                        {
+                            let needs_expand_up =
+                                is_excerpt_start && is_start && buffer_row.unwrap() > 0;
+                            let needs_expand_down = is_excerpt_end
+                                && is_end
+                                && buffer.read(cx).max_point().row > buffer_row.unwrap();
+                            expand_direction = if needs_expand_up && needs_expand_down {
+                                Some(ExpandExcerptDirection::UpAndDown)
+                            } else if needs_expand_up {
+                                Some(ExpandExcerptDirection::Up)
+                            } else if needs_expand_down {
+                                Some(ExpandExcerptDirection::Down)
+                            } else {
+                                None
+                            };
+                        }
                         RowInfo {
                             buffer_id: region.buffer_id,
                             diff_status: region.status,
@@ -2358,6 +2411,12 @@ impl ReferenceMultibuffer {
                             multibuffer_row: Some(MultiBufferRow(
                                 text[..ix].matches('\n').count() as u32
                             )),
+                            expand_info: expand_direction.zip(region.excerpt_id).map(
+                                |(direction, excerpt_id)| ExpandInfo {
+                                    direction,
+                                    excerpt_id,
+                                },
+                            ),
                         }
                     });
                 ix += line.len() + 1;

crates/outline_panel/src/outline_panel.rs 🔗

@@ -1075,45 +1075,36 @@ impl OutlinePanel {
                     });
                 } else {
                     let mut offset = Point::default();
-                    let show_excerpt_controls = active_editor
-                        .read(cx)
-                        .display_map
-                        .read(cx)
-                        .show_excerpt_controls();
                     let expand_excerpt_control_height = 1.0;
                     if let Some(buffer_id) = scroll_to_buffer {
                         let current_folded = active_editor.read(cx).is_buffer_folded(buffer_id, cx);
                         if current_folded {
-                            if show_excerpt_controls {
-                                let previous_buffer_id = self
-                                    .fs_entries
-                                    .iter()
-                                    .rev()
-                                    .filter_map(|entry| match entry {
-                                        FsEntry::File(file) => Some(file.buffer_id),
-                                        FsEntry::ExternalFile(external_file) => {
-                                            Some(external_file.buffer_id)
-                                        }
-                                        FsEntry::Directory(..) => None,
-                                    })
-                                    .skip_while(|id| *id != buffer_id)
-                                    .nth(1);
-                                if let Some(previous_buffer_id) = previous_buffer_id {
-                                    if !active_editor
-                                        .read(cx)
-                                        .is_buffer_folded(previous_buffer_id, cx)
-                                    {
-                                        offset.y += expand_excerpt_control_height;
+                            let previous_buffer_id = self
+                                .fs_entries
+                                .iter()
+                                .rev()
+                                .filter_map(|entry| match entry {
+                                    FsEntry::File(file) => Some(file.buffer_id),
+                                    FsEntry::ExternalFile(external_file) => {
+                                        Some(external_file.buffer_id)
                                     }
+                                    FsEntry::Directory(..) => None,
+                                })
+                                .skip_while(|id| *id != buffer_id)
+                                .nth(1);
+                            if let Some(previous_buffer_id) = previous_buffer_id {
+                                if !active_editor
+                                    .read(cx)
+                                    .is_buffer_folded(previous_buffer_id, cx)
+                                {
+                                    offset.y += expand_excerpt_control_height;
                                 }
                             }
                         } else {
                             if multi_buffer_snapshot.as_singleton().is_none() {
                                 offset.y = -(active_editor.read(cx).file_header_size() as f32);
                             }
-                            if show_excerpt_controls {
-                                offset.y -= expand_excerpt_control_height;
-                            }
+                            offset.y -= expand_excerpt_control_height;
                         }
                     }
                     active_editor.update(cx, |editor, cx| {

crates/repl/src/outputs.rs 🔗

@@ -185,13 +185,7 @@ impl Output {
                                             multi_buffer
                                         });
 
-                                        Editor::for_multibuffer(
-                                            multibuffer,
-                                            None,
-                                            false,
-                                            window,
-                                            cx,
-                                        )
+                                        Editor::for_multibuffer(multibuffer, None, window, cx)
                                     }));
                                     workspace
                                         .update(cx, |workspace, cx| {

crates/search/src/buffer_search.rs 🔗

@@ -2678,7 +2678,7 @@ mod tests {
                 ],
                 cx,
             );
-            Editor::for_multibuffer(multibuffer, None, false, window, cx)
+            Editor::for_multibuffer(multibuffer, None, window, cx)
         });
 
         let search_bar = cx.new_window_entity(|window, cx| {

crates/search/src/project_search.rs 🔗

@@ -739,8 +739,7 @@ impl ProjectSearchView {
             editor
         });
         let results_editor = cx.new(|cx| {
-            let mut editor =
-                Editor::for_multibuffer(excerpts, Some(project.clone()), true, window, cx);
+            let mut editor = Editor::for_multibuffer(excerpts, Some(project.clone()), window, cx);
             editor.set_searchable(false);
             editor.set_in_project_search(true);
             editor
@@ -2273,7 +2272,7 @@ pub mod tests {
                 search_view
                     .results_editor
                     .update(cx, |editor, cx| editor.display_text(cx)),
-                "\n\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\n\n\nconst TWO: usize = one::ONE + one::ONE;\n"
+                "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;"
             );
             let match_background_color = cx.theme().colors().search_match_background;
             assert_eq!(
@@ -2282,15 +2281,15 @@ pub mod tests {
                     .update(cx, |editor, cx| editor.all_text_background_highlights(window, cx)),
                 &[
                     (
-                        DisplayPoint::new(DisplayRow(3), 32)..DisplayPoint::new(DisplayRow(3), 35),
+                        DisplayPoint::new(DisplayRow(2), 32)..DisplayPoint::new(DisplayRow(2), 35),
                         match_background_color
                     ),
                     (
-                        DisplayPoint::new(DisplayRow(3), 37)..DisplayPoint::new(DisplayRow(3), 40),
+                        DisplayPoint::new(DisplayRow(2), 37)..DisplayPoint::new(DisplayRow(2), 40),
                         match_background_color
                     ),
                     (
-                        DisplayPoint::new(DisplayRow(8), 6)..DisplayPoint::new(DisplayRow(8), 9),
+                        DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 9),
                         match_background_color
                     )
                 ]
@@ -2300,7 +2299,7 @@ pub mod tests {
                 search_view
                     .results_editor
                     .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(DisplayRow(3), 32)..DisplayPoint::new(DisplayRow(3), 35)]
+                [DisplayPoint::new(DisplayRow(2), 32)..DisplayPoint::new(DisplayRow(2), 35)]
             );
 
             search_view.select_match(Direction::Next, window, cx);
@@ -2313,7 +2312,7 @@ pub mod tests {
                     search_view
                         .results_editor
                         .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                    [DisplayPoint::new(DisplayRow(3), 37)..DisplayPoint::new(DisplayRow(3), 40)]
+                    [DisplayPoint::new(DisplayRow(2), 37)..DisplayPoint::new(DisplayRow(2), 40)]
                 );
                 search_view.select_match(Direction::Next, window, cx);
             })
@@ -2326,7 +2325,7 @@ pub mod tests {
                     search_view
                         .results_editor
                         .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                    [DisplayPoint::new(DisplayRow(8), 6)..DisplayPoint::new(DisplayRow(8), 9)]
+                    [DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 9)]
                 );
                 search_view.select_match(Direction::Next, window, cx);
             })
@@ -2339,7 +2338,7 @@ pub mod tests {
                     search_view
                         .results_editor
                         .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                    [DisplayPoint::new(DisplayRow(3), 32)..DisplayPoint::new(DisplayRow(3), 35)]
+                    [DisplayPoint::new(DisplayRow(2), 32)..DisplayPoint::new(DisplayRow(2), 35)]
                 );
                 search_view.select_match(Direction::Prev, window, cx);
             })
@@ -2352,7 +2351,7 @@ pub mod tests {
                     search_view
                         .results_editor
                         .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                    [DisplayPoint::new(DisplayRow(8), 6)..DisplayPoint::new(DisplayRow(8), 9)]
+                    [DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 9)]
                 );
                 search_view.select_match(Direction::Prev, window, cx);
             })
@@ -2365,7 +2364,7 @@ pub mod tests {
                     search_view
                         .results_editor
                         .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                    [DisplayPoint::new(DisplayRow(3), 37)..DisplayPoint::new(DisplayRow(3), 40)]
+                    [DisplayPoint::new(DisplayRow(2), 37)..DisplayPoint::new(DisplayRow(2), 40)]
                 );
             })
             .unwrap();
@@ -2538,7 +2537,7 @@ pub mod tests {
                     search_view
                         .results_editor
                         .update(cx, |editor, cx| editor.display_text(cx)),
-                    "\n\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\n\n\nconst TWO: usize = one::ONE + one::ONE;\n",
+                    "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
                     "Search view results should match the query"
                 );
                 assert!(
@@ -2582,7 +2581,7 @@ pub mod tests {
                     search_view
                         .results_editor
                         .update(cx, |editor, cx| editor.display_text(cx)),
-                    "\n\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\n\n\nconst TWO: usize = one::ONE + one::ONE;\n",
+                    "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
                     "Results should be unchanged after search view 2nd open in a row"
                 );
                 assert!(
@@ -2774,7 +2773,7 @@ pub mod tests {
                     search_view
                         .results_editor
                         .update(cx, |editor, cx| editor.display_text(cx)),
-                    "\n\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\n\n\nconst TWO: usize = one::ONE + one::ONE;\n",
+                    "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
                     "Search view results should match the query"
                 );
                 assert!(
@@ -2829,7 +2828,7 @@ pub mod tests {
                         search_view
                             .results_editor
                             .update(cx, |editor, cx| editor.display_text(cx)),
-                        "\n\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\n\n\nconst TWO: usize = one::ONE + one::ONE;\n",
+                        "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
                         "Results of the first search view should not update too"
                     );
                     assert!(
@@ -2878,7 +2877,7 @@ pub mod tests {
                         search_view_2
                             .results_editor
                             .update(cx, |editor, cx| editor.display_text(cx)),
-                        "\n\n\nconst FOUR: usize = one::ONE + three::THREE;\n",
+                        "\n\nconst FOUR: usize = one::ONE + three::THREE;",
                         "New search view with the updated query should have new search results"
                     );
                     assert!(
@@ -3023,7 +3022,7 @@ pub mod tests {
                 search_view
                     .results_editor
                     .update(cx, |editor, cx| editor.display_text(cx)),
-                "\n\n\nconst ONE: usize = 1;\n\n\n\n\nconst TWO: usize = one::ONE + one::ONE;\n",
+                "\n\nconst ONE: usize = 1;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
                 "New search in directory should have a filter that matches a certain directory"
             );
                 })

crates/vim/src/test.rs 🔗

@@ -1728,14 +1728,7 @@ async fn test_folded_multibuffer_excerpts(cx: &mut gpui::TestAppContext) {
             ],
             cx,
         );
-        let mut editor = Editor::new(
-            EditorMode::Full,
-            multi_buffer.clone(),
-            None,
-            true,
-            window,
-            cx,
-        );
+        let mut editor = Editor::new(EditorMode::Full, multi_buffer.clone(), None, window, cx);
 
         let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
         // fold all but the second buffer, so that we test navigating between two

crates/zed/src/zed.rs 🔗

@@ -1138,7 +1138,7 @@ fn open_log_file(workspace: &mut Workspace, window: &mut Window, cx: &mut Contex
                             .new(|cx| MultiBuffer::singleton(buffer, cx).with_title("Log".into()));
                         let editor = cx.new(|cx| {
                             let mut editor =
-                                Editor::for_multibuffer(buffer, Some(project), true, window, cx);
+                                Editor::for_multibuffer(buffer, Some(project), window, cx);
                             editor.set_read_only(true);
                             editor.set_breadcrumb_header(format!(
                                 "Last {} lines in {}",
@@ -1626,7 +1626,7 @@ fn open_telemetry_log_file(
                 });
                 workspace.add_item_to_active_pane(
                     Box::new(cx.new(|cx| {
-                        let mut editor = Editor::for_multibuffer(buffer, Some(project), true, window, cx);
+                        let mut editor = Editor::for_multibuffer(buffer, Some(project), window, cx);
                         editor.set_read_only(true);
                         editor.set_breadcrumb_header("Telemetry Log".into());
                         editor
@@ -1665,13 +1665,8 @@ fn open_bundled_file(
                         cx.new(|cx| MultiBuffer::singleton(buffer, cx).with_title(title.into()));
                     workspace.add_item_to_active_pane(
                         Box::new(cx.new(|cx| {
-                            let mut editor = Editor::for_multibuffer(
-                                buffer,
-                                Some(project.clone()),
-                                true,
-                                window,
-                                cx,
-                            );
+                            let mut editor =
+                                Editor::for_multibuffer(buffer, Some(project.clone()), window, cx);
                             editor.set_read_only(true);
                             editor.set_breadcrumb_header(title.into());
                             editor