Skip to content

Commit fb87786

Browse files
authored
Automatically switch to unified diffs when diff view is narrower than a configurable "minimum split diff width" (zed-industries#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.
1 parent e39d5c9 commit fb87786

7 files changed

Lines changed: 208 additions & 107 deletions

File tree

assets/settings/default.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,13 @@
299299
//
300300
// Default: split
301301
"diff_view_style": "split",
302+
// The minimum width (in em-widths) at which the split diff view is used.
303+
// When the editor is narrower than this, the diff view automatically
304+
// switches to unified mode and switches back when the editor is wide
305+
// enough. Set to 0 to disable automatic switching.
306+
//
307+
// Default: 100
308+
"minimum_split_diff_width": 100,
302309
// Show method signatures in the editor, when inside parentheses.
303310
"auto_signature_help": false,
304311
// Whether to show the signature help after completion or a bracket pair inserted.

crates/editor/src/editor_settings.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ pub struct EditorSettings {
6060
pub completion_menu_scrollbar: ShowScrollbar,
6161
pub completion_detail_alignment: CompletionDetailAlignment,
6262
pub diff_view_style: DiffViewStyle,
63+
pub minimum_split_diff_width: f32,
6364
}
6465
#[derive(Debug, Clone)]
6566
pub struct Jupyter {
@@ -294,6 +295,7 @@ impl Settings for EditorSettings {
294295
.unwrap(),
295296
completion_detail_alignment: editor.completion_detail_alignment.unwrap(),
296297
diff_view_style: editor.diff_view_style.unwrap(),
298+
minimum_split_diff_width: editor.minimum_split_diff_width.unwrap(),
297299
}
298300
}
299301
}

crates/editor/src/split.rs

Lines changed: 84 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use buffer_diff::{BufferDiff, BufferDiffSnapshot};
77
use collections::HashMap;
88

99
use gpui::{
10-
Action, AppContext as _, Entity, EventEmitter, Focusable, Font, Subscription, WeakEntity,
10+
Action, AppContext as _, Entity, EventEmitter, Focusable, Font, Pixels, Subscription,
11+
WeakEntity, canvas,
1112
};
1213
use itertools::Itertools;
1314
use language::{Buffer, Capability, HighlightedText};
@@ -17,7 +18,7 @@ use multi_buffer::{
1718
};
1819
use project::Project;
1920
use rope::Point;
20-
use settings::DiffViewStyle;
21+
use settings::{DiffViewStyle, Settings};
2122
use text::{Bias, BufferId, OffsetRangeExt as _, Patch, ToPoint as _};
2223
use ui::{
2324
App, Context, InteractiveElement as _, IntoElement as _, ParentElement as _, Render,
@@ -36,7 +37,7 @@ use workspace::{
3637
};
3738

3839
use crate::{
39-
Autoscroll, Editor, EditorEvent, RenderDiffHunkControlsFn, ToggleSoftWrap,
40+
Autoscroll, Editor, EditorEvent, EditorSettings, RenderDiffHunkControlsFn, ToggleSoftWrap,
4041
actions::{DisableBreakpoint, EditLogBreakpoint, EnableBreakpoint, ToggleBreakpoint},
4142
display_map::Companion,
4243
};
@@ -377,6 +378,12 @@ pub struct SplittableEditor {
377378
workspace: WeakEntity<Workspace>,
378379
split_state: Entity<SplitEditorState>,
379380
searched_side: Option<SplitSide>,
381+
/// The preferred diff style.
382+
diff_view_style: DiffViewStyle,
383+
/// True when the current width is below the minimum threshold for split
384+
/// mode, regardless of the current diff view style setting.
385+
too_narrow_for_split: bool,
386+
last_width: Option<Pixels>,
380387
_subscriptions: Vec<Subscription>,
381388
}
382389

@@ -396,6 +403,10 @@ impl SplittableEditor {
396403
self.lhs.as_ref().map(|s| &s.editor)
397404
}
398405

406+
pub fn diff_view_style(&self) -> DiffViewStyle {
407+
self.diff_view_style
408+
}
409+
399410
pub fn is_split(&self) -> bool {
400411
self.lhs.is_some()
401412
}
@@ -499,12 +510,15 @@ impl SplittableEditor {
499510
});
500511
let split_state = cx.new(|cx| SplitEditorState::new(cx));
501512
Self {
513+
diff_view_style: style,
502514
rhs_editor,
503515
rhs_multibuffer,
504516
lhs: None,
505517
workspace: workspace.downgrade(),
506518
split_state,
507519
searched_side: None,
520+
too_narrow_for_split: false,
521+
last_width: None,
508522
_subscriptions: subscriptions,
509523
}
510524
}
@@ -826,10 +840,19 @@ impl SplittableEditor {
826840
window: &mut Window,
827841
cx: &mut Context<Self>,
828842
) {
829-
if self.lhs.is_some() {
830-
self.unsplit(window, cx);
831-
} else {
832-
self.split(window, cx);
843+
match self.diff_view_style {
844+
DiffViewStyle::Unified => {
845+
self.diff_view_style = DiffViewStyle::Split;
846+
if !self.too_narrow_for_split {
847+
self.split(window, cx);
848+
}
849+
}
850+
DiffViewStyle::Split => {
851+
self.diff_view_style = DiffViewStyle::Unified;
852+
if self.is_split() {
853+
self.unsplit(window, cx);
854+
}
855+
}
833856
}
834857
}
835858

@@ -1249,6 +1272,35 @@ impl SplittableEditor {
12491272
}
12501273
});
12511274
}
1275+
1276+
fn width_changed(&mut self, width: Pixels, window: &mut Window, cx: &mut Context<Self>) {
1277+
self.last_width = Some(width);
1278+
1279+
let min_ems = EditorSettings::get_global(cx).minimum_split_diff_width;
1280+
1281+
let style = self.rhs_editor.read(cx).create_style(cx);
1282+
let font_id = window.text_system().resolve_font(&style.text.font());
1283+
let font_size = style.text.font_size.to_pixels(window.rem_size());
1284+
let em_advance = window
1285+
.text_system()
1286+
.em_advance(font_id, font_size)
1287+
.unwrap_or(font_size);
1288+
let min_width = em_advance * min_ems;
1289+
let is_split = self.lhs.is_some();
1290+
1291+
self.too_narrow_for_split = min_ems > 0.0 && width < min_width;
1292+
1293+
match self.diff_view_style {
1294+
DiffViewStyle::Unified => {}
1295+
DiffViewStyle::Split => {
1296+
if self.too_narrow_for_split && is_split {
1297+
self.unsplit(window, cx);
1298+
} else if !self.too_narrow_for_split && !is_split {
1299+
self.split(window, cx);
1300+
}
1301+
}
1302+
}
1303+
}
12521304
}
12531305

12541306
#[cfg(test)]
@@ -2042,30 +2094,23 @@ impl Focusable for SplittableEditor {
20422094
}
20432095
}
20442096

2045-
// impl Item for SplittableEditor {
2046-
// type Event = EditorEvent;
2047-
2048-
// fn tab_content_text(&self, detail: usize, cx: &App) -> ui::SharedString {
2049-
// self.rhs_editor().tab_content_text(detail, cx)
2050-
// }
2051-
2052-
// fn as_searchable(&self, _this: &Entity<Self>, cx: &App) -> Option<Box<dyn workspace::searchable::SearchableItemHandle>> {
2053-
// Some(Box::new(self.last_selected_editor().clone()))
2054-
// }
2055-
// }
2056-
20572097
impl Render for SplittableEditor {
20582098
fn render(
20592099
&mut self,
20602100
_window: &mut ui::Window,
20612101
cx: &mut ui::Context<Self>,
20622102
) -> impl ui::IntoElement {
2063-
let inner = if self.lhs.is_some() {
2103+
let is_split = self.lhs.is_some();
2104+
let inner = if is_split {
20642105
let style = self.rhs_editor.read(cx).create_style(cx);
20652106
SplitEditorView::new(cx.entity(), style, self.split_state.clone()).into_any_element()
20662107
} else {
20672108
self.rhs_editor.clone().into_any_element()
20682109
};
2110+
2111+
let this = cx.entity().downgrade();
2112+
let last_width = self.last_width;
2113+
20692114
div()
20702115
.id("splittable-editor")
20712116
.on_action(cx.listener(Self::toggle_split))
@@ -2079,6 +2124,25 @@ impl Render for SplittableEditor {
20792124
.capture_action(cx.listener(Self::toggle_soft_wrap))
20802125
.size_full()
20812126
.child(inner)
2127+
.child(
2128+
canvas(
2129+
move |bounds, window, cx| {
2130+
let width = bounds.size.width;
2131+
if last_width == Some(width) {
2132+
return;
2133+
}
2134+
window.defer(cx, move |window, cx| {
2135+
this.update(cx, |this, cx| {
2136+
this.width_changed(width, window, cx);
2137+
})
2138+
.ok();
2139+
});
2140+
},
2141+
|_, _, _, _| {},
2142+
)
2143+
.absolute()
2144+
.size_full(),
2145+
)
20822146
}
20832147
}
20842148

0 commit comments

Comments
 (0)