mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-24 05:25:18 +00:00
Automatically switch to unified diffs when diff view is narrower than a configurable "minimum split diff width" (#52781)
Release Notes: - The git diff diff view now automatically switches from split mode to unified mode when the pane is narrower than a configurable minimum column count. You can configure this via the new `minimum_split_diff_width` setting.
This commit is contained in:
parent
e39d5c9908
commit
fb87786375
7 changed files with 208 additions and 107 deletions
|
|
@ -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()
|
||||
}))
|
||||
.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::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()
|
||||
}))
|
||||
.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,
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue