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.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using AvaloniaEdit;
|
||||
using AvaloniaEdit.Editing;
|
||||
using AvaloniaEdit.Rendering;
|
||||
using AvaloniaEdit.TextMate;
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
using TextMateSharp.Grammars;
|
||||
@@ -57,6 +61,8 @@ public partial class DiffTextView : UserControl
|
||||
|
||||
private AlignedDiff _aligned = AlignedDiff.Empty;
|
||||
|
||||
private DiffLineNumberMargin? _leftMargin, _rightMargin;
|
||||
|
||||
public DiffTextView()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -104,6 +110,7 @@ public partial class DiffTextView : UserControl
|
||||
_rightRows = Array.Empty<RowInfo?>();
|
||||
}
|
||||
|
||||
RebuildMargins();
|
||||
ApplyWrap();
|
||||
ApplyGrammar(File?.Path);
|
||||
}
|
||||
@@ -144,7 +151,98 @@ public partial class DiffTextView : UserControl
|
||||
_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>
|
||||
internal sealed record RowInfo(
|
||||
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