Detailed changes
@@ -356,6 +356,49 @@
"vertical": true
}
},
+ // Minimap related settings
+ "minimap": {
+ // When to show the minimap in the editor.
+ // This setting can take three values:
+ // 1. Show the minimap if the editor's scrollbar is visible:
+ // "auto"
+ // 2. Always show the minimap:
+ // "always"
+ // 3. Never show the minimap:
+ // "never" (default)
+ "show": "never",
+ // When to show the minimap thumb.
+ // This setting can take two values:
+ // 1. Show the minimap thumb if the mouse is over the minimap:
+ // "hover"
+ // 2. Always show the minimap thumb:
+ // "always" (default)
+ "thumb": "always",
+ // How the minimap thumb border should look.
+ // This setting can take five values:
+ // 1. Display a border on all sides of the thumb:
+ // "thumb_border": "full"
+ // 2. Display a border on all sides except the left side of the thumb:
+ // "thumb_border": "left_open" (default)
+ // 3. Display a border on all sides except the right side of the thumb:
+ // "thumb_border": "right_open"
+ // 4. Display a border only on the left side of the thumb:
+ // "thumb_border": "left_only"
+ // 5. Display the thumb without any border:
+ // "thumb_border": "none"
+ "thumb_border": "left_open",
+ // How to highlight the current line in the minimap.
+ // This setting can take the following values:
+ //
+ // 1. `null` to inherit the editor `current_line_highlight` setting (default)
+ // 2. "line" or "all" to highlight the current line in the minimap.
+ // 3. "gutter" or "none" to not highlight the current line in the minimap.
+ "current_line_highlight": null,
+ // The width of the minimap in pixels.
+ "width": 100,
+ // The font size of the minimap in pixels.
+ "font_size": 2
+ },
// Enable middle-click paste on Linux.
"middle_click_paste": true,
// What to do when multibuffer is double clicked in some of its excerpts
@@ -8,9 +8,10 @@ use anyhow::{Context as _, Result};
use assistant_settings::AssistantSettings;
use client::telemetry::Telemetry;
use collections::{HashMap, HashSet, VecDeque, hash_map};
+use editor::display_map::EditorMargins;
use editor::{
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange,
- GutterDimensions, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
+ MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
actions::SelectAll,
display_map::{
BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
@@ -458,11 +459,11 @@ impl InlineAssistant {
)
});
- let gutter_dimensions = Arc::new(Mutex::new(GutterDimensions::default()));
+ let editor_margins = Arc::new(Mutex::new(EditorMargins::default()));
let prompt_editor = cx.new(|cx| {
PromptEditor::new_buffer(
assist_id,
- gutter_dimensions.clone(),
+ editor_margins,
self.prompt_history.clone(),
prompt_buffer.clone(),
codegen.clone(),
@@ -577,11 +578,11 @@ impl InlineAssistant {
)
});
- let gutter_dimensions = Arc::new(Mutex::new(GutterDimensions::default()));
+ let editor_margins = Arc::new(Mutex::new(EditorMargins::default()));
let prompt_editor = cx.new(|cx| {
PromptEditor::new_buffer(
assist_id,
- gutter_dimensions.clone(),
+ editor_margins,
self.prompt_history.clone(),
prompt_buffer.clone(),
codegen.clone(),
@@ -650,6 +651,7 @@ impl InlineAssistant {
height: Some(prompt_editor_height),
render: build_assist_editor_renderer(prompt_editor),
priority: 0,
+ render_in_minimap: false,
},
BlockProperties {
style: BlockStyle::Sticky,
@@ -664,6 +666,7 @@ impl InlineAssistant {
.into_any_element()
}),
priority: 0,
+ render_in_minimap: false,
},
];
@@ -1405,11 +1408,11 @@ impl InlineAssistant {
enum DeletedLines {}
let mut editor = Editor::for_multibuffer(multi_buffer, None, window, cx);
+ editor.disable_scrollbars_and_minimap(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);
editor.scroll_manager.set_forbid_vertical_scroll(true);
- editor.set_show_scrollbars(false, cx);
editor.set_read_only(true);
editor.set_show_edit_predictions(Some(false), window, cx);
editor.highlight_rows::<DeletedLines>(
@@ -1433,11 +1436,12 @@ impl InlineAssistant {
.bg(cx.theme().status().deleted_background)
.size_full()
.h(height as f32 * cx.window.line_height())
- .pl(cx.gutter_dimensions.full_width())
+ .pl(cx.margins.gutter.full_width())
.child(deleted_lines_editor.clone())
.into_any_element()
}),
priority: 0,
+ render_in_minimap: false,
});
}
@@ -1595,9 +1599,9 @@ fn build_assist_editor_renderer(editor: &Entity<PromptEditor<BufferCodegen>>) ->
let editor = editor.clone();
Arc::new(move |cx: &mut BlockContext| {
- let gutter_dimensions = editor.read(cx).gutter_dimensions();
+ let editor_margins = editor.read(cx).editor_margins();
- *gutter_dimensions.lock() = *cx.gutter_dimensions;
+ *editor_margins.lock() = *cx.margins;
editor.clone().into_any_element()
})
}
@@ -11,9 +11,9 @@ use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
use crate::{RemoveAllContext, ToggleContextPicker};
use client::ErrorExt;
use collections::VecDeque;
+use editor::display_map::EditorMargins;
use editor::{
- ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle,
- GutterDimensions, MultiBuffer,
+ ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
actions::{MoveDown, MoveUp},
};
use feature_flags::{FeatureFlagAppExt as _, ZedProFeatureFlag};
@@ -61,11 +61,13 @@ impl<T: 'static> Render for PromptEditor<T> {
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
let mut buttons = Vec::new();
- let left_gutter_width = match &self.mode {
+ const RIGHT_PADDING: Pixels = px(9.);
+
+ let (left_gutter_width, right_padding) = match &self.mode {
PromptEditorMode::Buffer {
id: _,
codegen,
- gutter_dimensions,
+ editor_margins,
} => {
let codegen = codegen.read(cx);
@@ -73,13 +75,17 @@ impl<T: 'static> Render for PromptEditor<T> {
buttons.push(self.render_cycle_controls(&codegen, cx));
}
- let gutter_dimensions = gutter_dimensions.lock();
+ let editor_margins = editor_margins.lock();
+ let gutter = editor_margins.gutter;
+
+ let left_gutter_width = gutter.full_width() + (gutter.margin / 2.0);
+ let right_padding = editor_margins.right + RIGHT_PADDING;
- gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)
+ (left_gutter_width, right_padding)
}
PromptEditorMode::Terminal { .. } => {
// Give the equivalent of the same left-padding that we're using on the right
- Pixels::from(40.0)
+ (Pixels::from(40.0), Pixels::from(24.))
}
};
@@ -100,7 +106,7 @@ impl<T: 'static> Render for PromptEditor<T> {
.size_full()
.pt_0p5()
.pb(bottom_padding)
- .pr_6()
+ .pr(right_padding)
.child(
h_flex()
.items_start()
@@ -806,7 +812,7 @@ pub enum PromptEditorMode {
Buffer {
id: InlineAssistId,
codegen: Entity<BufferCodegen>,
- gutter_dimensions: Arc<Mutex<GutterDimensions>>,
+ editor_margins: Arc<Mutex<EditorMargins>>,
},
Terminal {
id: TerminalInlineAssistId,
@@ -838,7 +844,7 @@ impl InlineAssistId {
impl PromptEditor<BufferCodegen> {
pub fn new_buffer(
id: InlineAssistId,
- gutter_dimensions: Arc<Mutex<GutterDimensions>>,
+ editor_margins: Arc<Mutex<EditorMargins>>,
prompt_history: VecDeque<String>,
prompt_buffer: Entity<MultiBuffer>,
codegen: Entity<BufferCodegen>,
@@ -855,7 +861,7 @@ impl PromptEditor<BufferCodegen> {
let mode = PromptEditorMode::Buffer {
id,
codegen,
- gutter_dimensions,
+ editor_margins,
};
let prompt_editor = cx.new(|cx| {
@@ -995,11 +1001,9 @@ impl PromptEditor<BufferCodegen> {
}
}
- pub fn gutter_dimensions(&self) -> &Arc<Mutex<GutterDimensions>> {
+ pub fn editor_margins(&self) -> &Arc<Mutex<EditorMargins>> {
match &self.mode {
- PromptEditorMode::Buffer {
- gutter_dimensions, ..
- } => gutter_dimensions,
+ PromptEditorMode::Buffer { editor_margins, .. } => editor_margins,
PromptEditorMode::Terminal { .. } => unreachable!(),
}
}
@@ -9,12 +9,11 @@ use client::{ErrorExt, telemetry::Telemetry};
use collections::{HashMap, HashSet, VecDeque, hash_map};
use editor::{
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode,
- EditorStyle, ExcerptId, ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot,
- ToOffset as _, ToPoint,
+ EditorStyle, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
actions::{MoveDown, MoveUp, SelectAll},
display_map::{
- BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
- ToDisplayPoint,
+ BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, EditorMargins,
+ RenderBlock, ToDisplayPoint,
},
};
use feature_flags::{FeatureFlagAppExt as _, ZedProFeatureFlag};
@@ -338,11 +337,11 @@ impl InlineAssistant {
)
});
- let gutter_dimensions = Arc::new(Mutex::new(GutterDimensions::default()));
+ let editor_margins = Arc::new(Mutex::new(EditorMargins::default()));
let prompt_editor = cx.new(|cx| {
PromptEditor::new(
assist_id,
- gutter_dimensions.clone(),
+ editor_margins,
self.prompt_history.clone(),
prompt_buffer.clone(),
codegen.clone(),
@@ -447,11 +446,11 @@ impl InlineAssistant {
)
});
- let gutter_dimensions = Arc::new(Mutex::new(GutterDimensions::default()));
+ let editor_margins = Arc::new(Mutex::new(EditorMargins::default()));
let prompt_editor = cx.new(|cx| {
PromptEditor::new(
assist_id,
- gutter_dimensions.clone(),
+ editor_margins,
self.prompt_history.clone(),
prompt_buffer.clone(),
codegen.clone(),
@@ -520,6 +519,7 @@ impl InlineAssistant {
height: Some(prompt_editor_height),
render: build_assist_editor_renderer(prompt_editor),
priority: 0,
+ render_in_minimap: false,
},
BlockProperties {
style: BlockStyle::Sticky,
@@ -534,6 +534,7 @@ impl InlineAssistant {
.into_any_element()
}),
priority: 0,
+ render_in_minimap: false,
},
];
@@ -1271,11 +1272,11 @@ impl InlineAssistant {
enum DeletedLines {}
let mut editor = Editor::for_multibuffer(multi_buffer, None, window, cx);
+ editor.disable_scrollbars_and_minimap(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);
editor.scroll_manager.set_forbid_vertical_scroll(true);
- editor.set_show_scrollbars(false, cx);
editor.set_read_only(true);
editor.set_show_edit_predictions(Some(false), window, cx);
editor.highlight_rows::<DeletedLines>(
@@ -1299,11 +1300,12 @@ impl InlineAssistant {
.bg(cx.theme().status().deleted_background)
.size_full()
.h(height as f32 * cx.window.line_height())
- .pl(cx.gutter_dimensions.full_width())
+ .pl(cx.margins.gutter.full_width())
.child(deleted_lines_editor.clone())
.into_any_element()
}),
priority: 0,
+ render_in_minimap: false,
});
}
@@ -1410,7 +1412,7 @@ impl InlineAssistGroup {
fn build_assist_editor_renderer(editor: &Entity<PromptEditor>) -> RenderBlock {
let editor = editor.clone();
Arc::new(move |cx: &mut BlockContext| {
- *editor.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
+ *editor.read(cx).editor_margins.lock() = *cx.margins;
editor.clone().into_any_element()
})
}
@@ -1450,7 +1452,7 @@ struct PromptEditor {
editor: Entity<Editor>,
language_model_selector: Entity<LanguageModelSelector>,
edited_since_done: bool,
- gutter_dimensions: Arc<Mutex<GutterDimensions>>,
+ editor_margins: Arc<Mutex<EditorMargins>>,
prompt_history: VecDeque<String>,
prompt_history_ix: Option<usize>,
pending_prompt: String,
@@ -1474,7 +1476,8 @@ impl EventEmitter<PromptEditorEvent> for PromptEditor {}
impl Render for PromptEditor {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- let gutter_dimensions = *self.gutter_dimensions.lock();
+ let editor_margins = *self.editor_margins.lock();
+ let gutter_dimensions = editor_margins.gutter;
let codegen = self.codegen.read(cx);
let mut buttons = Vec::new();
@@ -1599,6 +1602,7 @@ impl Render for PromptEditor {
.border_y_1()
.border_color(cx.theme().status().info_border)
.size_full()
+ .pr(editor_margins.right)
.py(window.line_height() / 2.5)
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::cancel))
@@ -1681,7 +1685,7 @@ impl Render for PromptEditor {
.child(
h_flex()
.gap_2()
- .pr_6()
+ .pr(px(9.))
.children(self.render_token_count(cx))
.children(buttons),
)
@@ -1699,7 +1703,7 @@ impl PromptEditor {
fn new(
id: InlineAssistId,
- gutter_dimensions: Arc<Mutex<GutterDimensions>>,
+ editor_margins: Arc<Mutex<EditorMargins>>,
prompt_history: VecDeque<String>,
prompt_buffer: Entity<MultiBuffer>,
codegen: Entity<Codegen>,
@@ -1762,7 +1766,7 @@ impl PromptEditor {
)
}),
edited_since_done: false,
- gutter_dimensions,
+ editor_margins,
prompt_history,
prompt_history_ix: None,
pending_prompt: String::new(),
@@ -242,9 +242,9 @@ impl ContextEditor {
let editor = cx.new(|cx| {
let mut editor =
Editor::for_buffer(context.read(cx).buffer().clone(), None, window, cx);
+ editor.disable_scrollbars_and_minimap(cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_line_numbers(false, cx);
- editor.set_show_scrollbars(false, cx);
editor.set_show_git_diff_gutter(false, cx);
editor.set_show_code_actions(false, cx);
editor.set_show_runnables(false, cx);
@@ -942,7 +942,7 @@ impl ContextEditor {
let patch_range = range.clone();
move |cx: &mut BlockContext| {
let max_width = cx.max_width;
- let gutter_width = cx.gutter_dimensions.full_width();
+ let gutter_width = cx.margins.gutter.full_width();
let block_id = cx.block_id;
let selected = cx.selected;
let window = &mut cx.window;
@@ -1488,7 +1488,7 @@ impl ContextEditor {
h_flex()
.id(("message_header", message_id.as_u64()))
- .pl(cx.gutter_dimensions.full_width())
+ .pl(cx.margins.gutter.full_width())
.h_11()
.w_full()
.relative()
@@ -1583,6 +1583,7 @@ impl ContextEditor {
),
priority: usize::MAX,
render: render_block(MessageMetadata::from(message)),
+ render_in_minimap: false,
};
let mut new_blocks = vec![];
let mut block_index_to_message = vec![];
@@ -2157,12 +2158,12 @@ impl ContextEditor {
let image_size = size_for_image(
&image,
size(
- cx.max_width - cx.gutter_dimensions.full_width(),
+ cx.max_width - cx.margins.gutter.full_width(),
MAX_HEIGHT_IN_LINES as f32 * cx.line_height,
),
);
h_flex()
- .pl(cx.gutter_dimensions.full_width())
+ .pl(cx.margins.gutter.full_width())
.child(
img(image.clone())
.object_fit(gpui::ObjectFit::ScaleDown)
@@ -2172,6 +2173,7 @@ impl ContextEditor {
.into_any_element()
}),
priority: 0,
+ render_in_minimap: false,
})
})
.collect::<Vec<_>>();
@@ -360,9 +360,9 @@ impl EditFileToolCard {
editor.set_show_gutter(false, cx);
editor.disable_inline_diagnostics();
editor.disable_expand_excerpt_buttons(cx);
+ editor.disable_scrollbars_and_minimap(cx);
editor.set_soft_wrap_mode(SoftWrap::None, cx);
editor.scroll_manager.set_forbid_vertical_scroll(true);
- editor.set_show_scrollbars(false, cx);
editor.set_show_indent_guides(false, cx);
editor.set_read_only(true);
editor.set_show_breakpoints(false, cx);
@@ -145,6 +145,7 @@ impl editor::DiagnosticRenderer for DiagnosticRenderer {
style: BlockStyle::Flex,
render: Arc::new(move |bcx| block.render_block(editor.clone(), bcx)),
priority: 1,
+ render_in_minimap: false,
}
})
.collect()
@@ -632,6 +632,7 @@ impl ProjectDiagnosticsEditor {
block.render_block(editor.clone(), bcx)
}),
priority: 1,
+ render_in_minimap: false,
}
});
let block_ids = this.editor.update(cx, |editor, cx| {
@@ -31,7 +31,7 @@ use crate::{
};
pub use block_map::{
Block, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, BlockPlacement,
- BlockPoint, BlockProperties, BlockRows, BlockStyle, CustomBlockId, RenderBlock,
+ BlockPoint, BlockProperties, BlockRows, BlockStyle, CustomBlockId, EditorMargins, RenderBlock,
StickyHeaderExcerpt,
};
use block_map::{BlockRow, BlockSnapshot};
@@ -258,6 +258,7 @@ impl DisplayMap {
height: Some(height),
style,
priority,
+ render_in_minimap: true,
}
}),
);
@@ -950,16 +951,17 @@ impl DisplaySnapshot {
diagnostic_highlight.fade_out = Some(editor_style.unnecessary_code_fade);
}
- if let Some(severity) = chunk.diagnostic_severity {
- // Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
- if severity <= DiagnosticSeverity::WARNING || !chunk.is_unnecessary {
- let diagnostic_color = super::diagnostic_style(severity, &editor_style.status);
- diagnostic_highlight.underline = Some(UnderlineStyle {
- color: Some(diagnostic_color),
- thickness: 1.0.into(),
- wavy: true,
- });
- }
+ // Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
+ if let Some(severity) = chunk.diagnostic_severity.filter(|severity| {
+ editor_style.show_underlines
+ && (!chunk.is_unnecessary || *severity <= DiagnosticSeverity::WARNING)
+ }) {
+ let diagnostic_color = super::diagnostic_style(severity, &editor_style.status);
+ diagnostic_highlight.underline = Some(UnderlineStyle {
+ color: Some(diagnostic_color),
+ thickness: 1.0.into(),
+ wavy: true,
+ });
}
if let Some(highlight_style) = highlight_style.as_mut() {
@@ -1613,6 +1615,7 @@ pub mod tests {
height: Some(height),
render: Arc::new(|_| div().into_any()),
priority,
+ render_in_minimap: true,
}
})
.collect::<Vec<_>>();
@@ -1975,6 +1978,7 @@ pub mod tests {
style: BlockStyle::Sticky,
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
}],
cx,
);
@@ -2170,6 +2174,7 @@ pub mod tests {
style: BlockStyle::Sticky,
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
BlockProperties {
placement: BlockPlacement::Below(
@@ -2179,6 +2184,7 @@ pub mod tests {
style: BlockStyle::Sticky,
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
],
cx,
@@ -2284,6 +2290,7 @@ pub mod tests {
style: BlockStyle::Sticky,
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
}],
cx,
)
@@ -2358,6 +2365,7 @@ pub mod tests {
style: BlockStyle::Fixed,
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
}],
cx,
);
@@ -193,6 +193,7 @@ pub struct CustomBlock {
style: BlockStyle,
render: Arc<Mutex<RenderBlock>>,
priority: usize,
+ pub(crate) render_in_minimap: bool,
}
#[derive(Clone)]
@@ -204,6 +205,7 @@ pub struct BlockProperties<P> {
pub style: BlockStyle,
pub render: RenderBlock,
pub priority: usize,
+ pub render_in_minimap: bool,
}
impl<P: Debug> Debug for BlockProperties<P> {
@@ -223,6 +225,12 @@ pub enum BlockStyle {
Sticky,
}
+#[derive(Debug, Default, Copy, Clone)]
+pub struct EditorMargins {
+ pub gutter: GutterDimensions,
+ pub right: Pixels,
+}
+
#[derive(gpui::AppContext, gpui::VisualContext)]
pub struct BlockContext<'a, 'b> {
#[window]
@@ -231,7 +239,7 @@ pub struct BlockContext<'a, 'b> {
pub app: &'b mut App,
pub anchor_x: Pixels,
pub max_width: Pixels,
- pub gutter_dimensions: &'b GutterDimensions,
+ pub margins: &'b EditorMargins,
pub em_width: Pixels,
pub line_height: Pixels,
pub block_id: BlockId,
@@ -1037,6 +1045,7 @@ impl BlockMapWriter<'_> {
render: Arc::new(Mutex::new(block.render)),
style: block.style,
priority: block.priority,
+ render_in_minimap: block.render_in_minimap,
});
self.0.custom_blocks.insert(block_ix, new_block.clone());
self.0.custom_blocks_by_id.insert(id, new_block);
@@ -1071,6 +1080,7 @@ impl BlockMapWriter<'_> {
style: block.style,
render: block.render.clone(),
priority: block.priority,
+ render_in_minimap: block.render_in_minimap,
};
let new_block = Arc::new(new_block);
*block = new_block.clone();
@@ -1967,6 +1977,7 @@ mod tests {
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
BlockProperties {
style: BlockStyle::Fixed,
@@ -1974,6 +1985,7 @@ mod tests {
height: Some(2),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
BlockProperties {
style: BlockStyle::Fixed,
@@ -1981,6 +1993,7 @@ mod tests {
height: Some(3),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
]);
@@ -2205,6 +2218,7 @@ mod tests {
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
BlockProperties {
style: BlockStyle::Fixed,
@@ -2212,6 +2226,7 @@ mod tests {
height: Some(2),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
BlockProperties {
style: BlockStyle::Fixed,
@@ -2219,6 +2234,7 @@ mod tests {
height: Some(3),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
]);
@@ -2307,6 +2323,7 @@ mod tests {
render: Arc::new(|_| div().into_any()),
height: Some(1),
priority: 0,
+ render_in_minimap: true,
},
BlockProperties {
style: BlockStyle::Fixed,
@@ -2314,6 +2331,7 @@ mod tests {
render: Arc::new(|_| div().into_any()),
height: Some(1),
priority: 0,
+ render_in_minimap: true,
},
]);
@@ -2353,6 +2371,7 @@ mod tests {
height: Some(4),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
}])[0];
let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
@@ -2406,6 +2425,7 @@ mod tests {
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
BlockProperties {
style: BlockStyle::Fixed,
@@ -2413,6 +2433,7 @@ mod tests {
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
BlockProperties {
style: BlockStyle::Fixed,
@@ -2420,6 +2441,7 @@ mod tests {
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
]);
let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
@@ -2434,6 +2456,7 @@ mod tests {
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
BlockProperties {
style: BlockStyle::Fixed,
@@ -2441,6 +2464,7 @@ mod tests {
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
BlockProperties {
style: BlockStyle::Fixed,
@@ -2448,6 +2472,7 @@ mod tests {
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
]);
let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
@@ -2547,6 +2572,7 @@ mod tests {
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
BlockProperties {
style: BlockStyle::Fixed,
@@ -2554,6 +2580,7 @@ mod tests {
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
BlockProperties {
style: BlockStyle::Fixed,
@@ -2561,6 +2588,7 @@ mod tests {
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
]);
let excerpt_blocks_3 = writer.insert(vec![
@@ -2570,6 +2598,7 @@ mod tests {
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
BlockProperties {
style: BlockStyle::Fixed,
@@ -2577,6 +2606,7 @@ mod tests {
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
},
]);
@@ -2624,6 +2654,7 @@ mod tests {
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
}]);
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
let blocks = blocks_snapshot
@@ -2981,6 +3012,7 @@ mod tests {
height: Some(height),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
}
})
.collect::<Vec<_>>();
@@ -3001,6 +3033,7 @@ mod tests {
style: props.style,
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
}));
for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
@@ -3525,6 +3558,7 @@ mod tests {
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
}])[0];
let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
@@ -61,11 +61,11 @@ use collections::{BTreeMap, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing};
use display_map::*;
pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
-use editor_settings::GoToDefinitionFallback;
pub use editor_settings::{
CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
ShowScrollbar,
};
+use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
pub use editor_settings_controls::*;
use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
pub use element::{
@@ -231,6 +231,7 @@ pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration:
pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
+pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
pub type RenderDiffHunkControlsFn = Arc<
dyn Fn(
@@ -465,7 +466,7 @@ pub enum SelectMode {
All,
}
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+#[derive(Clone, PartialEq, Eq, Debug)]
pub enum EditorMode {
SingleLine {
auto_width: bool,
@@ -481,6 +482,9 @@ pub enum EditorMode {
/// When set to `true`, the editor's height will be determined by its content.
sized_by_content: bool,
},
+ Minimap {
+ parent: WeakEntity<Editor>,
+ },
}
impl EditorMode {
@@ -495,6 +499,10 @@ impl EditorMode {
pub fn is_full(&self) -> bool {
matches!(self, Self::Full { .. })
}
+
+ fn is_minimap(&self) -> bool {
+ matches!(self, Self::Minimap { .. })
+ }
}
#[derive(Copy, Clone, Debug)]
@@ -525,6 +533,7 @@ pub struct EditorStyle {
pub inlay_hints_style: HighlightStyle,
pub inline_completion_styles: InlineCompletionStyles,
pub unnecessary_code_fade: f32,
+ pub show_underlines: bool,
}
impl Default for EditorStyle {
@@ -545,6 +554,7 @@ impl Default for EditorStyle {
whitespace: HighlightStyle::default(),
},
unnecessary_code_fade: Default::default(),
+ show_underlines: true,
}
}
}
@@ -871,6 +881,7 @@ pub struct Editor {
show_breadcrumbs: bool,
show_gutter: bool,
show_scrollbars: bool,
+ show_minimap: bool,
disable_expand_excerpt_buttons: bool,
show_line_numbers: Option<bool>,
use_relative_line_numbers: Option<bool>,
@@ -989,6 +1000,7 @@ pub struct Editor {
serialize_selections: Task<()>,
serialize_folds: Task<()>,
mouse_cursor_hidden: bool,
+ minimap: Option<Entity<Self>>,
hide_mouse_mode: HideMouseMode,
pub change_list: ChangeList,
inline_value_cache: InlineValueCache,
@@ -1452,7 +1464,7 @@ impl Editor {
pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
let mut clone = Self::new(
- self.mode,
+ self.mode.clone(),
self.buffer.clone(),
self.project.clone(),
window,
@@ -1479,6 +1491,21 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
+ Editor::new_internal(mode, buffer, project, None, window, cx)
+ }
+
+ fn new_internal(
+ mode: EditorMode,
+ buffer: Entity<MultiBuffer>,
+ project: Option<Entity<Project>>,
+ display_map: Option<Entity<DisplayMap>>,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> Self {
+ debug_assert!(
+ display_map.is_none() || mode.is_minimap(),
+ "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
+ );
let style = window.text_style();
let font_size = style.font_size.to_pixels(window.rem_size());
let editor = cx.entity().downgrade();
@@ -1514,17 +1541,19 @@ impl Editor {
merge_adjacent: true,
..Default::default()
};
- let display_map = cx.new(|cx| {
- DisplayMap::new(
- buffer.clone(),
- style.font(),
- font_size,
- None,
- FILE_HEADER_HEIGHT,
- MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
- fold_placeholder,
- cx,
- )
+ let display_map = display_map.unwrap_or_else(|| {
+ cx.new(|cx| {
+ DisplayMap::new(
+ buffer.clone(),
+ style.font(),
+ font_size,
+ None,
+ FILE_HEADER_HEIGHT,
+ MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
+ fold_placeholder,
+ cx,
+ )
+ })
});
let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
@@ -1628,7 +1657,7 @@ impl Editor {
None
};
- let breakpoint_store = match (mode, project.as_ref()) {
+ let breakpoint_store = match (&mode, project.as_ref()) {
(EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
_ => None,
};
@@ -1649,6 +1678,8 @@ impl Editor {
code_action_providers.push(Rc::new(project) as Rc<_>);
}
+ let full_mode = mode.is_full();
+
let mut this = Self {
focus_handle,
show_cursor_when_unfocused: false,
@@ -1678,8 +1709,8 @@ impl Editor {
project,
blink_manager: blink_manager.clone(),
show_local_selections: true,
- show_scrollbars: true,
- mode,
+ show_scrollbars: full_mode,
+ show_minimap: full_mode,
show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
show_gutter: mode.is_full(),
show_line_numbers: None,
@@ -1727,7 +1758,7 @@ impl Editor {
workspace: None,
input_enabled: true,
use_modal_editing: mode.is_full(),
- read_only: false,
+ read_only: mode.is_minimap(),
use_autoclose: true,
use_auto_surround: true,
auto_replace_emoji_shortcode: false,
@@ -1771,9 +1802,10 @@ impl Editor {
show_git_blame_inline_delay_task: None,
git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
- serialize_dirty_buffers: ProjectSettings::get_global(cx)
- .session
- .restore_unsaved_buffers,
+ serialize_dirty_buffers: !mode.is_minimap()
+ && ProjectSettings::get_global(cx)
+ .session
+ .restore_unsaved_buffers,
blame: None,
blame_subscription: None,
tasks: Default::default(),
@@ -1816,10 +1848,12 @@ impl Editor {
load_diff_task: load_uncommitted_diff,
temporary_diff_override: false,
mouse_cursor_hidden: false,
+ minimap: None,
hide_mouse_mode: EditorSettings::get_global(cx)
.hide_mouse
.unwrap_or_default(),
change_list: ChangeList::new(),
+ mode,
};
if let Some(breakpoints) = this.breakpoint_store.as_ref() {
this._subscriptions
@@ -1906,12 +1940,11 @@ impl Editor {
this.scroll_manager.show_scrollbars(window, cx);
jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
- if mode.is_full() {
+ if full_mode {
let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
if this.git_blame_inline_enabled {
- this.git_blame_inline_enabled = true;
this.start_git_blame_inline(false, window, cx);
}
@@ -1926,6 +1959,8 @@ impl Editor {
.insert(buffer.read(cx).remote_id(), handle);
}
}
+
+ this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
}
this.report_editor_event("Editor Opened", None, cx);
@@ -1969,6 +2004,7 @@ impl Editor {
let mode = match self.mode {
EditorMode::SingleLine { .. } => "single_line",
EditorMode::AutoHeight { .. } => "auto_height",
+ EditorMode::Minimap { .. } => "minimap",
EditorMode::Full { .. } => "full",
};
@@ -2215,7 +2251,7 @@ impl Editor {
.flatten();
EditorSnapshot {
- mode: self.mode,
+ mode: self.mode.clone(),
show_gutter: self.show_gutter,
show_line_numbers: self.show_line_numbers,
show_git_diff_gutter: self.show_git_diff_gutter,
@@ -2251,8 +2287,8 @@ impl Editor {
.excerpt_containing(self.selections.newest_anchor().head(), cx)
}
- pub fn mode(&self) -> EditorMode {
- self.mode
+ pub fn mode(&self) -> &EditorMode {
+ &self.mode
}
pub fn set_mode(&mut self, mode: EditorMode) {
@@ -2707,7 +2743,9 @@ impl Editor {
use text::ToOffset as _;
use text::ToPoint as _;
- if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
+ if self.mode.is_minimap()
+ || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
+ {
return;
}
@@ -7238,6 +7276,7 @@ impl Editor {
&mut self,
text_bounds: &Bounds<Pixels>,
content_origin: gpui::Point<Pixels>,
+ right_margin: Pixels,
editor_snapshot: &EditorSnapshot,
visible_row_range: Range<DisplayRow>,
scroll_top: f32,
@@ -7251,6 +7290,9 @@ impl Editor {
window: &mut Window,
cx: &mut App,
) -> Option<(AnyElement, gpui::Point<Pixels>)> {
+ if self.mode().is_minimap() {
+ return None;
+ }
let active_inline_completion = self.active_inline_completion.as_ref()?;
if self.edit_prediction_visible_in_cursor_popover(true) {
@@ -7328,6 +7370,7 @@ impl Editor {
} => self.render_edit_prediction_diff_popover(
text_bounds,
content_origin,
+ right_margin,
editor_snapshot,
visible_row_range,
line_layouts,
@@ -7601,6 +7644,7 @@ impl Editor {
self: &Editor,
text_bounds: &Bounds<Pixels>,
content_origin: gpui::Point<Pixels>,
+ right_margin: Pixels,
editor_snapshot: &EditorSnapshot,
visible_row_range: Range<DisplayRow>,
line_layouts: &[LineWithInvisibles],
@@ -7710,7 +7754,7 @@ impl Editor {
let viewport_bounds =
Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
- right: -EditorElement::SCROLLBAR_WIDTH,
+ right: -right_margin,
..Default::default()
});
@@ -9286,10 +9330,11 @@ impl Editor {
placement: BlockPlacement::Above(anchor),
height: Some(height),
render: Arc::new(move |cx| {
- *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
+ *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
cloned_prompt.clone().into_any_element()
}),
priority: 0,
+ render_in_minimap: true,
}];
let focus_handle = bp_prompt.focus_handle(cx);
@@ -14546,6 +14591,7 @@ impl Editor {
}
}),
priority: 0,
+ render_in_minimap: true,
}],
Some(Autoscroll::fit()),
cx,
@@ -14928,6 +14974,10 @@ impl Editor {
}
fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
+ if self.mode.is_minimap() {
+ return;
+ }
+
if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
let buffer = self.buffer.read(cx).snapshot(cx);
let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
@@ -15041,7 +15091,10 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Self>,
) {
- if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
+ if self.mode.is_minimap()
+ || !self.inline_diagnostics_enabled
+ || !self.show_inline_diagnostics
+ {
self.inline_diagnostics_update = Task::ready(());
self.inline_diagnostics.clear();
return;
@@ -16246,6 +16299,55 @@ impl Editor {
.text()
}
+ fn create_minimap(
+ &self,
+ minimap_settings: MinimapSettings,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> Option<Entity<Self>> {
+ (minimap_settings.minimap_enabled() && self.is_singleton(cx))
+ .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
+ }
+
+ fn initialize_new_minimap(
+ &self,
+ minimap_settings: MinimapSettings,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> Entity<Self> {
+ const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
+
+ let mut minimap = Editor::new_internal(
+ EditorMode::Minimap {
+ parent: cx.weak_entity(),
+ },
+ self.buffer.clone(),
+ self.project.clone(),
+ Some(self.display_map.clone()),
+ window,
+ cx,
+ );
+ minimap.scroll_manager.clone_state(&self.scroll_manager);
+ minimap.set_text_style_refinement(TextStyleRefinement {
+ font_size: Some(MINIMAP_FONT_SIZE),
+ font_weight: Some(MINIMAP_FONT_WEIGHT),
+ ..Default::default()
+ });
+ minimap.update_minimap_configuration(minimap_settings, cx);
+ cx.new(|_| minimap)
+ }
+
+ fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
+ let current_line_highlight = minimap_settings
+ .current_line_highlight
+ .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
+ self.set_current_line_highlight(Some(current_line_highlight));
+ }
+
+ pub fn minimap(&self) -> Option<&Entity<Self>> {
+ self.minimap.as_ref().filter(|_| self.show_minimap)
+ }
+
pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
let mut wrap_guides = smallvec::smallvec![];
@@ -16313,14 +16415,19 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Self>,
) {
- let rem_size = window.rem_size();
- self.display_map.update(cx, |map, cx| {
- map.set_font(
- style.text.font(),
- style.text.font_size.to_pixels(rem_size),
- cx,
- )
- });
+ // We intentionally do not inform the display map about the minimap style
+ // so that wrapping is not recalculated and stays consistent for the editor
+ // and its linked minimap.
+ if !self.mode.is_minimap() {
+ let rem_size = window.rem_size();
+ self.display_map.update(cx, |map, cx| {
+ map.set_font(
+ style.text.font(),
+ style.text.font_size.to_pixels(rem_size),
+ cx,
+ )
+ });
+ }
self.style = Some(style);
}
@@ -16435,6 +16542,16 @@ impl Editor {
cx.notify();
}
+ pub fn set_show_minimap(&mut self, show_minimap: bool, cx: &mut Context<Self>) {
+ self.show_minimap = show_minimap;
+ cx.notify();
+ }
+
+ pub fn disable_scrollbars_and_minimap(&mut self, cx: &mut Context<Self>) {
+ self.set_show_scrollbars(false, cx);
+ self.set_show_minimap(false, cx);
+ }
+
pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
self.show_line_numbers = Some(show_line_numbers);
cx.notify();
@@ -16808,7 +16925,7 @@ impl Editor {
}
pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
- self.show_git_blame_gutter && self.has_blame_entries(cx)
+ !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
}
pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
@@ -17910,7 +18027,8 @@ impl Editor {
}
let project_settings = ProjectSettings::get_global(cx);
- self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
+ self.serialize_dirty_buffers =
+ !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
if self.mode.is_full() {
let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
@@ -17923,6 +18041,15 @@ impl Editor {
if self.git_blame_inline_enabled != inline_blame_enabled {
self.toggle_git_blame_inline_internal(false, window, cx);
}
+
+ let minimap_settings = EditorSettings::get_global(cx).minimap;
+ if self.minimap.as_ref().is_some() != minimap_settings.minimap_enabled() {
+ self.minimap = self.create_minimap(minimap_settings, window, cx);
+ } else if let Some(minimap_entity) = self.minimap.as_ref() {
+ minimap_entity.update(cx, |minimap_editor, cx| {
+ minimap_editor.update_minimap_configuration(minimap_settings, cx)
+ })
+ }
}
cx.notify();
@@ -18618,6 +18745,9 @@ impl Editor {
}
pub fn register_addon<T: Addon>(&mut self, instance: T) {
+ if self.mode.is_minimap() {
+ return;
+ }
self.addons
.insert(std::any::TypeId::of::<T>(), Box::new(instance));
}
@@ -18663,6 +18793,7 @@ impl Editor {
cx: &mut Context<Editor>,
) {
if self.is_singleton(cx)
+ && !self.mode.is_minimap()
&& WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
{
let buffer_snapshot = OnceCell::new();
@@ -20268,7 +20399,7 @@ impl Render for Editor {
line_height: relative(settings.buffer_line_height.value()),
..Default::default()
},
- EditorMode::Full { .. } => TextStyle {
+ EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
color: cx.theme().colors().editor_foreground,
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
@@ -20287,8 +20418,11 @@ impl Render for Editor {
EditorMode::SingleLine { .. } => cx.theme().system().transparent,
EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
EditorMode::Full { .. } => cx.theme().colors().editor_background,
+ EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
};
+ let show_underlines = !self.mode.is_minimap();
+
EditorElement::new(
&cx.entity(),
EditorStyle {
@@ -20301,6 +20435,7 @@ impl Render for Editor {
inlay_hints_style: make_inlay_hints_style(cx),
inline_completion_styles: make_suggestion_styles(cx),
unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
+ show_underlines,
},
)
}
@@ -20912,7 +21047,7 @@ struct BreakpointPromptEditor {
breakpoint: Breakpoint,
edit_action: BreakpointPromptEditAction,
block_ids: HashSet<CustomBlockId>,
- gutter_dimensions: Arc<Mutex<GutterDimensions>>,
+ editor_margins: Arc<Mutex<EditorMargins>>,
_subscriptions: Vec<Subscription>,
}
@@ -20968,7 +21103,7 @@ impl BreakpointPromptEditor {
breakpoint_anchor,
breakpoint,
edit_action,
- gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
+ editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
block_ids: Default::default(),
_subscriptions: vec![],
}
@@ -21053,7 +21188,8 @@ impl BreakpointPromptEditor {
impl Render for BreakpointPromptEditor {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- let gutter_dimensions = *self.gutter_dimensions.lock();
+ let editor_margins = *self.editor_margins.lock();
+ let gutter_dimensions = editor_margins.gutter;
h_flex()
.key_context("Editor")
.bg(cx.theme().colors().editor_background)
@@ -15,6 +15,7 @@ pub struct EditorSettings {
pub hover_popover_delay: u64,
pub toolbar: Toolbar,
pub scrollbar: Scrollbar,
+ pub minimap: Minimap,
pub gutter: Gutter,
pub scroll_beyond_last_line: ScrollBeyondLastLine,
pub vertical_scroll_margin: f32,
@@ -116,6 +117,20 @@ pub struct Scrollbar {
pub axes: ScrollbarAxes,
}
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
+pub struct Minimap {
+ pub show: ShowMinimap,
+ pub thumb: MinimapThumb,
+ pub thumb_border: MinimapThumbBorder,
+ pub current_line_highlight: Option<CurrentLineHighlight>,
+}
+
+impl Minimap {
+ pub fn minimap_enabled(&self) -> bool {
+ self.show != ShowMinimap::Never
+ }
+}
+
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct Gutter {
pub line_numbers: bool,
@@ -141,6 +156,53 @@ pub enum ShowScrollbar {
Never,
}
+/// When to show the minimap in the editor.
+///
+/// Default: never
+#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[serde(rename_all = "snake_case")]
+pub enum ShowMinimap {
+ /// Follow the visibility of the scrollbar.
+ Auto,
+ /// Always show the minimap.
+ Always,
+ /// Never show the minimap.
+ #[default]
+ Never,
+}
+
+/// When to show the minimap thumb.
+///
+/// Default: always
+#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[serde(rename_all = "snake_case")]
+pub enum MinimapThumb {
+ /// Show the minimap thumb only when the mouse is hovering over the minimap.
+ Hover,
+ /// Always show the minimap thumb.
+ #[default]
+ Always,
+}
+
+/// Defines the border style for the minimap's scrollbar thumb.
+///
+/// Default: left_open
+#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[serde(rename_all = "snake_case")]
+pub enum MinimapThumbBorder {
+ /// Displays a border on all sides of the thumb.
+ Full,
+ /// Displays a border on all sides except the left side of the thumb.
+ #[default]
+ LeftOpen,
+ /// Displays a border on all sides except the right side of the thumb.
+ RightOpen,
+ /// Displays a border only on the left side of the thumb.
+ LeftOnly,
+ /// Displays the thumb without any border.
+ None,
+}
+
/// Forcefully enable or disable the scrollbar for each axis
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
@@ -300,6 +362,8 @@ pub struct EditorSettingsContent {
pub toolbar: Option<ToolbarContent>,
/// Scrollbar related settings
pub scrollbar: Option<ScrollbarContent>,
+ /// Minimap related settings
+ pub minimap: Option<MinimapContent>,
/// Gutter related settings
pub gutter: Option<GutterContent>,
/// Whether the editor will scroll beyond the last line.
@@ -446,6 +510,30 @@ pub struct ScrollbarContent {
pub axes: Option<ScrollbarAxesContent>,
}
+/// Minimap related settings
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
+pub struct MinimapContent {
+ /// When to show the minimap in the editor.
+ ///
+ /// Default: never
+ pub show: Option<ShowMinimap>,
+
+ /// When to show the minimap thumb.
+ ///
+ /// Default: always
+ pub thumb: Option<MinimapThumb>,
+
+ /// Defines the border style for the minimap's scrollbar thumb.
+ ///
+ /// Default: left_open
+ pub thumb_border: Option<MinimapThumbBorder>,
+
+ /// How to highlight the current line in the minimap.
+ ///
+ /// Default: inherits editor line highlights setting
+ pub current_line_highlight: Option<Option<CurrentLineHighlight>>,
+}
+
/// Forcefully enable or disable the scrollbar for each axis
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
pub struct ScrollbarAxesContent {
@@ -4327,6 +4327,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
+ render_in_minimap: true,
}],
Some(Autoscroll::fit()),
cx,
@@ -4369,6 +4370,7 @@ async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
style: BlockStyle::Sticky,
render: Arc::new(|_| gpui::div().into_any_element()),
priority: 0,
+ render_in_minimap: true,
}],
None,
cx,
@@ -6,16 +6,19 @@ use crate::{
EditDisplayMode, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
FILE_HEADER_HEIGHT, FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput,
HoveredCursor, InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineHighlight,
- LineUp, MAX_LINE_LEN, MIN_LINE_NUMBER_DIGITS, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, OpenExcerpts,
- PageDown, PageUp, PhantomBreakpointIndicator, Point, RowExt, RowRangeExt, SelectPhase,
- SelectedTextHighlight, Selection, SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold,
+ LineUp, MAX_LINE_LEN, MIN_LINE_NUMBER_DIGITS, MINIMAP_FONT_SIZE,
+ MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, OpenExcerpts, PageDown, PageUp, PhantomBreakpointIndicator,
+ Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap,
+ StickyHeaderExcerpt, ToPoint, ToggleFold,
code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
display_map::{
- Block, BlockContext, BlockStyle, DisplaySnapshot, FoldId, HighlightedChunk, ToDisplayPoint,
+ Block, BlockContext, BlockStyle, DisplaySnapshot, EditorMargins, FoldId, HighlightedChunk,
+ ToDisplayPoint,
},
editor_settings::{
- CurrentLineHighlight, DoubleClickInMultibuffer, MultiCursorModifier, ScrollBeyondLastLine,
- ScrollbarAxes, ScrollbarDiagnostics, ShowScrollbar,
+ CurrentLineHighlight, DoubleClickInMultibuffer, MinimapThumb, MinimapThumbBorder,
+ MultiCursorModifier, ScrollBeyondLastLine, ScrollbarAxes, ScrollbarDiagnostics,
+ ShowMinimap, ShowScrollbar,
},
git::blame::{BlameRenderer, GitBlame, GlobalBlameRenderer},
hover_popover::{
@@ -40,7 +43,7 @@ use gpui::{
Action, Along, AnyElement, App, AppContext, AvailableSpace, Axis as ScrollbarAxis, BorderStyle,
Bounds, ClickEvent, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase, Edges,
Element, ElementInputHandler, Entity, Focusable as _, FontId, GlobalElementId, Hitbox, Hsla,
- InteractiveElement, IntoElement, Keystroke, Length, ModifiersChangedEvent, MouseButton,
+ InteractiveElement, IntoElement, IsZero, Keystroke, Length, ModifiersChangedEvent, MouseButton,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta,
ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement,
Style, Styled, TextRun, TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill,
@@ -57,6 +60,7 @@ use multi_buffer::{
Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, ExpandInfo, MultiBufferPoint,
MultiBufferRow, RowInfo,
};
+
use project::{
ProjectPath,
debugger::breakpoint_store::Breakpoint,
@@ -93,6 +97,7 @@ struct LineHighlightSpec {
_active_stack_frame: bool,
}
+#[derive(Debug)]
struct SelectionLayout {
head: DisplayPoint,
cursor_shape: CursorShape,
@@ -1111,7 +1116,12 @@ impl EditorElement {
let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
let mut active_rows = BTreeMap::new();
let mut newest_selection_head = None;
- self.editor.update(cx, |editor, cx| {
+
+ let Some(editor_with_selections) = self.editor_with_selections(cx) else {
+ return (selections, active_rows, newest_selection_head);
+ };
+
+ editor_with_selections.update(cx, |editor, cx| {
if editor.show_local_selections {
let mut layouts = Vec::new();
let newest = editor.selections.newest(cx);
@@ -1442,10 +1452,12 @@ impl EditorElement {
fn layout_scrollbars(
&self,
snapshot: &EditorSnapshot,
- scrollbar_layout_information: ScrollbarLayoutInformation,
+ scrollbar_layout_information: &ScrollbarLayoutInformation,
content_offset: gpui::Point<Pixels>,
scroll_position: gpui::Point<f32>,
non_visible_cursors: bool,
+ right_margin: Pixels,
+ editor_width: Pixels,
window: &mut Window,
cx: &mut App,
) -> Option<EditorScrollbars> {
@@ -1494,16 +1506,153 @@ impl EditorElement {
Some(EditorScrollbars::from_scrollbar_axes(
scrollbar_settings.axes,
- &scrollbar_layout_information,
+ scrollbar_layout_information,
content_offset,
scroll_position,
self.style.scrollbar_width,
+ right_margin,
+ editor_width,
show_scrollbars,
self.editor.read(cx).scroll_manager.active_scrollbar_state(),
window,
))
}
+ fn layout_minimap(
+ &self,
+ snapshot: &EditorSnapshot,
+ minimap_width: Pixels,
+ scroll_position: gpui::Point<f32>,
+ scrollbar_layout_information: &ScrollbarLayoutInformation,
+ scrollbar_layout: Option<&EditorScrollbars>,
+ window: &mut Window,
+ cx: &mut App,
+ ) -> Option<MinimapLayout> {
+ let minimap_editor = self
+ .editor
+ .read_with(cx, |editor, _| editor.minimap().cloned())?;
+
+ let minimap_settings = EditorSettings::get_global(cx).minimap;
+
+ if !snapshot.mode.is_full()
+ || minimap_width.is_zero()
+ || matches!(
+ minimap_settings.show,
+ ShowMinimap::Never | ShowMinimap::Auto if scrollbar_layout.is_none_or(|layout| !layout.visible)
+ )
+ {
+ return None;
+ }
+
+ const MINIMAP_AXIS: ScrollbarAxis = ScrollbarAxis::Vertical;
+
+ let ScrollbarLayoutInformation {
+ editor_bounds,
+ scroll_range,
+ glyph_grid_cell,
+ } = scrollbar_layout_information;
+
+ let line_height = glyph_grid_cell.height;
+ let scroll_position = scroll_position.along(MINIMAP_AXIS);
+
+ let top_right_anchor = scrollbar_layout
+ .and_then(|layout| layout.vertical.as_ref())
+ .map(|vertical_scrollbar| vertical_scrollbar.hitbox.origin)
+ .unwrap_or_else(|| editor_bounds.top_right());
+
+ let show_thumb = match minimap_settings.thumb {
+ MinimapThumb::Always => true,
+ MinimapThumb::Hover => self.editor.update(cx, |editor, _| {
+ editor.scroll_manager.minimap_thumb_visible()
+ }),
+ };
+
+ let minimap_bounds = Bounds::from_corner_and_size(
+ Corner::TopRight,
+ top_right_anchor,
+ size(minimap_width, editor_bounds.size.height),
+ );
+ let minimap_line_height = self.get_minimap_line_height(
+ minimap_editor
+ .read_with(cx, |editor, _| {
+ editor
+ .text_style_refinement
+ .as_ref()
+ .and_then(|refinement| refinement.font_size)
+ })
+ .unwrap_or(MINIMAP_FONT_SIZE),
+ window,
+ cx,
+ );
+ let minimap_height = minimap_bounds.size.height;
+
+ let visible_editor_lines = editor_bounds.size.height / line_height;
+ let total_editor_lines = scroll_range.height / line_height;
+ let minimap_lines = minimap_height / minimap_line_height;
+
+ let minimap_scroll_top = MinimapLayout::calculate_minimap_top_offset(
+ total_editor_lines,
+ visible_editor_lines,
+ minimap_lines,
+ scroll_position,
+ );
+
+ let layout = ScrollbarLayout::for_minimap(
+ window.insert_hitbox(minimap_bounds, false),
+ visible_editor_lines,
+ total_editor_lines,
+ minimap_line_height,
+ scroll_position,
+ minimap_scroll_top,
+ );
+
+ minimap_editor.update(cx, |editor, cx| {
+ editor.set_scroll_position(point(0., minimap_scroll_top), window, cx)
+ });
+
+ // Required for the drop shadow to be visible
+ const PADDING_OFFSET: Pixels = px(4.);
+
+ let mut minimap = div()
+ .size_full()
+ .shadow_sm()
+ .px(PADDING_OFFSET)
+ .child(minimap_editor)
+ .into_any_element();
+
+ let extended_bounds = minimap_bounds.extend(Edges {
+ right: PADDING_OFFSET,
+ left: PADDING_OFFSET,
+ ..Default::default()
+ });
+ minimap.layout_as_root(extended_bounds.size.into(), window, cx);
+ window.with_absolute_element_offset(extended_bounds.origin, |window| {
+ minimap.prepaint(window, cx)
+ });
+
+ Some(MinimapLayout {
+ minimap,
+ thumb_layout: layout,
+ show_thumb,
+ thumb_border_style: minimap_settings.thumb_border,
+ minimap_line_height,
+ minimap_scroll_top,
+ max_scroll_top: total_editor_lines,
+ })
+ }
+
+ fn get_minimap_line_height(
+ &self,
+ font_size: AbsoluteLength,
+ window: &mut Window,
+ cx: &mut App,
+ ) -> Pixels {
+ let rem_size = self.rem_size(cx).unwrap_or(window.rem_size());
+ let mut text_style = self.style.text.clone();
+ text_style.font_size = font_size;
+ text_style.line_height_in_pixels(rem_size)
+ }
+
fn prepaint_crease_toggles(
&self,
crease_toggles: &mut [Option<AnyElement>],
@@ -1643,6 +1792,9 @@ impl EditorElement {
window: &mut Window,
cx: &mut App,
) -> HashMap<DisplayRow, AnyElement> {
+ if self.editor.read(cx).mode().is_minimap() {
+ return HashMap::default();
+ }
let max_severity = ProjectSettings::get_global(cx)
.diagnostics
.inline
@@ -2048,6 +2200,9 @@ impl EditorElement {
window: &mut Window,
cx: &mut App,
) -> Option<Vec<IndentGuideLayout>> {
+ if self.editor.read(cx).mode().is_minimap() {
+ return None;
+ }
let indent_guides = self.editor.update(cx, |editor, cx| {
editor.indent_guides(visible_buffer_range, snapshot, cx)
})?;
@@ -2680,7 +2835,7 @@ impl EditorElement {
&style,
MAX_LINE_LEN,
rows.len(),
- snapshot.mode,
+ &snapshot.mode,
editor_width,
is_row_soft_wrapped,
window,
@@ -2726,6 +2881,7 @@ impl EditorElement {
rows: &Range<DisplayRow>,
line_layouts: &[LineWithInvisibles],
gutter_dimensions: &GutterDimensions,
+ right_margin: Pixels,
line_height: Pixels,
em_width: Pixels,
text_hitbox: &Hitbox,
@@ -2785,20 +2941,29 @@ impl EditorElement {
})
.is_ok();
+ let margins = EditorMargins {
+ gutter: *gutter_dimensions,
+ right: right_margin,
+ };
+
div()
.size_full()
- .child(custom.render(&mut BlockContext {
- window,
- app: cx,
- anchor_x,
- gutter_dimensions,
- line_height,
- em_width,
- block_id,
- selected,
- max_width: text_hitbox.size.width.max(*scroll_width),
- editor_style: &self.style,
- }))
+ .children(
+ (!snapshot.mode.is_minimap() || custom.render_in_minimap).then(|| {
+ custom.render(&mut BlockContext {
+ window,
+ app: cx,
+ anchor_x,
+ margins: &margins,
+ line_height,
+ em_width,
+ block_id,
+ selected,
+ max_width: text_hitbox.size.width.max(*scroll_width),
+ editor_style: &self.style,
+ })
+ }),
+ )
.into_any()
}
@@ -3117,6 +3282,7 @@ impl EditorElement {
editor_width: Pixels,
scroll_width: &mut Pixels,
gutter_dimensions: &GutterDimensions,
+ right_margin: Pixels,
em_width: Pixels,
text_x: Pixels,
line_height: Pixels,
@@ -3157,6 +3323,7 @@ impl EditorElement {
&rows,
line_layouts,
gutter_dimensions,
+ right_margin,
line_height,
em_width,
text_hitbox,
@@ -3214,6 +3381,7 @@ impl EditorElement {
&rows,
line_layouts,
gutter_dimensions,
+ right_margin,
line_height,
em_width,
text_hitbox,
@@ -3268,6 +3436,7 @@ impl EditorElement {
&rows,
line_layouts,
gutter_dimensions,
+ right_margin,
line_height,
em_width,
text_hitbox,
@@ -3425,6 +3594,7 @@ impl EditorElement {
line_height: Pixels,
text_hitbox: &Hitbox,
content_origin: gpui::Point<Pixels>,
+ right_margin: Pixels,
start_row: DisplayRow,
scroll_pixel_position: gpui::Point<Pixels>,
line_layouts: &[LineWithInvisibles],
@@ -3493,7 +3663,7 @@ impl EditorElement {
let viewport_bounds =
Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
- right: -Self::SCROLLBAR_WIDTH - MENU_GAP,
+ right: -right_margin - MENU_GAP,
..Default::default()
});
@@ -3626,6 +3796,7 @@ impl EditorElement {
line_height: Pixels,
text_hitbox: &Hitbox,
content_origin: gpui::Point<Pixels>,
+ right_margin: Pixels,
scroll_pixel_position: gpui::Point<Pixels>,
gutter_overshoot: Pixels,
window: &mut Window,
@@ -3659,7 +3830,7 @@ impl EditorElement {
let max_height = line_height * max_height_in_lines as f32 + POPOVER_Y_PADDING;
let viewport_bounds =
Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
- right: -Self::SCROLLBAR_WIDTH - MENU_GAP,
+ right: -right_margin - MENU_GAP,
..Default::default()
});
self.layout_popovers_above_or_below_line(
@@ -4134,6 +4305,7 @@ impl EditorElement {
position_map: &PositionMap,
newest_cursor_position: Option<DisplayPoint>,
line_height: Pixels,
+ right_margin: Pixels,
scroll_pixel_position: gpui::Point<Pixels>,
display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
highlighted_rows: &BTreeMap<DisplayRow, LineHighlight>,
@@ -4213,10 +4385,7 @@ impl EditorElement {
let size =
element.layout_as_root(size(px(100.0), line_height).into(), window, cx);
- let x = text_hitbox.bounds.right()
- - self.style.scrollbar_width
- - px(10.)
- - size.width;
+ let x = text_hitbox.bounds.right() - right_margin - px(10.) - size.width;
window.with_absolute_element_offset(gpui::Point::new(x, y), |window| {
element.prepaint(window, cx)
@@ -4315,11 +4484,18 @@ impl EditorElement {
self.style.background,
));
- if let EditorMode::Full {
- show_active_line_background,
- ..
- } = layout.mode
- {
+ if matches!(
+ layout.mode,
+ EditorMode::Full { .. } | EditorMode::Minimap { .. }
+ ) {
+ let show_active_line_background = match layout.mode {
+ EditorMode::Full {
+ show_active_line_background,
+ ..
+ } => show_active_line_background,
+ EditorMode::Minimap { .. } => true,
+ _ => false,
+ };
let mut active_rows = layout.active_rows.iter().peekable();
while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
let mut end_row = start_row.0;
@@ -5549,6 +5725,159 @@ impl EditorElement {
}
}
+ fn paint_minimap(&self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
+ if let Some(mut layout) = layout.minimap.take() {
+ let minimap_hitbox = layout.thumb_layout.hitbox.clone();
+ let thumb_bounds = layout.thumb_layout.thumb_bounds();
+
+ window.paint_layer(layout.thumb_layout.hitbox.bounds, |window| {
+ window.with_element_namespace("minimap", |window| {
+ layout.minimap.paint(window, cx);
+ if layout.show_thumb {
+ let minimap_thumb_border = match layout.thumb_border_style {
+ MinimapThumbBorder::Full => Edges::all(ScrollbarLayout::BORDER_WIDTH),
+ MinimapThumbBorder::LeftOnly => Edges {
+ left: ScrollbarLayout::BORDER_WIDTH,
+ ..Default::default()
+ },
+ MinimapThumbBorder::LeftOpen => Edges {
+ right: ScrollbarLayout::BORDER_WIDTH,
+ top: ScrollbarLayout::BORDER_WIDTH,
+ bottom: ScrollbarLayout::BORDER_WIDTH,
+ ..Default::default()
+ },
+ MinimapThumbBorder::RightOpen => Edges {
+ left: ScrollbarLayout::BORDER_WIDTH,
+ top: ScrollbarLayout::BORDER_WIDTH,
+ bottom: ScrollbarLayout::BORDER_WIDTH,
+ ..Default::default()
+ },
+ MinimapThumbBorder::None => Default::default(),
+ };
+
+ window.paint_layer(minimap_hitbox.bounds, |window| {
+ window.paint_quad(quad(
+ thumb_bounds,
+ Corners::default(),
+ cx.theme().colors().scrollbar_thumb_background,
+ minimap_thumb_border,
+ cx.theme().colors().scrollbar_thumb_border,
+ BorderStyle::Solid,
+ ));
+ });
+ }
+ });
+ });
+
+ window.set_cursor_style(CursorStyle::Arrow, Some(&minimap_hitbox));
+
+ let minimap_axis = ScrollbarAxis::Vertical;
+ let pixels_per_line = (minimap_hitbox.size.height / layout.max_scroll_top)
+ .min(layout.minimap_line_height);
+
+ let mut mouse_position = window.mouse_position();
+
+ window.on_mouse_event({
+ let editor = self.editor.clone();
+
+ let minimap_hitbox = minimap_hitbox.clone();
+
+ move |event: &MouseMoveEvent, phase, window, cx| {
+ if phase == DispatchPhase::Capture {
+ return;
+ }
+
+ editor.update(cx, |editor, cx| {
+ if event.pressed_button == Some(MouseButton::Left)
+ && editor.scroll_manager.is_dragging_minimap()
+ {
+ let old_position = mouse_position.along(minimap_axis);
+ let new_position = event.position.along(minimap_axis);
+ if (minimap_hitbox.origin.along(minimap_axis)
+ ..minimap_hitbox.bottom_right().along(minimap_axis))
+ .contains(&old_position)
+ {
+ let position =
+ editor.scroll_position(cx).apply_along(minimap_axis, |p| {
+ (p + (new_position - old_position) / pixels_per_line)
+ .max(0.)
+ });
+ editor.set_scroll_position(position, window, cx);
+ }
+ cx.stop_propagation();
+ } else {
+ editor.scroll_manager.set_is_dragging_minimap(false, cx);
+
+ if minimap_hitbox.is_hovered(window) {
+ editor.scroll_manager.show_minimap_thumb(cx);
+
+ // Stop hover events from propagating to the
+ // underlying editor if the minimap hitbox is hovered
+ if !event.dragging() {
+ cx.stop_propagation();
+ }
+ } else {
+ editor.scroll_manager.hide_minimap_thumb(cx);
+ }
+ }
+ mouse_position = event.position;
+ });
+ }
+ });
+
+ if self.editor.read(cx).scroll_manager.is_dragging_minimap() {
+ window.on_mouse_event({
+ let editor = self.editor.clone();
+ move |_: &MouseUpEvent, phase, _, cx| {
+ if phase == DispatchPhase::Capture {
+ return;
+ }
+
+ editor.update(cx, |editor, cx| {
+ editor.scroll_manager.set_is_dragging_minimap(false, cx);
+ cx.stop_propagation();
+ });
+ }
+ });
+ } else {
+ window.on_mouse_event({
+ let editor = self.editor.clone();
+
+ move |event: &MouseDownEvent, phase, window, cx| {
+ if phase == DispatchPhase::Capture || !minimap_hitbox.is_hovered(window) {
+ return;
+ }
+
+ let event_position = event.position;
+
+ editor.update(cx, |editor, cx| {
+ if !thumb_bounds.contains(&event_position) {
+ let click_position =
+ event_position.relative_to(&minimap_hitbox.origin).y;
+
+ let top_position = (click_position
+ - thumb_bounds.size.along(minimap_axis) / 2.0)
+ .max(Pixels::ZERO);
+
+ let scroll_offset = (layout.minimap_scroll_top
+ + top_position / layout.minimap_line_height)
+ .min(layout.max_scroll_top);
+
+ let scroll_position = editor
+ .scroll_position(cx)
+ .apply_along(minimap_axis, |_| scroll_offset);
+ editor.set_scroll_position(scroll_position, window, cx);
+ }
+
+ editor.scroll_manager.set_is_dragging_minimap(true, cx);
+ cx.stop_propagation();
+ });
+ }
+ });
+ }
+ }
+ }
+
fn paint_blocks(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
for mut block in layout.blocks.drain(..) {
if block.overlaps_gutter {
@@ -5654,6 +5983,10 @@ impl EditorElement {
}
fn paint_mouse_listeners(&mut self, layout: &EditorLayout, window: &mut Window, cx: &mut App) {
+ if self.editor.read(cx).mode.is_minimap() {
+ return;
+ }
+
self.paint_scroll_wheel_listener(layout, window, cx);
window.on_mouse_event({
@@ -6077,7 +6410,7 @@ impl LineWithInvisibles {
editor_style: &EditorStyle,
max_line_len: usize,
max_line_count: usize,
- editor_mode: EditorMode,
+ editor_mode: &EditorMode,
text_width: Pixels,
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
window: &mut Window,
@@ -6594,12 +6927,10 @@ impl EditorElement {
fn rem_size(&self, cx: &mut App) -> Option<Pixels> {
match self.editor.read(cx).mode {
EditorMode::Full {
- scale_ui_elements_with_buffer_font_size,
+ scale_ui_elements_with_buffer_font_size: true,
..
- } => {
- if !scale_ui_elements_with_buffer_font_size {
- return None;
- }
+ }
+ | EditorMode::Minimap { .. } => {
let buffer_font_size = self.style.text.font_size;
match buffer_font_size {
AbsoluteLength::Pixels(pixels) => {
@@ -6627,7 +6958,15 @@ impl EditorElement {
// We currently use single-line and auto-height editors in UI contexts,
// so we don't want to scale everything with the buffer font size, as it
// ends up looking off.
- EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => None,
+ _ => None,
+ }
+ }
+
+ fn editor_with_selections(&self, cx: &App) -> Option<Entity<Editor>> {
+ if let EditorMode::Minimap { parent } = self.editor.read(cx).mode() {
+ parent.upgrade()
+ } else {
+ Some(self.editor.clone())
}
}
}
@@ -6716,6 +7055,12 @@ impl Element for EditorElement {
},
)
}
+ EditorMode::Minimap { .. } => {
+ let mut style = Style::default();
+ style.size.width = relative(1.).into();
+ style.size.height = relative(1.).into();
+ window.request_layout(style, None, cx)
+ }
EditorMode::Full {
sized_by_content, ..
} => {
@@ -6786,28 +7131,65 @@ impl Element for EditorElement {
});
let text_width = bounds.size.width - gutter_dimensions.width;
+ let settings = EditorSettings::get_global(cx);
+ let scrollbars_shown = settings.scrollbar.show != ShowScrollbar::Never;
+ let vertical_scrollbar_width = (scrollbars_shown
+ && settings.scrollbar.axes.vertical
+ && self
+ .editor
+ .read_with(cx, |editor, _| editor.show_scrollbars))
+ .then_some(style.scrollbar_width)
+ .unwrap_or_default();
+ let minimap_width = self
+ .editor
+ .read_with(cx, |editor, _| editor.minimap().is_some())
+ .then(|| match settings.minimap.show {
+ ShowMinimap::Never => None,
+ ShowMinimap::Always => Some(MinimapLayout::MINIMAP_WIDTH),
+ ShowMinimap::Auto => {
+ scrollbars_shown.then_some(MinimapLayout::MINIMAP_WIDTH)
+ }
+ })
+ .flatten()
+ .filter(|minimap_width| {
+ text_width - vertical_scrollbar_width - *minimap_width > *minimap_width
+ })
+ .unwrap_or_default();
+
+ let right_margin = minimap_width + vertical_scrollbar_width;
+
let editor_width =
- text_width - gutter_dimensions.margin - em_width - style.scrollbar_width;
+ text_width - gutter_dimensions.margin - 2 * em_width - right_margin;
+
+ // Offset the content_bounds from the text_bounds by the gutter margin (which
+ // is roughly half a character wide) to make hit testing work more like how we want.
+ let content_offset = point(gutter_dimensions.margin, Pixels::ZERO);
+
+ let editor_content_width = editor_width - content_offset.x;
snapshot = self.editor.update(cx, |editor, cx| {
editor.last_bounds = Some(bounds);
editor.gutter_dimensions = gutter_dimensions;
editor.set_visible_line_count(bounds.size.height / line_height, window, cx);
- if matches!(editor.mode, EditorMode::AutoHeight { .. }) {
+ if matches!(
+ editor.mode,
+ EditorMode::AutoHeight { .. } | EditorMode::Minimap { .. }
+ ) {
snapshot
} else {
+ let wrap_width_for = |column: u32| (column as f32 * em_advance).ceil();
let wrap_width = match editor.soft_wrap_mode(cx) {
SoftWrap::GitDiff => None,
- SoftWrap::None => Some((MAX_LINE_LEN / 2) as f32 * em_advance),
- SoftWrap::EditorWidth => Some(editor_width),
- SoftWrap::Column(column) => Some(column as f32 * em_advance),
+ SoftWrap::None => Some(wrap_width_for(MAX_LINE_LEN as u32 / 2)),
+ SoftWrap::EditorWidth => Some(editor_content_width),
+ SoftWrap::Column(column) => Some(wrap_width_for(column)),
SoftWrap::Bounded(column) => {
- Some(editor_width.min(column as f32 * em_advance))
+ Some(editor_content_width.min(wrap_width_for(column)))
}
};
- if editor.set_wrap_width(wrap_width.map(|w| w.ceil()), cx) {
+ if editor.set_wrap_width(wrap_width, cx) {
editor.snapshot(window, cx)
} else {
snapshot
@@ -6834,9 +7216,6 @@ impl Element for EditorElement {
false,
);
- // Offset the content_bounds from the text_bounds by the gutter margin (which
- // is roughly half a character wide) to make hit testing work more like how we want.
- let content_offset = point(gutter_dimensions.margin, Pixels::ZERO);
let content_origin = text_hitbox.origin + content_offset;
let editor_text_bounds =
@@ -6982,11 +7361,16 @@ impl Element for EditorElement {
.or_insert(background);
}
- let highlighted_ranges = self.editor.read(cx).background_highlights_in_range(
- start_anchor..end_anchor,
- &snapshot.display_snapshot,
- cx.theme().colors(),
- );
+ let highlighted_ranges = self
+ .editor_with_selections(cx)
+ .map(|editor| {
+ editor.read(cx).background_highlights_in_range(
+ start_anchor..end_anchor,
+ &snapshot.display_snapshot,
+ cx.theme().colors(),
+ )
+ })
+ .unwrap_or_default();
let highlighted_gutter_ranges =
self.editor.read(cx).gutter_highlights_in_range(
start_anchor..end_anchor,
@@ -7003,34 +7387,40 @@ impl Element for EditorElement {
let (local_selections, selected_buffer_ids): (
Vec<Selection<Point>>,
Vec<BufferId>,
- ) = self.editor.update(cx, |editor, cx| {
- let all_selections = editor.selections.all::<Point>(cx);
- let selected_buffer_ids = if editor.is_singleton(cx) {
- Vec::new()
- } else {
- let mut selected_buffer_ids = Vec::with_capacity(all_selections.len());
+ ) = self
+ .editor_with_selections(cx)
+ .map(|editor| {
+ editor.update(cx, |editor, cx| {
+ let all_selections = editor.selections.all::<Point>(cx);
+ let selected_buffer_ids = if editor.is_singleton(cx) {
+ Vec::new()
+ } else {
+ let mut selected_buffer_ids =
+ Vec::with_capacity(all_selections.len());
- for selection in all_selections {
- for buffer_id in snapshot
- .buffer_snapshot
- .buffer_ids_for_range(selection.range())
- {
- if selected_buffer_ids.last() != Some(&buffer_id) {
- selected_buffer_ids.push(buffer_id);
+ for selection in all_selections {
+ for buffer_id in snapshot
+ .buffer_snapshot
+ .buffer_ids_for_range(selection.range())
+ {
+ if selected_buffer_ids.last() != Some(&buffer_id) {
+ selected_buffer_ids.push(buffer_id);
+ }
+ }
}
- }
- }
- selected_buffer_ids
- };
+ selected_buffer_ids
+ };
- let mut selections = editor
- .selections
- .disjoint_in_range(start_anchor..end_anchor, cx);
- selections.extend(editor.selections.pending(cx));
+ let mut selections = editor
+ .selections
+ .disjoint_in_range(start_anchor..end_anchor, cx);
+ selections.extend(editor.selections.pending(cx));
- (selections, selected_buffer_ids)
- });
+ (selections, selected_buffer_ids)
+ })
+ })
+ .unwrap_or_default();
let (selections, mut active_rows, newest_selection_head) = self
.layout_selections(
@@ -7207,7 +7597,6 @@ impl Element for EditorElement {
glyph_grid_cell,
size(longest_line_width, max_row.as_f32() * line_height),
longest_line_blame_width,
- style.scrollbar_width,
editor_width,
EditorSettings::get_global(cx),
);
@@ -7231,6 +7620,7 @@ impl Element for EditorElement {
editor_width,
&mut scroll_width,
&gutter_dimensions,
+ right_margin,
em_width,
gutter_dimensions.full_width(),
line_height,
@@ -7275,7 +7665,7 @@ impl Element for EditorElement {
MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot).row);
let scroll_max = point(
- ((scroll_width - editor_text_bounds.size.width) / em_width).max(0.0),
+ ((scroll_width - editor_content_width) / em_width).max(0.0),
max_scroll_top,
);
@@ -7285,8 +7675,7 @@ impl Element for EditorElement {
let autoscrolled = if autoscroll_horizontally {
editor.autoscroll_horizontally(
start_row,
- editor_width - (glyph_grid_cell.width / 2.0)
- + style.scrollbar_width,
+ editor_content_width,
scroll_width,
em_width,
&line_layouts,
@@ -7338,6 +7727,7 @@ impl Element for EditorElement {
editor.render_edit_prediction_popover(
&text_hitbox.bounds,
content_origin,
+ right_margin,
&snapshot,
start_row..end_row,
scroll_position.y,
@@ -7417,8 +7807,7 @@ impl Element for EditorElement {
let autoscrolled = if autoscroll_horizontally {
editor.autoscroll_horizontally(
start_row,
- editor_width - (glyph_grid_cell.width / 2.0)
- + style.scrollbar_width,
+ editor_content_width,
scroll_width,
em_width,
&line_layouts,
@@ -7481,10 +7870,12 @@ impl Element for EditorElement {
let scrollbars_layout = self.layout_scrollbars(
&snapshot,
- scrollbar_layout_information,
+ &scrollbar_layout_information,
content_offset,
scroll_position,
non_visible_cursors,
+ right_margin,
+ editor_width,
window,
cx,
);
@@ -7500,6 +7891,7 @@ impl Element for EditorElement {
line_height,
&text_hitbox,
content_origin,
+ right_margin,
start_row,
scroll_pixel_position,
&line_layouts,
@@ -7516,6 +7908,7 @@ impl Element for EditorElement {
line_height,
&text_hitbox,
content_origin,
+ right_margin,
scroll_pixel_position,
gutter_dimensions.width - gutter_dimensions.left_padding,
window,
@@ -7616,6 +8009,18 @@ impl Element for EditorElement {
self.prepaint_expand_toggles(&mut expand_toggles, window, cx)
});
+ let minimap = window.with_element_namespace("minimap", |window| {
+ self.layout_minimap(
+ &snapshot,
+ minimap_width,
+ scroll_position,
+ &scrollbar_layout_information,
+ scrollbars_layout.as_ref(),
+ window,
+ cx,
+ )
+ });
+
let invisible_symbol_font_size = font_size / 2.;
let tab_invisible = window
.text_system()
@@ -7648,7 +8053,7 @@ impl Element for EditorElement {
)
.unwrap();
- let mode = snapshot.mode;
+ let mode = snapshot.mode.clone();
let position_map = Rc::new(PositionMap {
size: bounds.size,
@@ -7678,6 +8083,7 @@ impl Element for EditorElement {
&position_map,
newest_selection_head,
line_height,
+ right_margin,
scroll_pixel_position,
&display_hunks,
&highlighted_rows,
@@ -7698,6 +8104,7 @@ impl Element for EditorElement {
display_hunks,
content_origin,
scrollbars_layout,
+ minimap,
active_rows,
highlighted_rows,
highlighted_ranges,
@@ -7789,6 +8196,7 @@ impl Element for EditorElement {
}
});
+ self.paint_minimap(layout, window, cx);
self.paint_scrollbars(layout, window, cx);
self.paint_inline_completion_popover(layout, window, cx);
self.paint_mouse_context_menu(layout, window, cx);
@@ -7824,7 +8232,6 @@ impl ScrollbarLayoutInformation {
glyph_grid_cell: Size<Pixels>,
document_size: Size<Pixels>,
longest_line_blame_width: Pixels,
- scrollbar_width: Pixels,
editor_width: Pixels,
settings: &EditorSettings,
) -> Self {
@@ -7837,7 +8244,7 @@ impl ScrollbarLayoutInformation {
};
let right_margin = if document_size.width + longest_line_blame_width >= editor_width {
- glyph_grid_cell.width + scrollbar_width
+ glyph_grid_cell.width
} else {
px(0.0)
};
@@ -7868,6 +8275,7 @@ pub struct EditorLayout {
gutter_hitbox: Hitbox,
content_origin: gpui::Point<Pixels>,
scrollbars_layout: Option<EditorScrollbars>,
+ minimap: Option<MinimapLayout>,
mode: EditorMode,
wrap_guides: SmallVec<[(Pixels, bool); 2]>,
indent_guides: Option<Vec<IndentGuideLayout>>,
@@ -7955,6 +8363,8 @@ impl EditorScrollbars {
content_offset: gpui::Point<Pixels>,
scroll_position: gpui::Point<f32>,
scrollbar_width: Pixels,
+ right_margin: Pixels,
+ editor_width: Pixels,
show_scrollbars: bool,
scrollbar_state: Option<&ActiveScrollbarState>,
window: &mut Window,
@@ -7965,23 +8375,23 @@ impl EditorScrollbars {
glyph_grid_cell,
} = layout_information;
+ let viewport_size = size(editor_width, editor_bounds.size.height);
+
let scrollbar_bounds_for = |axis: ScrollbarAxis| match axis {
ScrollbarAxis::Horizontal => Bounds::from_corner_and_size(
Corner::BottomLeft,
editor_bounds.bottom_left(),
size(
- if settings_visibility.vertical {
- editor_bounds.size.width - scrollbar_width
- } else {
- editor_bounds.size.width
- },
+ // The horizontal viewport size differs from the space available for the
+ // horizontal scrollbar, so we have to manually stich it together here.
+ editor_bounds.size.width - right_margin,
scrollbar_width,
),
),
ScrollbarAxis::Vertical => Bounds::from_corner_and_size(
Corner::TopRight,
editor_bounds.top_right(),
- size(scrollbar_width, editor_bounds.size.height),
+ size(scrollbar_width, viewport_size.height),
),
};
@@ -1225,6 +1225,9 @@ impl SerializableItem for Editor {
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Task<Result<()>>> {
+ if self.mode.is_minimap() {
+ return None;
+ }
let mut serialize_dirty_buffers = self.serialize_dirty_buffers;
let project = self.project.clone()?;
@@ -1382,7 +1385,7 @@ impl Editor {
cx: &mut Context<Self>,
write: impl for<'a> FnOnce(&'a mut RestorationData) + 'static,
) {
- if !WorkspaceSettings::get(None, cx).restore_on_file_reopen {
+ if self.mode.is_minimap() || !WorkspaceSettings::get(None, cx).restore_on_file_reopen {
return;
}
@@ -157,6 +157,8 @@ pub struct ScrollManager {
active_scrollbar: Option<ActiveScrollbarState>,
visible_line_count: Option<f32>,
forbid_vertical_scroll: bool,
+ dragging_minimap: bool,
+ show_minimap_thumb: bool,
}
impl ScrollManager {
@@ -172,6 +174,8 @@ impl ScrollManager {
last_autoscroll: None,
visible_line_count: None,
forbid_vertical_scroll: false,
+ dragging_minimap: false,
+ show_minimap_thumb: false,
}
}
@@ -341,6 +345,24 @@ impl ScrollManager {
self.show_scrollbars
}
+ pub fn show_minimap_thumb(&mut self, cx: &mut Context<Editor>) {
+ if !self.show_minimap_thumb {
+ self.show_minimap_thumb = true;
+ cx.notify();
+ }
+ }
+
+ pub fn hide_minimap_thumb(&mut self, cx: &mut Context<Editor>) {
+ if self.show_minimap_thumb {
+ self.show_minimap_thumb = false;
+ cx.notify();
+ }
+ }
+
+ pub fn minimap_thumb_visible(&mut self) -> bool {
+ self.show_minimap_thumb
+ }
+
pub fn autoscroll_request(&self) -> Option<Autoscroll> {
self.autoscroll_request.map(|(autoscroll, _)| autoscroll)
}
@@ -396,6 +418,15 @@ impl ScrollManager {
}
}
+ pub fn is_dragging_minimap(&self) -> bool {
+ self.dragging_minimap
+ }
+
+ pub fn set_is_dragging_minimap(&mut self, dragging: bool, cx: &mut Context<Editor>) {
+ self.dragging_minimap = dragging;
+ cx.notify();
+ }
+
pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
if max < self.anchor.offset.x {
self.anchor.offset.x = max;
@@ -297,6 +297,7 @@ fn conflicts_updated(
move |cx| render_conflict_buttons(&conflict, excerpt_id, editor_handle.clone(), cx)
}),
priority: 0,
+ render_in_minimap: true,
})
}
let new_block_ids = editor.insert_blocks(blocks, None, cx);
@@ -387,7 +388,7 @@ fn render_conflict_buttons(
h_flex()
.h(cx.line_height)
.items_end()
- .ml(cx.gutter_dimensions.width)
+ .ml(cx.margins.gutter.width)
.id(cx.block_id)
.gap_0p5()
.child(
@@ -91,7 +91,8 @@ impl CursorPosition {
cursor_position.selected_count.selections = editor.selections.count();
match editor.mode() {
editor::EditorMode::AutoHeight { .. }
- | editor::EditorMode::SingleLine { .. } => {
+ | editor::EditorMode::SingleLine { .. }
+ | editor::EditorMode::Minimap { .. } => {
cursor_position.position = None;
cursor_position.context = None;
}
@@ -93,6 +93,7 @@ impl EditorBlock {
style: BlockStyle::Sticky,
render: Self::create_output_area_renderer(execution_view.clone(), on_close.clone()),
priority: 0,
+ render_in_minimap: false,
};
let block_id = editor.insert_blocks([block], None, cx)[0];
@@ -126,7 +127,8 @@ impl EditorBlock {
let execution_view = execution_view.clone();
let text_style = crate::outputs::plain::text_style(cx.window, cx.app);
- let gutter = cx.gutter_dimensions;
+ let editor_margins = cx.margins;
+ let gutter = editor_margins.gutter;
let block_id = cx.block_id;
let on_close = on_close.clone();
@@ -184,7 +186,8 @@ impl EditorBlock {
.flex_1()
.size_full()
.py(text_line_height / 2.)
- .mr(gutter.width)
+ .mr(editor_margins.right)
+ .pr_2()
.child(execution_view),
)
.into_any_element()
@@ -1140,10 +1140,10 @@ impl Vim {
let editor_mode = editor.mode();
if editor_mode.is_full()
- && !newest_selection_empty
- && self.mode == Mode::Normal
- // When following someone, don't switch vim mode.
- && editor.leader_id().is_none()
+ && !newest_selection_empty
+ && self.mode == Mode::Normal
+ // When following someone, don't switch vim mode.
+ && editor.leader_id().is_none()
{
if preserve_selection {
self.switch_mode(Mode::Visual, true, window, cx);
@@ -275,9 +275,9 @@ impl RateCompletionModal {
completion,
feedback_editor: cx.new(|cx| {
let mut editor = Editor::multi_line(window, cx);
+ editor.disable_scrollbars_and_minimap(cx);
editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
editor.set_show_line_numbers(false, cx);
- editor.set_show_scrollbars(false, cx);
editor.set_show_git_diff_gutter(false, cx);
editor.set_show_code_actions(false, cx);
editor.set_show_runnables(false, cx);