made git diff rendering respect line wrap
This commit is contained in:
parent
7f84abaf13
commit
5769cdc354
5 changed files with 185 additions and 95 deletions
|
@ -46,6 +46,7 @@ use std::{
|
||||||
ops::Range,
|
ops::Range,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
use theme::DiffStyle;
|
||||||
|
|
||||||
struct SelectionLayout {
|
struct SelectionLayout {
|
||||||
head: DisplayPoint,
|
head: DisplayPoint,
|
||||||
|
@ -525,98 +526,156 @@ impl EditorElement {
|
||||||
layout: &mut LayoutState,
|
layout: &mut LayoutState,
|
||||||
cx: &mut PaintContext,
|
cx: &mut PaintContext,
|
||||||
) {
|
) {
|
||||||
let line_height = layout.position_map.line_height;
|
struct GutterLayout {
|
||||||
let scroll_position = layout.position_map.snapshot.scroll_position();
|
line_height: f32,
|
||||||
let scroll_top = scroll_position.y() * line_height;
|
// scroll_position: Vector2F,
|
||||||
|
scroll_top: f32,
|
||||||
|
bounds: RectF,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DiffLayout<'a> {
|
||||||
|
buffer_line: usize,
|
||||||
|
last_diff: Option<(&'a DiffHunk<u32>, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff_quad(
|
||||||
|
status: DiffHunkStatus,
|
||||||
|
layout_range: Range<usize>,
|
||||||
|
gutter_layout: &GutterLayout,
|
||||||
|
diff_style: &DiffStyle,
|
||||||
|
) -> Quad {
|
||||||
|
let color = match status {
|
||||||
|
DiffHunkStatus::Added => diff_style.inserted,
|
||||||
|
DiffHunkStatus::Modified => diff_style.modified,
|
||||||
|
|
||||||
|
//TODO: This rendering is entirely a horrible hack
|
||||||
|
DiffHunkStatus::Removed => {
|
||||||
|
let row = layout_range.start;
|
||||||
|
|
||||||
|
let offset = gutter_layout.line_height / 2.;
|
||||||
|
let start_y =
|
||||||
|
row as f32 * gutter_layout.line_height + offset - gutter_layout.scroll_top;
|
||||||
|
let end_y = start_y + gutter_layout.line_height;
|
||||||
|
|
||||||
|
let width = diff_style.removed_width_em * gutter_layout.line_height;
|
||||||
|
let highlight_origin = gutter_layout.bounds.origin() + vec2f(-width, start_y);
|
||||||
|
let highlight_size = vec2f(width * 2., end_y - start_y);
|
||||||
|
let highlight_bounds = RectF::new(highlight_origin, highlight_size);
|
||||||
|
|
||||||
|
return Quad {
|
||||||
|
bounds: highlight_bounds,
|
||||||
|
background: Some(diff_style.deleted),
|
||||||
|
border: Border::new(0., Color::transparent_black()),
|
||||||
|
corner_radius: 1. * gutter_layout.line_height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let start_row = layout_range.start;
|
||||||
|
let end_row = layout_range.end;
|
||||||
|
|
||||||
|
let start_y = start_row as f32 * gutter_layout.line_height - gutter_layout.scroll_top;
|
||||||
|
let end_y = end_row as f32 * gutter_layout.line_height - gutter_layout.scroll_top;
|
||||||
|
|
||||||
|
let width = diff_style.width_em * gutter_layout.line_height;
|
||||||
|
let highlight_origin = gutter_layout.bounds.origin() + vec2f(-width, start_y);
|
||||||
|
let highlight_size = vec2f(width * 2., end_y - start_y);
|
||||||
|
let highlight_bounds = RectF::new(highlight_origin, highlight_size);
|
||||||
|
|
||||||
|
Quad {
|
||||||
|
bounds: highlight_bounds,
|
||||||
|
background: Some(color),
|
||||||
|
border: Border::new(0., Color::transparent_black()),
|
||||||
|
corner_radius: diff_style.corner_radius * gutter_layout.line_height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let gutter_layout = {
|
||||||
|
let scroll_position = layout.position_map.snapshot.scroll_position();
|
||||||
|
let line_height = layout.position_map.line_height;
|
||||||
|
GutterLayout {
|
||||||
|
scroll_top: scroll_position.y() * line_height,
|
||||||
|
// scroll_position,
|
||||||
|
line_height,
|
||||||
|
bounds,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut diff_layout = DiffLayout {
|
||||||
|
buffer_line: 0,
|
||||||
|
last_diff: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let diff_style = &cx.global::<Settings>().theme.editor.diff.clone();
|
||||||
|
// dbg!("***************");
|
||||||
|
// dbg!(&layout.diff_hunks);
|
||||||
|
// dbg!("***************");
|
||||||
|
|
||||||
|
// line is `None` when there's a line wrap
|
||||||
for (ix, line) in layout.line_number_layouts.iter().enumerate() {
|
for (ix, line) in layout.line_number_layouts.iter().enumerate() {
|
||||||
|
// dbg!(ix);
|
||||||
if let Some(line) = line {
|
if let Some(line) = line {
|
||||||
let line_origin = bounds.origin()
|
let line_origin = bounds.origin()
|
||||||
+ vec2f(
|
+ vec2f(
|
||||||
bounds.width() - line.width() - layout.gutter_padding,
|
bounds.width() - line.width() - layout.gutter_padding,
|
||||||
ix as f32 * layout.position_map.line_height
|
ix as f32 * gutter_layout.line_height
|
||||||
- (scroll_top % layout.position_map.line_height),
|
- (gutter_layout.scroll_top % gutter_layout.line_height),
|
||||||
);
|
);
|
||||||
line.paint(
|
|
||||||
line_origin,
|
line.paint(line_origin, visible_bounds, gutter_layout.line_height, cx);
|
||||||
visible_bounds,
|
|
||||||
layout.position_map.line_height,
|
//This line starts a buffer line, so let's do the diff calculation
|
||||||
cx,
|
let new_hunk = get_hunk(diff_layout.buffer_line, &layout.diff_hunks);
|
||||||
);
|
|
||||||
|
// This + the unwraps are annoying, but at least it's legible
|
||||||
|
let (is_ending, is_starting) = match (diff_layout.last_diff, new_hunk) {
|
||||||
|
(None, None) => (false, false),
|
||||||
|
(None, Some(_)) => (false, true),
|
||||||
|
(Some(_), None) => (true, false),
|
||||||
|
(Some((old_hunk, _)), Some(new_hunk)) if new_hunk == old_hunk => (false, false),
|
||||||
|
(Some(_), Some(_)) => (true, true),
|
||||||
|
};
|
||||||
|
|
||||||
|
// dbg!(diff_layout.buffer_line, is_starting);
|
||||||
|
|
||||||
|
if is_ending {
|
||||||
|
let (last_hunk, start_line) = diff_layout.last_diff.take().unwrap();
|
||||||
|
// dbg!("ending");
|
||||||
|
// dbg!(start_line..ix);
|
||||||
|
cx.scene.push_quad(diff_quad(
|
||||||
|
last_hunk.status(),
|
||||||
|
start_line..ix,
|
||||||
|
&gutter_layout,
|
||||||
|
diff_style,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_starting {
|
||||||
|
let new_hunk = new_hunk.unwrap();
|
||||||
|
|
||||||
|
diff_layout.last_diff = Some((new_hunk, ix));
|
||||||
|
};
|
||||||
|
|
||||||
|
diff_layout.buffer_line += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (
|
// If we ran out with a diff hunk still being prepped, paint it now
|
||||||
inserted_color,
|
if let Some((last_hunk, start_line)) = diff_layout.last_diff {
|
||||||
modified_color,
|
let end_line = layout.line_number_layouts.len();
|
||||||
deleted_color,
|
cx.scene.push_quad(diff_quad(
|
||||||
width_multiplier,
|
last_hunk.status(),
|
||||||
corner_radius,
|
start_line..end_line,
|
||||||
removed_width_mult,
|
&gutter_layout,
|
||||||
) = {
|
diff_style,
|
||||||
let editor = &cx.global::<Settings>().theme.editor;
|
))
|
||||||
(
|
|
||||||
editor.diff_background_inserted,
|
|
||||||
editor.diff_background_modified,
|
|
||||||
editor.diff_background_deleted,
|
|
||||||
editor.diff_indicator_width_multiplier,
|
|
||||||
editor.diff_indicator_corner_radius,
|
|
||||||
editor.removed_diff_width_multiplier,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
for hunk in &layout.diff_hunks {
|
|
||||||
let color = match hunk.status() {
|
|
||||||
DiffHunkStatus::Added => inserted_color,
|
|
||||||
DiffHunkStatus::Modified => modified_color,
|
|
||||||
|
|
||||||
//TODO: This rendering is entirely a horrible hack
|
|
||||||
DiffHunkStatus::Removed => {
|
|
||||||
let row = hunk.buffer_range.start;
|
|
||||||
|
|
||||||
let offset = line_height / 2.;
|
|
||||||
let start_y = row as f32 * line_height + offset - scroll_top;
|
|
||||||
let end_y = start_y + line_height;
|
|
||||||
|
|
||||||
let width = removed_width_mult * line_height;
|
|
||||||
let highlight_origin = bounds.origin() + vec2f(-width, start_y);
|
|
||||||
let highlight_size = vec2f(width * 2., end_y - start_y);
|
|
||||||
let highlight_bounds = RectF::new(highlight_origin, highlight_size);
|
|
||||||
|
|
||||||
cx.scene.push_quad(Quad {
|
|
||||||
bounds: highlight_bounds,
|
|
||||||
background: Some(deleted_color),
|
|
||||||
border: Border::new(0., Color::transparent_black()),
|
|
||||||
corner_radius: 1. * line_height,
|
|
||||||
});
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let start_row = hunk.buffer_range.start;
|
|
||||||
let end_row = hunk.buffer_range.end;
|
|
||||||
|
|
||||||
let start_y = start_row as f32 * line_height - scroll_top;
|
|
||||||
let end_y = end_row as f32 * line_height - scroll_top;
|
|
||||||
|
|
||||||
let width = width_multiplier * line_height;
|
|
||||||
let highlight_origin = bounds.origin() + vec2f(-width, start_y);
|
|
||||||
let highlight_size = vec2f(width * 2., end_y - start_y);
|
|
||||||
let highlight_bounds = RectF::new(highlight_origin, highlight_size);
|
|
||||||
|
|
||||||
cx.scene.push_quad(Quad {
|
|
||||||
bounds: highlight_bounds,
|
|
||||||
background: Some(color),
|
|
||||||
border: Border::new(0., Color::transparent_black()),
|
|
||||||
corner_radius: corner_radius * line_height,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() {
|
if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() {
|
||||||
let mut x = bounds.width() - layout.gutter_padding;
|
let mut x = bounds.width() - layout.gutter_padding;
|
||||||
let mut y = *row as f32 * line_height - scroll_top;
|
let mut y = *row as f32 * gutter_layout.line_height - gutter_layout.scroll_top;
|
||||||
x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
|
x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
|
||||||
y += (line_height - indicator.size().y()) / 2.;
|
y += (gutter_layout.line_height - indicator.size().y()) / 2.;
|
||||||
indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx);
|
indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1321,6 +1380,28 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the hunk that contains buffer_line, starting from start_idx
|
||||||
|
/// Returns none if there is none found, and
|
||||||
|
fn get_hunk(buffer_line: usize, hunks: &[DiffHunk<u32>]) -> Option<&DiffHunk<u32>> {
|
||||||
|
for i in 0..hunks.len() {
|
||||||
|
// Safety: Index out of bounds is handled by the check above
|
||||||
|
let hunk = hunks.get(i).unwrap();
|
||||||
|
if hunk.buffer_range.contains(&(buffer_line as u32)) {
|
||||||
|
return Some(hunk);
|
||||||
|
} else if hunk.status() == DiffHunkStatus::Removed
|
||||||
|
&& buffer_line == hunk.buffer_range.start as usize
|
||||||
|
{
|
||||||
|
return Some(hunk);
|
||||||
|
} else if hunk.buffer_range.start > buffer_line as u32 {
|
||||||
|
// If we've passed the buffer_line, just stop
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We reached the end of the array without finding a hunk, just return none.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
impl Element for EditorElement {
|
impl Element for EditorElement {
|
||||||
type LayoutState = LayoutState;
|
type LayoutState = LayoutState;
|
||||||
type PaintState = ();
|
type PaintState = ();
|
||||||
|
|
|
@ -6,7 +6,7 @@ use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point};
|
||||||
pub use git2 as libgit;
|
pub use git2 as libgit;
|
||||||
use libgit::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
|
use libgit::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum DiffHunkStatus {
|
pub enum DiffHunkStatus {
|
||||||
Added,
|
Added,
|
||||||
Modified,
|
Modified,
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use fsevent::EventStream;
|
use fsevent::EventStream;
|
||||||
use futures::{future::BoxFuture, Stream, StreamExt};
|
use futures::{future::BoxFuture, Stream, StreamExt};
|
||||||
use git::repository::{FakeGitRepositoryState, GitRepository, LibGitRepository};
|
use git::repository::{GitRepository, LibGitRepository};
|
||||||
use language::LineEnding;
|
use language::LineEnding;
|
||||||
use parking_lot::Mutex as SyncMutex;
|
use parking_lot::Mutex as SyncMutex;
|
||||||
use smol::io::{AsyncReadExt, AsyncWriteExt};
|
use smol::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use std::sync::Arc;
|
||||||
use std::{
|
use std::{
|
||||||
io,
|
io,
|
||||||
os::unix::fs::MetadataExt,
|
os::unix::fs::MetadataExt,
|
||||||
|
@ -12,16 +13,17 @@ use std::{
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
time::{Duration, SystemTime},
|
time::{Duration, SystemTime},
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
|
||||||
|
|
||||||
use text::Rope;
|
use text::Rope;
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
use collections::{btree_map, BTreeMap};
|
use collections::{btree_map, BTreeMap};
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
use futures::lock::Mutex;
|
use futures::lock::Mutex;
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
use std::sync::{Arc, Weak};
|
use git::repository::FakeGitRepositoryState;
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
use std::sync::Weak;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait Fs: Send + Sync {
|
pub trait Fs: Send + Sync {
|
||||||
|
|
|
@ -488,12 +488,7 @@ pub struct Editor {
|
||||||
pub rename_fade: f32,
|
pub rename_fade: f32,
|
||||||
pub document_highlight_read_background: Color,
|
pub document_highlight_read_background: Color,
|
||||||
pub document_highlight_write_background: Color,
|
pub document_highlight_write_background: Color,
|
||||||
pub diff_background_deleted: Color,
|
pub diff: DiffStyle,
|
||||||
pub diff_background_inserted: Color,
|
|
||||||
pub diff_background_modified: Color,
|
|
||||||
pub removed_diff_width_multiplier: f32,
|
|
||||||
pub diff_indicator_width_multiplier: f32,
|
|
||||||
pub diff_indicator_corner_radius: f32,
|
|
||||||
pub line_number: Color,
|
pub line_number: Color,
|
||||||
pub line_number_active: Color,
|
pub line_number_active: Color,
|
||||||
pub guest_selections: Vec<SelectionStyle>,
|
pub guest_selections: Vec<SelectionStyle>,
|
||||||
|
@ -577,6 +572,16 @@ pub struct CodeActions {
|
||||||
pub vertical_scale: f32,
|
pub vertical_scale: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Default)]
|
||||||
|
pub struct DiffStyle {
|
||||||
|
pub inserted: Color,
|
||||||
|
pub modified: Color,
|
||||||
|
pub deleted: Color,
|
||||||
|
pub removed_width_em: f32,
|
||||||
|
pub width_em: f32,
|
||||||
|
pub corner_radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy)]
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
pub struct Interactive<T> {
|
pub struct Interactive<T> {
|
||||||
pub default: T,
|
pub default: T,
|
||||||
|
|
|
@ -60,12 +60,14 @@ export default function editor(theme: Theme) {
|
||||||
indicator: iconColor(theme, "secondary"),
|
indicator: iconColor(theme, "secondary"),
|
||||||
verticalScale: 0.618
|
verticalScale: 0.618
|
||||||
},
|
},
|
||||||
diffBackgroundDeleted: theme.iconColor.error,
|
diff: {
|
||||||
diffBackgroundInserted: theme.iconColor.ok,
|
deleted: theme.iconColor.error,
|
||||||
diffBackgroundModified: theme.iconColor.warning,
|
inserted: theme.iconColor.ok,
|
||||||
removedDiffWidthMultiplier: 0.275,
|
modified: theme.iconColor.warning,
|
||||||
diffIndicatorWidthMultiplier: 0.16,
|
removedWidthEm: 0.275,
|
||||||
diffIndicatorCornerRadius: 0.05,
|
widthEm: 0.16,
|
||||||
|
cornerRadius: 0.05,
|
||||||
|
},
|
||||||
documentHighlightReadBackground: theme.editor.highlight.occurrence,
|
documentHighlightReadBackground: theme.editor.highlight.occurrence,
|
||||||
documentHighlightWriteBackground: theme.editor.highlight.activeOccurrence,
|
documentHighlightWriteBackground: theme.editor.highlight.activeOccurrence,
|
||||||
errorColor: theme.textColor.error,
|
errorColor: theme.textColor.error,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue