Detailed changes
@@ -299,6 +299,13 @@
//
// Default: split
"diff_view_style": "split",
+ // The minimum width (in em-widths) at which the split diff view is used.
+ // When the editor is narrower than this, the diff view automatically
+ // switches to unified mode and switches back when the editor is wide
+ // enough. Set to 0 to disable automatic switching.
+ //
+ // Default: 100
+ "minimum_split_diff_width": 100,
// Show method signatures in the editor, when inside parentheses.
"auto_signature_help": false,
// Whether to show the signature help after completion or a bracket pair inserted.
@@ -60,6 +60,7 @@ pub struct EditorSettings {
pub completion_menu_scrollbar: ShowScrollbar,
pub completion_detail_alignment: CompletionDetailAlignment,
pub diff_view_style: DiffViewStyle,
+ pub minimum_split_diff_width: f32,
}
#[derive(Debug, Clone)]
pub struct Jupyter {
@@ -294,6 +295,7 @@ impl Settings for EditorSettings {
.unwrap(),
completion_detail_alignment: editor.completion_detail_alignment.unwrap(),
diff_view_style: editor.diff_view_style.unwrap(),
+ minimum_split_diff_width: editor.minimum_split_diff_width.unwrap(),
}
}
}
@@ -7,7 +7,8 @@ use buffer_diff::{BufferDiff, BufferDiffSnapshot};
use collections::HashMap;
use gpui::{
- Action, AppContext as _, Entity, EventEmitter, Focusable, Font, Subscription, WeakEntity,
+ Action, AppContext as _, Entity, EventEmitter, Focusable, Font, Pixels, Subscription,
+ WeakEntity, canvas,
};
use itertools::Itertools;
use language::{Buffer, Capability, HighlightedText};
@@ -17,7 +18,7 @@ use multi_buffer::{
};
use project::Project;
use rope::Point;
-use settings::DiffViewStyle;
+use settings::{DiffViewStyle, Settings};
use text::{Bias, BufferId, OffsetRangeExt as _, Patch, ToPoint as _};
use ui::{
App, Context, InteractiveElement as _, IntoElement as _, ParentElement as _, Render,
@@ -36,7 +37,7 @@ use workspace::{
};
use crate::{
- Autoscroll, Editor, EditorEvent, RenderDiffHunkControlsFn, ToggleSoftWrap,
+ Autoscroll, Editor, EditorEvent, EditorSettings, RenderDiffHunkControlsFn, ToggleSoftWrap,
actions::{DisableBreakpoint, EditLogBreakpoint, EnableBreakpoint, ToggleBreakpoint},
display_map::Companion,
};
@@ -377,6 +378,12 @@ pub struct SplittableEditor {
workspace: WeakEntity<Workspace>,
split_state: Entity<SplitEditorState>,
searched_side: Option<SplitSide>,
+ /// The preferred diff style.
+ diff_view_style: DiffViewStyle,
+ /// True when the current width is below the minimum threshold for split
+ /// mode, regardless of the current diff view style setting.
+ too_narrow_for_split: bool,
+ last_width: Option<Pixels>,
_subscriptions: Vec<Subscription>,
}
@@ -396,6 +403,10 @@ impl SplittableEditor {
self.lhs.as_ref().map(|s| &s.editor)
}
+ pub fn diff_view_style(&self) -> DiffViewStyle {
+ self.diff_view_style
+ }
+
pub fn is_split(&self) -> bool {
self.lhs.is_some()
}
@@ -499,12 +510,15 @@ impl SplittableEditor {
});
let split_state = cx.new(|cx| SplitEditorState::new(cx));
Self {
+ diff_view_style: style,
rhs_editor,
rhs_multibuffer,
lhs: None,
workspace: workspace.downgrade(),
split_state,
searched_side: None,
+ too_narrow_for_split: false,
+ last_width: None,
_subscriptions: subscriptions,
}
}
@@ -826,10 +840,19 @@ impl SplittableEditor {
window: &mut Window,
cx: &mut Context<Self>,
) {
- if self.lhs.is_some() {
- self.unsplit(window, cx);
- } else {
- self.split(window, cx);
+ match self.diff_view_style {
+ DiffViewStyle::Unified => {
+ self.diff_view_style = DiffViewStyle::Split;
+ if !self.too_narrow_for_split {
+ self.split(window, cx);
+ }
+ }
+ DiffViewStyle::Split => {
+ self.diff_view_style = DiffViewStyle::Unified;
+ if self.is_split() {
+ self.unsplit(window, cx);
+ }
+ }
}
}
@@ -1249,6 +1272,35 @@ impl SplittableEditor {
}
});
}
+
+ fn width_changed(&mut self, width: Pixels, window: &mut Window, cx: &mut Context<Self>) {
+ self.last_width = Some(width);
+
+ let min_ems = EditorSettings::get_global(cx).minimum_split_diff_width;
+
+ let style = self.rhs_editor.read(cx).create_style(cx);
+ let font_id = window.text_system().resolve_font(&style.text.font());
+ let font_size = style.text.font_size.to_pixels(window.rem_size());
+ let em_advance = window
+ .text_system()
+ .em_advance(font_id, font_size)
+ .unwrap_or(font_size);
+ let min_width = em_advance * min_ems;
+ let is_split = self.lhs.is_some();
+
+ self.too_narrow_for_split = min_ems > 0.0 && width < min_width;
+
+ match self.diff_view_style {
+ DiffViewStyle::Unified => {}
+ DiffViewStyle::Split => {
+ if self.too_narrow_for_split && is_split {
+ self.unsplit(window, cx);
+ } else if !self.too_narrow_for_split && !is_split {
+ self.split(window, cx);
+ }
+ }
+ }
+ }
}
#[cfg(test)]
@@ -2042,30 +2094,23 @@ impl Focusable for SplittableEditor {
}
}
-// impl Item for SplittableEditor {
-// type Event = EditorEvent;
-
-// fn tab_content_text(&self, detail: usize, cx: &App) -> ui::SharedString {
-// self.rhs_editor().tab_content_text(detail, cx)
-// }
-
-// fn as_searchable(&self, _this: &Entity<Self>, cx: &App) -> Option<Box<dyn workspace::searchable::SearchableItemHandle>> {
-// Some(Box::new(self.last_selected_editor().clone()))
-// }
-// }
-
impl Render for SplittableEditor {
fn render(
&mut self,
_window: &mut ui::Window,
cx: &mut ui::Context<Self>,
) -> impl ui::IntoElement {
- let inner = if self.lhs.is_some() {
+ let is_split = self.lhs.is_some();
+ let inner = if is_split {
let style = self.rhs_editor.read(cx).create_style(cx);
SplitEditorView::new(cx.entity(), style, self.split_state.clone()).into_any_element()
} else {
self.rhs_editor.clone().into_any_element()
};
+
+ let this = cx.entity().downgrade();
+ let last_width = self.last_width;
+
div()
.id("splittable-editor")
.on_action(cx.listener(Self::toggle_split))
@@ -2079,6 +2124,25 @@ impl Render for SplittableEditor {
.capture_action(cx.listener(Self::toggle_soft_wrap))
.size_full()
.child(inner)
+ .child(
+ canvas(
+ move |bounds, window, cx| {
+ let width = bounds.size.width;
+ if last_width == Some(width) {
+ return;
+ }
+ window.defer(cx, move |window, cx| {
+ this.update(cx, |this, cx| {
+ this.width_changed(width, window, cx);
+ })
+ .ok();
+ });
+ },
+ |_, _, _, _| {},
+ )
+ .absolute()
+ .size_full(),
+ )
}
}
@@ -20,9 +20,9 @@ use editor::{
};
use futures::channel::oneshot;
use gpui::{
- App, ClickEvent, Context, Entity, EventEmitter, Focusable, InteractiveElement as _,
- IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle, Styled, Subscription, Task,
- WeakEntity, Window, div,
+ Action as _, App, ClickEvent, Context, Entity, EventEmitter, Focusable,
+ InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle,
+ Styled, Subscription, Task, WeakEntity, Window, div,
};
use language::{Language, LanguageRegistry};
use project::{
@@ -33,7 +33,9 @@ use project::{
use fs::Fs;
use settings::{DiffViewStyle, Settings, update_settings_file};
use std::{any::TypeId, sync::Arc};
-use zed_actions::{outline::ToggleOutline, workspace::CopyPath, workspace::CopyRelativePath};
+use zed_actions::{
+ OpenSettingsAt, outline::ToggleOutline, workspace::CopyPath, workspace::CopyRelativePath,
+};
use ui::{
BASE_REM_SIZE_IN_PX, IconButtonShape, PlatformStyle, TextSize, Tooltip, prelude::*,
@@ -110,96 +112,97 @@ impl Render for BufferSearchBar {
.as_ref()
.and_then(|weak| weak.upgrade())
.map(|splittable_editor| {
- let is_split = splittable_editor.read(cx).is_split();
+ let editor_ref = splittable_editor.read(cx);
+ let diff_view_style = editor_ref.diff_view_style();
+ let is_split = editor_ref.is_split();
+ let min_columns =
+ EditorSettings::get_global(cx).minimum_split_diff_width as u32;
+
+ let mut split_button = IconButton::new("diff-split", IconName::DiffSplit)
+ .shape(IconButtonShape::Square)
+ .tooltip(Tooltip::element(move |_, cx| {
+ let message = if min_columns == 0 {
+ SharedString::from("Split")
+ } else {
+ format!("Split when wider than {} columns", min_columns).into()
+ };
+
+ v_flex()
+ .child(message)
+ .child(
+ h_flex()
+ .gap_0p5()
+ .text_ui_sm(cx)
+ .text_color(Color::Muted.color(cx))
+ .children(render_modifiers(
+ &gpui::Modifiers::secondary_key(),
+ PlatformStyle::platform(),
+ None,
+ Some(TextSize::Small.rems(cx).into()),
+ false,
+ ))
+ .child("click to change min width"),
+ )
+ .into_any()
+ }))
+ .on_click({
+ let splittable_editor = splittable_editor.downgrade();
+ move |_, window, cx| {
+ if window.modifiers().secondary() {
+ window.dispatch_action(
+ OpenSettingsAt {
+ path: "minimum_split_diff_width".to_string(),
+ }
+ .boxed_clone(),
+ cx,
+ );
+ } else {
+ update_settings_file(
+ <dyn Fs>::global(cx),
+ cx,
+ |settings, _| {
+ settings.editor.diff_view_style =
+ Some(DiffViewStyle::Split);
+ },
+ );
+ if diff_view_style == DiffViewStyle::Unified {
+ splittable_editor
+ .update(cx, |editor, cx| {
+ editor.toggle_split(&ToggleSplitDiff, window, cx);
+ })
+ .ok();
+ }
+ }
+ }
+ });
+
+ if diff_view_style == DiffViewStyle::Split {
+ if !is_split {
+ split_button = split_button.icon_color(Color::Disabled)
+ } else {
+ split_button = split_button.toggle_state(true)
+ }
+ }
+
h_flex()
.gap_1()
.child(
IconButton::new("diff-unified", IconName::DiffUnified)
.shape(IconButtonShape::Square)
- .toggle_state(!is_split)
- .tooltip(Tooltip::element(move |_, cx| {
- v_flex()
- .child("Unified")
- .child(
- h_flex()
- .gap_0p5()
- .text_ui_sm(cx)
- .text_color(Color::Muted.color(cx))
- .children(render_modifiers(
- &gpui::Modifiers::secondary_key(),
- PlatformStyle::platform(),
- None,
- Some(TextSize::Small.rems(cx).into()),
- false,
- ))
- .child("click to set as default"),
- )
- .into_any()
- }))
- .on_click({
- let splittable_editor = splittable_editor.downgrade();
- move |_, window, cx| {
- if window.modifiers().secondary() {
- update_settings_file(
- <dyn Fs>::global(cx),
- cx,
- |settings, _| {
- settings.editor.diff_view_style =
- Some(DiffViewStyle::Unified);
- },
- );
- }
- if is_split {
- splittable_editor
- .update(cx, |editor, cx| {
- editor.toggle_split(
- &ToggleSplitDiff,
- window,
- cx,
- );
- })
- .ok();
- }
- }
- }),
- )
- .child(
- IconButton::new("diff-split", IconName::DiffSplit)
- .shape(IconButtonShape::Square)
- .toggle_state(is_split)
- .tooltip(Tooltip::element(move |_, cx| {
- v_flex()
- .child("Split")
- .child(
- h_flex()
- .gap_0p5()
- .text_ui_sm(cx)
- .text_color(Color::Muted.color(cx))
- .children(render_modifiers(
- &gpui::Modifiers::secondary_key(),
- PlatformStyle::platform(),
- None,
- Some(TextSize::Small.rems(cx).into()),
- false,
- ))
- .child("click to set as default"),
- )
- .into_any()
- }))
+ .toggle_state(diff_view_style == DiffViewStyle::Unified)
+ .tooltip(Tooltip::text("Unified"))
.on_click({
let splittable_editor = splittable_editor.downgrade();
move |_, window, cx| {
- if window.modifiers().secondary() {
- update_settings_file(
- <dyn Fs>::global(cx),
- cx,
- |settings, _| {
- settings.editor.diff_view_style =
- Some(DiffViewStyle::Split);
- },
- );
- }
- if !is_split {
+ update_settings_file(
+ <dyn Fs>::global(cx),
+ cx,
+ |settings, _| {
+ settings.editor.diff_view_style =
+ Some(DiffViewStyle::Unified);
+ },
+ );
+ if diff_view_style == DiffViewStyle::Split {
splittable_editor
.update(cx, |editor, cx| {
editor.toggle_split(
@@ -213,6 +216,7 @@ impl Render for BufferSearchBar {
}
}),
)
+ .child(split_button)
})
} else {
None
@@ -308,6 +308,7 @@ impl VsCodeSettings {
completion_menu_scrollbar: None,
completion_detail_alignment: None,
diff_view_style: None,
+ minimum_split_diff_width: None,
}
}
@@ -226,6 +226,14 @@ pub struct EditorSettingsContent {
///
/// Default: split
pub diff_view_style: Option<DiffViewStyle>,
+
+ /// The minimum width (in em-widths) at which the split diff view is used.
+ /// When the editor is narrower than this, the diff view automatically
+ /// switches to unified mode and switches back when the editor is wide
+ /// enough. Set to 0 to disable automatic switching.
+ ///
+ /// Default: 100
+ pub minimum_split_diff_width: Option<f32>,
}
#[derive(
@@ -1474,7 +1474,7 @@ fn editor_page() -> SettingsPage {
]
}
- fn multibuffer_section() -> [SettingsPageItem; 6] {
+ fn multibuffer_section() -> [SettingsPageItem; 7] {
[
SettingsPageItem::SectionHeader("Multibuffer"),
SettingsPageItem::SettingItem(SettingItem {
@@ -1554,6 +1554,21 @@ fn editor_page() -> SettingsPage {
metadata: None,
files: USER,
}),
+ SettingsPageItem::SettingItem(SettingItem {
+ title: "Minimum Split Diff Width",
+ description: "The minimum width (in columns) at which the split diff view is used. When the editor is narrower, the diff view automatically switches to unified mode. Set to 0 to disable.",
+ field: Box::new(SettingField {
+ json_path: Some("minimum_split_diff_width"),
+ pick: |settings_content| {
+ settings_content.editor.minimum_split_diff_width.as_ref()
+ },
+ write: |settings_content, value| {
+ settings_content.editor.minimum_split_diff_width = value;
+ },
+ }),
+ metadata: None,
+ files: USER,
+ }),
]
}