feat(diff): draw old and new line numbers in a custom margin
This commit is contained in:
@@ -1,9 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media;
|
||||||
using AvaloniaEdit;
|
using AvaloniaEdit;
|
||||||
|
using AvaloniaEdit.Editing;
|
||||||
|
using AvaloniaEdit.Rendering;
|
||||||
using AvaloniaEdit.TextMate;
|
using AvaloniaEdit.TextMate;
|
||||||
using ClaudeDo.Ui.ViewModels.Modals;
|
using ClaudeDo.Ui.ViewModels.Modals;
|
||||||
using TextMateSharp.Grammars;
|
using TextMateSharp.Grammars;
|
||||||
@@ -57,6 +61,8 @@ public partial class DiffTextView : UserControl
|
|||||||
|
|
||||||
private AlignedDiff _aligned = AlignedDiff.Empty;
|
private AlignedDiff _aligned = AlignedDiff.Empty;
|
||||||
|
|
||||||
|
private DiffLineNumberMargin? _leftMargin, _rightMargin;
|
||||||
|
|
||||||
public DiffTextView()
|
public DiffTextView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -104,6 +110,7 @@ public partial class DiffTextView : UserControl
|
|||||||
_rightRows = Array.Empty<RowInfo?>();
|
_rightRows = Array.Empty<RowInfo?>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RebuildMargins();
|
||||||
ApplyWrap();
|
ApplyWrap();
|
||||||
ApplyGrammar(File?.Path);
|
ApplyGrammar(File?.Path);
|
||||||
}
|
}
|
||||||
@@ -144,7 +151,98 @@ public partial class DiffTextView : UserControl
|
|||||||
_rightTm?.SetGrammar(scope);
|
_rightTm?.SetGrammar(scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The margin's column layout depends on split vs unified, so it is rebuilt rather than
|
||||||
|
/// reconfigured whenever the layout changes.
|
||||||
|
private void RebuildMargins()
|
||||||
|
{
|
||||||
|
if (_leftMargin is not null) LeftEditor.TextArea.LeftMargins.Remove(_leftMargin);
|
||||||
|
if (_rightMargin is not null) RightEditor.TextArea.LeftMargins.Remove(_rightMargin);
|
||||||
|
|
||||||
|
var foreground = Brush("TextFaintBrush", Color.Parse("#80FFFFFF"));
|
||||||
|
var typeface = new Typeface(LeftEditor.FontFamily);
|
||||||
|
|
||||||
|
_leftMargin = new DiffLineNumberMargin(LeftRow, showOld: true, showNew: !IsSplit,
|
||||||
|
foreground, typeface, LeftEditor.FontSize);
|
||||||
|
LeftEditor.TextArea.LeftMargins.Insert(0, _leftMargin);
|
||||||
|
|
||||||
|
_rightMargin = new DiffLineNumberMargin(RightRow, showOld: false, showNew: true,
|
||||||
|
foreground, typeface, RightEditor.FontSize);
|
||||||
|
RightEditor.TextArea.LeftMargins.Insert(0, _rightMargin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IBrush Brush(string key, Color fallback) =>
|
||||||
|
this.TryGetResource(key, ActualThemeVariant, out var value) && value is IBrush brush
|
||||||
|
? brush
|
||||||
|
: new SolidColorBrush(fallback);
|
||||||
|
|
||||||
/// <summary>What one document line represents, for the margin and the background renderers.</summary>
|
/// <summary>What one document line represents, for the margin and the background renderers.</summary>
|
||||||
internal sealed record RowInfo(
|
internal sealed record RowInfo(
|
||||||
AlignedSide Kind, int? OldNo, int? NewNo, IReadOnlyList<TextSpan> Spans);
|
AlignedSide Kind, int? OldNo, int? NewNo, IReadOnlyList<TextSpan> Spans);
|
||||||
|
|
||||||
|
/// <summary>Draws one or two gutter columns of diff line numbers from the row lookup.</summary>
|
||||||
|
private sealed class DiffLineNumberMargin : AbstractMargin
|
||||||
|
{
|
||||||
|
private const double ColumnWidth = 44;
|
||||||
|
private const double Gap = 6;
|
||||||
|
|
||||||
|
private readonly Func<int, RowInfo?> _rows;
|
||||||
|
private readonly bool _showOld;
|
||||||
|
private readonly bool _showNew;
|
||||||
|
private readonly IBrush _foreground;
|
||||||
|
private readonly Typeface _typeface;
|
||||||
|
private readonly double _fontSize;
|
||||||
|
|
||||||
|
public DiffLineNumberMargin(Func<int, RowInfo?> rows, bool showOld, bool showNew,
|
||||||
|
IBrush foreground, Typeface typeface, double fontSize)
|
||||||
|
{
|
||||||
|
_rows = rows;
|
||||||
|
_showOld = showOld;
|
||||||
|
_showNew = showNew;
|
||||||
|
_foreground = foreground;
|
||||||
|
_typeface = typeface;
|
||||||
|
_fontSize = fontSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double Columns => (_showOld ? 1 : 0) + (_showNew ? 1 : 0);
|
||||||
|
|
||||||
|
protected override Size MeasureOverride(Size availableSize) =>
|
||||||
|
new(Columns * ColumnWidth + Gap, 0);
|
||||||
|
|
||||||
|
protected override void OnTextViewChanged(TextView? oldTextView, TextView? newTextView)
|
||||||
|
{
|
||||||
|
if (oldTextView is not null) oldTextView.VisualLinesChanged -= OnVisualLinesChanged;
|
||||||
|
base.OnTextViewChanged(oldTextView, newTextView);
|
||||||
|
if (newTextView is not null) newTextView.VisualLinesChanged += OnVisualLinesChanged;
|
||||||
|
InvalidateVisual();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnVisualLinesChanged(object? sender, EventArgs e) => InvalidateVisual();
|
||||||
|
|
||||||
|
public override void Render(DrawingContext context)
|
||||||
|
{
|
||||||
|
var textView = TextView;
|
||||||
|
if (textView is null || !textView.VisualLinesValid) return;
|
||||||
|
|
||||||
|
foreach (var visualLine in textView.VisualLines)
|
||||||
|
{
|
||||||
|
var lineNumber = visualLine.FirstDocumentLine.LineNumber;
|
||||||
|
if (_rows(lineNumber) is not { } row) continue;
|
||||||
|
|
||||||
|
var y = visualLine.VisualTop - textView.ScrollOffset.Y;
|
||||||
|
var column = 0;
|
||||||
|
if (_showOld) DrawNumber(context, row.OldNo, column++, y);
|
||||||
|
if (_showNew) DrawNumber(context, row.NewNo, column, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawNumber(DrawingContext context, int? value, int column, double y)
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
var text = new FormattedText(value.Value.ToString(CultureInfo.InvariantCulture),
|
||||||
|
CultureInfo.InvariantCulture, FlowDirection.LeftToRight, _typeface, _fontSize, _foreground);
|
||||||
|
// Right-align inside the column so the digits line up across rows.
|
||||||
|
var x = (column + 1) * ColumnWidth - text.Width - Gap;
|
||||||
|
context.DrawText(text, new Point(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user