refactor: use a new class Models.ConflictLine for conflict editor instead of Models.TextDiffLine

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo
2026-01-30 11:05:58 +08:00
parent 2006fa8040
commit 7f757e11d5
7 changed files with 326 additions and 372 deletions

View File

@@ -2,6 +2,13 @@ using System.Collections.Generic;
namespace SourceGit.Models
{
public enum ConflictPanelType
{
Ours,
Theirs,
Result
}
public enum ConflictResolution
{
None,
@@ -11,19 +18,13 @@ namespace SourceGit.Models
UseBothTheirsFirst,
}
public enum ConflictMarkerType
public enum ConflictLineType
{
Start, // <<<<<<<
Base, // ||||||| (diff3 style)
Separator, // =======
End, // >>>>>>>
}
public enum ConflictPanelType
{
Mine,
None,
Common,
Marker,
Ours,
Theirs,
Result
}
public enum ConflictLineState
@@ -37,6 +38,28 @@ namespace SourceGit.Models
ResolvedBlockEnd,
}
public class ConflictLine
{
public ConflictLineType Type { get; set; } = ConflictLineType.None;
public string Content { get; set; } = string.Empty;
public string LineNumber { get; set; } = string.Empty;
public ConflictLine()
{
}
public ConflictLine(ConflictLineType type, string content)
{
Type = type;
Content = content;
}
public ConflictLine(ConflictLineType type, string content, int lineNumber)
{
Type = type;
Content = content;
LineNumber = lineNumber.ToString();
}
}
public record ConflictSelectedChunk(
double Y,
double Height,
@@ -45,14 +68,6 @@ namespace SourceGit.Models
bool IsResolved
);
public class ConflictMarkerInfo
{
public int LineNumber { get; set; }
public int StartOffset { get; set; }
public int EndOffset { get; set; }
public ConflictMarkerType Type { get; set; }
}
public class ConflictRegion
{
public int StartLineInOriginal { get; set; }

View File

@@ -22,19 +22,19 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _error, value);
}
public List<Models.TextDiffLine> OursDiffLines
public List<Models.ConflictLine> OursDiffLines
{
get => _oursDiffLines;
private set => SetProperty(ref _oursDiffLines, value);
}
public List<Models.TextDiffLine> TheirsDiffLines
public List<Models.ConflictLine> TheirsDiffLines
{
get => _theirsDiffLines;
private set => SetProperty(ref _theirsDiffLines, value);
}
public List<Models.TextDiffLine> ResultDiffLines
public List<Models.ConflictLine> ResultDiffLines
{
get => _resultDiffLines;
private set => SetProperty(ref _resultDiffLines, value);
@@ -207,8 +207,8 @@ namespace SourceGit.ViewModels
return;
var lines = content.Split('\n', StringSplitOptions.None);
var oursLines = new List<Models.TextDiffLine>();
var theirsLines = new List<Models.TextDiffLine>();
var oursLines = new List<Models.ConflictLine>();
var theirsLines = new List<Models.ConflictLine>();
int oursLineNumber = 1;
int theirsLineNumber = 1;
int i = 0;
@@ -225,8 +225,8 @@ namespace SourceGit.ViewModels
StartMarker = line,
};
oursLines.Add(new Models.TextDiffLine());
theirsLines.Add(new Models.TextDiffLine());
oursLines.Add(new());
theirsLines.Add(new());
i++;
// Collect ours content
@@ -234,7 +234,10 @@ namespace SourceGit.ViewModels
!lines[i].StartsWith("|||||||", StringComparison.Ordinal) &&
!lines[i].StartsWith("=======", StringComparison.Ordinal))
{
region.OursContent.Add(lines[i]);
line = lines[i];
region.OursContent.Add(line);
oursLines.Add(new(Models.ConflictLineType.Ours, line, oursLineNumber++));
theirsLines.Add(new());
i++;
}
@@ -249,8 +252,8 @@ namespace SourceGit.ViewModels
// Capture separator marker
if (i < lines.Length && lines[i].StartsWith("=======", StringComparison.Ordinal))
{
oursLines.Add(new Models.TextDiffLine());
theirsLines.Add(new Models.TextDiffLine());
oursLines.Add(new());
theirsLines.Add(new());
region.SeparatorMarker = lines[i];
i++;
}
@@ -258,27 +261,18 @@ namespace SourceGit.ViewModels
// Collect theirs content
while (i < lines.Length && !lines[i].StartsWith(">>>>>>>", StringComparison.Ordinal))
{
region.TheirsContent.Add(lines[i]);
line = lines[i];
region.TheirsContent.Add(line);
oursLines.Add(new());
theirsLines.Add(new(Models.ConflictLineType.Theirs, line, theirsLineNumber++));
i++;
}
foreach (var mine in region.OursContent)
{
oursLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Deleted, mine, oursLineNumber++, 0));
theirsLines.Add(new Models.TextDiffLine());
}
foreach (var theirs in region.TheirsContent)
{
oursLines.Add(new Models.TextDiffLine());
theirsLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Added, theirs, 0, theirsLineNumber++));
}
// Capture end marker (e.g., ">>>>>>> feature-branch")
if (i < lines.Length && lines[i].StartsWith(">>>>>>>", StringComparison.Ordinal))
{
oursLines.Add(new Models.TextDiffLine());
theirsLines.Add(new Models.TextDiffLine());
oursLines.Add(new());
theirsLines.Add(new());
region.EndMarker = lines[i];
region.EndLineInOriginal = i;
@@ -289,8 +283,8 @@ namespace SourceGit.ViewModels
}
else
{
oursLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Normal, line, oursLineNumber, oursLineNumber));
theirsLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Normal, line, theirsLineNumber, theirsLineNumber));
oursLines.Add(new(Models.ConflictLineType.Common, line, oursLineNumber));
theirsLines.Add(new(Models.ConflictLineType.Common, line, theirsLineNumber));
i++;
oursLineNumber++;
theirsLineNumber++;
@@ -305,7 +299,7 @@ namespace SourceGit.ViewModels
private void RefreshDisplayData()
{
var resultLines = new List<Models.TextDiffLine>();
var resultLines = new List<Models.ConflictLine>();
_lineStates.Clear();
if (_oursDiffLines == null || _oursDiffLines.Count == 0)
@@ -343,14 +337,14 @@ namespace SourceGit.ViewModels
int mineCount = currentRegion.OursContent.Count;
for (int i = 0; i < mineCount; i++)
{
resultLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Deleted, currentRegion.OursContent[i], resultLineNumber, resultLineNumber));
resultLines.Add(new(Models.ConflictLineType.Ours, currentRegion.OursContent[i], resultLineNumber));
resultLineNumber++;
}
int theirsCount = currentRegion.TheirsContent.Count;
for (int i = 0; i < theirsCount; i++)
{
resultLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Added, currentRegion.TheirsContent[i], resultLineNumber, resultLineNumber));
resultLines.Add(new(Models.ConflictLineType.Theirs, currentRegion.TheirsContent[i], resultLineNumber));
resultLineNumber++;
}
}
@@ -359,14 +353,14 @@ namespace SourceGit.ViewModels
int theirsCount = currentRegion.TheirsContent.Count;
for (int i = 0; i < theirsCount; i++)
{
resultLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Added, currentRegion.TheirsContent[i], resultLineNumber, resultLineNumber));
resultLines.Add(new(Models.ConflictLineType.Theirs, currentRegion.TheirsContent[i], resultLineNumber));
resultLineNumber++;
}
int mineCount = currentRegion.OursContent.Count;
for (int i = 0; i < mineCount; i++)
{
resultLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Deleted, currentRegion.OursContent[i], resultLineNumber, resultLineNumber));
resultLines.Add(new(Models.ConflictLineType.Ours, currentRegion.OursContent[i], resultLineNumber));
resultLineNumber++;
}
}
@@ -375,7 +369,7 @@ namespace SourceGit.ViewModels
int mineCount = currentRegion.OursContent.Count;
for (int i = 0; i < mineCount; i++)
{
resultLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Deleted, currentRegion.OursContent[i], resultLineNumber, resultLineNumber));
resultLines.Add(new(Models.ConflictLineType.Ours, currentRegion.OursContent[i], resultLineNumber));
resultLineNumber++;
}
}
@@ -384,7 +378,7 @@ namespace SourceGit.ViewModels
int theirsCount = currentRegion.TheirsContent.Count;
for (int i = 0; i < theirsCount; i++)
{
resultLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Added, currentRegion.TheirsContent[i], resultLineNumber, resultLineNumber));
resultLines.Add(new(Models.ConflictLineType.Theirs, currentRegion.TheirsContent[i], resultLineNumber));
resultLineNumber++;
}
}
@@ -393,7 +387,7 @@ namespace SourceGit.ViewModels
int added = resultLines.Count - oldLineCount;
int padding = regionLines - added;
for (int p = 0; p < padding; p++)
resultLines.Add(new Models.TextDiffLine());
resultLines.Add(new());
int blockSize = resultLines.Count - oldLineCount - 2;
_lineStates.Add(Models.ConflictLineState.ResolvedBlockStart);
@@ -403,31 +397,25 @@ namespace SourceGit.ViewModels
}
else
{
// Unresolved - show conflict markers with content, aligned with Mine/Theirs
// First line: start marker (use real marker from file)
resultLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, currentRegion.StartMarker, 0, 0));
resultLines.Add(new(Models.ConflictLineType.Marker, currentRegion.StartMarker));
_lineStates.Add(Models.ConflictLineState.ConflictBlockStart);
// Mine content lines (matches the deleted lines in Ours panel)
foreach (var line in currentRegion.OursContent)
{
resultLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line, 0, resultLineNumber++));
resultLines.Add(new(Models.ConflictLineType.Ours, line, resultLineNumber++));
_lineStates.Add(Models.ConflictLineState.ConflictBlock);
}
// Separator marker between Mine and Theirs
resultLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, currentRegion.SeparatorMarker, 0, 0));
resultLines.Add(new(Models.ConflictLineType.Marker, currentRegion.SeparatorMarker));
_lineStates.Add(Models.ConflictLineState.ConflictBlock);
// Theirs content lines (matches the added lines in Theirs panel)
foreach (var line in currentRegion.TheirsContent)
{
resultLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Added, line, 0, resultLineNumber++));
resultLines.Add(new(Models.ConflictLineType.Theirs, line, resultLineNumber++));
_lineStates.Add(Models.ConflictLineState.ConflictBlock);
}
// End marker (use real marker from file)
resultLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, currentRegion.EndMarker, 0, 0));
resultLines.Add(new(Models.ConflictLineType.Marker, currentRegion.EndMarker));
_lineStates.Add(Models.ConflictLineState.ConflictBlockEnd);
}
@@ -436,20 +424,10 @@ namespace SourceGit.ViewModels
}
else
{
// Normal line - copy from ours panel
var oursLine = _oursDiffLines[currentLine];
if (oursLine.Type == Models.TextDiffLineType.Normal)
{
resultLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Normal, oursLine.Content, resultLineNumber, resultLineNumber));
resultLineNumber++;
}
else
{
// Empty placeholder line (shouldn't happen outside conflicts, but handle it)
resultLines.Add(new Models.TextDiffLine());
}
resultLines.Add(new(oursLine.Type, oursLine.Content, resultLineNumber));
_lineStates.Add(Models.ConflictLineState.Normal);
resultLineNumber++;
currentLine++;
}
}
@@ -473,9 +451,9 @@ namespace SourceGit.ViewModels
private string _originalContent = string.Empty;
private int _unsolvedCount = 0;
private int _diffMaxLineNumber = 0;
private List<Models.TextDiffLine> _oursDiffLines = [];
private List<Models.TextDiffLine> _theirsDiffLines = [];
private List<Models.TextDiffLine> _resultDiffLines = [];
private List<Models.ConflictLine> _oursDiffLines = [];
private List<Models.ConflictLine> _theirsDiffLines = [];
private List<Models.ConflictLine> _resultDiffLines = [];
private List<Models.ConflictRegion> _conflictRegions = [];
private List<Models.ConflictLineState> _lineStates = [];
private Vector _scrollOffset = Vector.Zero;

View File

@@ -5,8 +5,6 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
{
public record TextDiffDisplayRange(int Start, int End);
public record TextDiffSelectedChunk(double Y, double Height, int StartIdx, int EndIdx, bool Combined, bool IsOldSide)
{
public static bool IsChanged(TextDiffSelectedChunk oldValue, TextDiffSelectedChunk newValue)
@@ -43,7 +41,7 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _blockNavigation, value);
}
public TextDiffDisplayRange DisplayRange
public TextLineRange DisplayRange
{
get => _displayRange;
set => SetProperty(ref _displayRange, value);
@@ -161,7 +159,7 @@ namespace SourceGit.ViewModels
protected Vector _scrollOffset = Vector.Zero;
protected BlockNavigation _blockNavigation = null;
private TextDiffDisplayRange _displayRange = null;
private TextLineRange _displayRange = null;
private TextDiffSelectedChunk _selectedChunk = null;
}

View File

@@ -0,0 +1,4 @@
namespace SourceGit.ViewModels
{
public record TextLineRange(int Start, int End);
}

View File

@@ -92,18 +92,17 @@
FontWeight="Bold"/>
</Border>
<Grid Grid.Row="1">
<v:MergeDiffPresenter x:Name="OursPresenter"
PanelType="Mine"
DiffLines="{Binding OursDiffLines, Mode=OneWay}"
MaxLineNumber="{Binding DiffMaxLineNumber}"
FileName="{Binding FilePath}"
SelectedChunk="{Binding SelectedChunk}"
FontFamily="{DynamicResource Fonts.Monospace}"
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
AddedContentBackground="{DynamicResource Brush.Diff.TheirsBG}"
DeletedContentBackground="{DynamicResource Brush.Diff.MineBG}"
BorderThickness="1"
BorderBrush="{DynamicResource Brush.Border2}"/>
<v:MergeConflictTextPresenter x:Name="OursPresenter"
PanelType="Ours"
Lines="{Binding OursDiffLines, Mode=OneWay}"
MaxLineNumber="{Binding DiffMaxLineNumber}"
FileName="{Binding FilePath}"
SelectedChunk="{Binding SelectedChunk}"
FontFamily="{DynamicResource Fonts.Monospace}"
TheirsContentBackground="{DynamicResource Brush.Diff.TheirsBG}"
OursContentBackground="{DynamicResource Brush.Diff.MineBG}"
BorderThickness="1"
BorderBrush="{DynamicResource Brush.Border2}"/>
<Border x:Name="MinePopup"
IsVisible="False"
VerticalAlignment="Top"
@@ -132,18 +131,17 @@
FontWeight="Bold"/>
</Border>
<Grid Grid.Row="1">
<v:MergeDiffPresenter x:Name="TheirsPresenter"
PanelType="Theirs"
DiffLines="{Binding TheirsDiffLines, Mode=OneWay}"
MaxLineNumber="{Binding DiffMaxLineNumber}"
FileName="{Binding FilePath}"
SelectedChunk="{Binding SelectedChunk}"
FontFamily="{DynamicResource Fonts.Monospace}"
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
AddedContentBackground="{DynamicResource Brush.Diff.TheirsBG}"
DeletedContentBackground="{DynamicResource Brush.Diff.MineBG}"
BorderThickness="1"
BorderBrush="{DynamicResource Brush.Border2}"/>
<v:MergeConflictTextPresenter x:Name="TheirsPresenter"
PanelType="Theirs"
Lines="{Binding TheirsDiffLines, Mode=OneWay}"
MaxLineNumber="{Binding DiffMaxLineNumber}"
FileName="{Binding FilePath}"
SelectedChunk="{Binding SelectedChunk}"
FontFamily="{DynamicResource Fonts.Monospace}"
TheirsContentBackground="{DynamicResource Brush.Diff.TheirsBG}"
OursContentBackground="{DynamicResource Brush.Diff.MineBG}"
BorderThickness="1"
BorderBrush="{DynamicResource Brush.Border2}"/>
<Border x:Name="TheirsPopup"
IsVisible="False"
VerticalAlignment="Top"
@@ -171,19 +169,17 @@
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.Result}" FontWeight="Bold"/>
</Border>
<Grid Grid.Row="1">
<v:MergeDiffPresenter x:Name="ResultPresenter"
PanelType="Result"
DiffLines="{Binding ResultDiffLines, Mode=OneWay}"
MaxLineNumber="{Binding DiffMaxLineNumber}"
FileName="{Binding FilePath}"
SelectedChunk="{Binding SelectedChunk}"
FontFamily="{DynamicResource Fonts.Monospace}"
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
AddedContentBackground="{DynamicResource Brush.Diff.TheirsBG}"
DeletedContentBackground="{DynamicResource Brush.Diff.MineBG}"
IndicatorBackground="{DynamicResource Brush.Diff.EmptyBG}"
BorderThickness="1"
BorderBrush="{DynamicResource Brush.Border2}"/>
<v:MergeConflictTextPresenter x:Name="ResultPresenter"
PanelType="Result"
Lines="{Binding ResultDiffLines, Mode=OneWay}"
MaxLineNumber="{Binding DiffMaxLineNumber}"
FileName="{Binding FilePath}"
SelectedChunk="{Binding SelectedChunk}"
FontFamily="{DynamicResource Fonts.Monospace}"
TheirsContentBackground="{DynamicResource Brush.Diff.TheirsBG}"
OursContentBackground="{DynamicResource Brush.Diff.MineBG}"
BorderThickness="1"
BorderBrush="{DynamicResource Brush.Border2}"/>
<Border x:Name="ResultPopup"
IsVisible="False"
VerticalAlignment="Top"

View File

@@ -23,10 +23,181 @@ using AvaloniaEdit.Utils;
namespace SourceGit.Views
{
public class MergeDiffPresenter : TextEditor
public class MergeConflictTextPresenter : TextEditor
{
public class LineNumberMargin : AbstractMargin
{
public LineNumberMargin(MergeConflictTextPresenter presenter)
{
_presenter = presenter;
Margin = new Thickness(8, 0);
ClipToBounds = true;
}
public override void Render(DrawingContext context)
{
var lines = _presenter.Lines;
if (lines == null)
return;
var view = TextView;
if (view is not { VisualLinesValid: true })
return;
var typeface = view.CreateTypeface();
foreach (var line in view.VisualLines)
{
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
continue;
var index = line.FirstDocumentLine.LineNumber;
if (index > lines.Count)
break;
var lineNumber = lines[index - 1].LineNumber;
if (string.IsNullOrEmpty(lineNumber))
continue;
var y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.LineMiddle) - view.VerticalOffset;
var txt = new FormattedText(
lineNumber,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
typeface,
_presenter.FontSize,
_presenter.Foreground);
context.DrawText(txt, new Point(Bounds.Width - txt.Width, y - (txt.Height * 0.5)));
}
}
protected override Size MeasureOverride(Size availableSize)
{
var maxLine = _presenter.MaxLineNumber;
if (maxLine == 0)
return new Size(32, 0);
var typeface = TextView.CreateTypeface();
var test = new FormattedText(
$"{maxLine}",
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
typeface,
_presenter.FontSize,
Brushes.White);
return new Size(test.Width, 0);
}
private readonly MergeConflictTextPresenter _presenter;
}
public class VerticalSeparatorMargin : AbstractMargin
{
public override void Render(DrawingContext context)
{
var pen = new Pen(Brushes.DarkGray);
context.DrawLine(pen, new Point(0, 0), new Point(0, Bounds.Height));
}
protected override Size MeasureOverride(Size availableSize)
{
return new Size(1, 0);
}
}
public class ConflictMarkerTransformer : DocumentColorizingTransformer
{
public ConflictMarkerTransformer(MergeConflictTextPresenter presenter)
{
_presenter = presenter;
}
protected override void ColorizeLine(DocumentLine line)
{
var lines = _presenter.Lines;
if (lines == null || line.LineNumber > lines.Count)
return;
var info = lines[line.LineNumber - 1];
if (info.Type == Models.ConflictLineType.Marker)
{
ChangeLinePart(line.Offset, line.EndOffset, element =>
{
element.TextRunProperties.SetTypeface(new Typeface(_presenter.FontFamily, FontStyle.Italic, FontWeight.Normal));
element.TextRunProperties.SetForegroundBrush(Brushes.Gray);
});
}
}
private readonly MergeConflictTextPresenter _presenter;
}
public class LineBackgroundRenderer : IBackgroundRenderer
{
public KnownLayer Layer => KnownLayer.Background;
public LineBackgroundRenderer(MergeConflictTextPresenter presenter)
{
_presenter = presenter;
}
public void Draw(TextView textView, DrawingContext drawingContext)
{
var lines = _presenter.Lines;
if (lines == null || _presenter.Document == null || !textView.VisualLinesValid)
return;
if (_presenter.DataContext is not ViewModels.MergeConflictEditor vm)
return;
var width = textView.Bounds.Width;
foreach (var line in textView.VisualLines)
{
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
continue;
var index = line.FirstDocumentLine.LineNumber;
if (index > lines.Count)
break;
var lineIndex = index - 1;
var info = lines[lineIndex];
var lineState = vm.GetLineState(lineIndex);
var startY = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.LineTop) - textView.VerticalOffset;
var endY = line.GetTextLineVisualYPosition(line.TextLines[^1], VisualYPosition.LineBottom) - textView.VerticalOffset;
var rect = new Rect(0, startY, width, endY - startY);
if (lineState == Models.ConflictLineState.ConflictBlockStart)
drawingContext.DrawLine(new Pen(new SolidColorBrush(Colors.Red, 0.6)), new Point(0, startY + 0.5), new Point(width, startY + 0.5));
else if (lineState == Models.ConflictLineState.ConflictBlockEnd)
drawingContext.DrawLine(new Pen(new SolidColorBrush(Colors.Red, 0.6)), new Point(0, endY - 0.5), new Point(width, endY - 0.5));
else if (lineState == Models.ConflictLineState.ResolvedBlockStart)
drawingContext.DrawLine(new Pen(new SolidColorBrush(Colors.Green, 0.6)), new Point(0, startY + 0.5), new Point(width, startY + 0.5));
else if (lineState == Models.ConflictLineState.ResolvedBlockEnd)
drawingContext.DrawLine(new Pen(new SolidColorBrush(Colors.Green, 0.6)), new Point(0, endY - 0.5), new Point(width, endY - 0.5));
if (lineState >= Models.ConflictLineState.ResolvedBlockStart)
drawingContext.DrawRectangle(new SolidColorBrush(Colors.Green, 0.1), null, rect);
else if (lineState >= Models.ConflictLineState.ConflictBlockStart)
drawingContext.DrawRectangle(new SolidColorBrush(Colors.Red, 0.1), null, rect);
var bg = info.Type switch
{
Models.ConflictLineType.Ours => _presenter.OursContentBackground,
Models.ConflictLineType.Theirs => _presenter.TheirsContentBackground,
_ => null,
};
if (bg != null)
drawingContext.DrawRectangle(bg, null, rect);
}
}
private readonly MergeConflictTextPresenter _presenter;
}
public static readonly StyledProperty<string> FileNameProperty =
AvaloniaProperty.Register<MergeDiffPresenter, string>(nameof(FileName), string.Empty);
AvaloniaProperty.Register<MergeConflictTextPresenter, string>(nameof(FileName), string.Empty);
public string FileName
{
@@ -35,7 +206,7 @@ namespace SourceGit.Views
}
public static readonly StyledProperty<Models.ConflictPanelType> PanelTypeProperty =
AvaloniaProperty.Register<MergeDiffPresenter, Models.ConflictPanelType>(nameof(PanelType));
AvaloniaProperty.Register<MergeConflictTextPresenter, Models.ConflictPanelType>(nameof(PanelType));
public Models.ConflictPanelType PanelType
{
@@ -43,17 +214,17 @@ namespace SourceGit.Views
set => SetValue(PanelTypeProperty, value);
}
public static readonly StyledProperty<List<Models.TextDiffLine>> DiffLinesProperty =
AvaloniaProperty.Register<MergeDiffPresenter, List<Models.TextDiffLine>>(nameof(DiffLines));
public static readonly StyledProperty<List<Models.ConflictLine>> LinesProperty =
AvaloniaProperty.Register<MergeConflictTextPresenter, List<Models.ConflictLine>>(nameof(Lines));
public List<Models.TextDiffLine> DiffLines
public List<Models.ConflictLine> Lines
{
get => GetValue(DiffLinesProperty);
set => SetValue(DiffLinesProperty, value);
get => GetValue(LinesProperty);
set => SetValue(LinesProperty, value);
}
public static readonly StyledProperty<int> MaxLineNumberProperty =
AvaloniaProperty.Register<MergeDiffPresenter, int>(nameof(MaxLineNumber));
AvaloniaProperty.Register<MergeConflictTextPresenter, int>(nameof(MaxLineNumber));
public int MaxLineNumber
{
@@ -61,44 +232,26 @@ namespace SourceGit.Views
set => SetValue(MaxLineNumberProperty, value);
}
public static readonly StyledProperty<IBrush> EmptyContentBackgroundProperty =
AvaloniaProperty.Register<MergeDiffPresenter, IBrush>(nameof(EmptyContentBackground), new SolidColorBrush(Color.FromArgb(60, 0, 0, 0)));
public static readonly StyledProperty<IBrush> OursContentBackgroundProperty =
AvaloniaProperty.Register<MergeConflictTextPresenter, IBrush>(nameof(OursContentBackground), new SolidColorBrush(Color.FromArgb(60, 0, 255, 0)));
public IBrush EmptyContentBackground
public IBrush OursContentBackground
{
get => GetValue(EmptyContentBackgroundProperty);
set => SetValue(EmptyContentBackgroundProperty, value);
get => GetValue(OursContentBackgroundProperty);
set => SetValue(OursContentBackgroundProperty, value);
}
public static readonly StyledProperty<IBrush> AddedContentBackgroundProperty =
AvaloniaProperty.Register<MergeDiffPresenter, IBrush>(nameof(AddedContentBackground), new SolidColorBrush(Color.FromArgb(60, 0, 255, 0)));
public static readonly StyledProperty<IBrush> TheirsContentBackgroundProperty =
AvaloniaProperty.Register<MergeConflictTextPresenter, IBrush>(nameof(TheirsContentBackground), new SolidColorBrush(Color.FromArgb(60, 255, 0, 0)));
public IBrush AddedContentBackground
public IBrush TheirsContentBackground
{
get => GetValue(AddedContentBackgroundProperty);
set => SetValue(AddedContentBackgroundProperty, value);
}
public static readonly StyledProperty<IBrush> DeletedContentBackgroundProperty =
AvaloniaProperty.Register<MergeDiffPresenter, IBrush>(nameof(DeletedContentBackground), new SolidColorBrush(Color.FromArgb(60, 255, 0, 0)));
public IBrush DeletedContentBackground
{
get => GetValue(DeletedContentBackgroundProperty);
set => SetValue(DeletedContentBackgroundProperty, value);
}
public static readonly StyledProperty<IBrush> IndicatorBackgroundProperty =
AvaloniaProperty.Register<MergeDiffPresenter, IBrush>(nameof(IndicatorBackground), new SolidColorBrush(Color.FromArgb(100, 100, 100, 100)));
public IBrush IndicatorBackground
{
get => GetValue(IndicatorBackgroundProperty);
set => SetValue(IndicatorBackgroundProperty, value);
get => GetValue(TheirsContentBackgroundProperty);
set => SetValue(TheirsContentBackgroundProperty, value);
}
public static readonly StyledProperty<Models.ConflictSelectedChunk> SelectedChunkProperty =
AvaloniaProperty.Register<MergeDiffPresenter, Models.ConflictSelectedChunk>(nameof(SelectedChunk));
AvaloniaProperty.Register<MergeConflictTextPresenter, Models.ConflictSelectedChunk>(nameof(SelectedChunk));
public Models.ConflictSelectedChunk SelectedChunk
{
@@ -106,10 +259,10 @@ namespace SourceGit.Views
set => SetValue(SelectedChunkProperty, value);
}
public static readonly StyledProperty<ViewModels.TextDiffDisplayRange> DisplayRangeProperty =
AvaloniaProperty.Register<MergeDiffPresenter, ViewModels.TextDiffDisplayRange>(nameof(DisplayRange));
public static readonly StyledProperty<ViewModels.TextLineRange> DisplayRangeProperty =
AvaloniaProperty.Register<MergeConflictTextPresenter, ViewModels.TextLineRange>(nameof(DisplayRange));
public ViewModels.TextDiffDisplayRange DisplayRange
public ViewModels.TextLineRange DisplayRange
{
get => GetValue(DisplayRangeProperty);
set => SetValue(DisplayRangeProperty, value);
@@ -117,7 +270,7 @@ namespace SourceGit.Views
protected override Type StyleKeyOverride => typeof(TextEditor);
public MergeDiffPresenter() : base(new TextArea(), new TextDocument())
public MergeConflictTextPresenter() : base(new TextArea(), new TextDocument())
{
IsReadOnly = true;
ShowLineNumbers = false;
@@ -128,9 +281,9 @@ namespace SourceGit.Views
Options.AllowScrollBelowDocument = false;
TextArea.TextView.Margin = new Thickness(4, 0);
TextArea.LeftMargins.Add(new MergeDiffLineNumberMargin(this));
TextArea.LeftMargins.Add(new MergeDiffVerticalSeparatorMargin());
TextArea.TextView.BackgroundRenderers.Add(new MergeDiffLineBackgroundRenderer(this));
TextArea.LeftMargins.Add(new LineNumberMargin(this));
TextArea.LeftMargins.Add(new VerticalSeparatorMargin());
TextArea.TextView.BackgroundRenderers.Add(new LineBackgroundRenderer(this));
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
@@ -158,7 +311,7 @@ namespace SourceGit.Views
TextArea.TextView.PointerMoved += OnTextViewPointerChanged;
TextArea.TextView.PointerWheelChanged += OnTextViewPointerWheelChanged;
TextArea.TextView.VisualLinesChanged += OnTextViewVisualLinesChanged;
TextArea.TextView.LineTransformers.Add(new MergeDiffIndicatorTransformer(this));
TextArea.TextView.LineTransformers.Add(new ConflictMarkerTransformer(this));
OnTextViewVisualLinesChanged(null, null);
}
@@ -184,7 +337,7 @@ namespace SourceGit.Views
{
base.OnPropertyChanged(change);
if (change.Property == DiffLinesProperty)
if (change.Property == LinesProperty)
UpdateContent();
else if (change.Property == FileNameProperty)
Models.TextMateHelper.SetGrammarByFileName(_textMate, FileName);
@@ -196,7 +349,7 @@ namespace SourceGit.Views
private void UpdateContent()
{
var lines = DiffLines;
var lines = Lines;
if (lines == null || lines.Count == 0)
{
Text = string.Empty;
@@ -278,7 +431,7 @@ namespace SourceGit.Views
return;
}
var lines = DiffLines;
var lines = Lines;
var start = int.MaxValue;
var count = 0;
foreach (var line in TextArea.TextView.VisualLines)
@@ -295,7 +448,7 @@ namespace SourceGit.Views
start = index;
}
SetCurrentValue(DisplayRangeProperty, new ViewModels.TextDiffDisplayRange(start, start + count));
SetCurrentValue(DisplayRangeProperty, new ViewModels.TextLineRange(start, start + count));
}
private void OnTextViewScrollChanged(object sender, ScrollChangedEventArgs e)
@@ -317,7 +470,7 @@ namespace SourceGit.Views
private void UpdateSelectedChunkPosition(ViewModels.MergeConflictEditor vm, double y)
{
var lines = DiffLines;
var lines = Lines;
var panel = PanelType;
var view = TextArea.TextView;
var lineIdx = -1;
@@ -380,202 +533,12 @@ namespace SourceGit.Views
private ScrollViewer _scrollViewer;
}
public class MergeDiffLineNumberMargin : AbstractMargin
{
public MergeDiffLineNumberMargin(MergeDiffPresenter presenter)
{
_presenter = presenter;
Margin = new Thickness(8, 0);
ClipToBounds = true;
}
public override void Render(DrawingContext context)
{
var lines = _presenter.DiffLines;
if (lines == null)
return;
var view = TextView;
if (view is not { VisualLinesValid: true })
return;
var panel = _presenter.PanelType;
var typeface = view.CreateTypeface();
foreach (var line in view.VisualLines)
{
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
continue;
var index = line.FirstDocumentLine.LineNumber;
if (index > lines.Count)
break;
var info = lines[index - 1];
string lineNumber = panel switch
{
Models.ConflictPanelType.Mine => info.OldLine,
_ => info.NewLine,
};
if (string.IsNullOrEmpty(lineNumber))
continue;
var y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.LineMiddle) - view.VerticalOffset;
var txt = new FormattedText(
lineNumber,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
typeface,
_presenter.FontSize,
_presenter.Foreground);
context.DrawText(txt, new Point(Bounds.Width - txt.Width, y - (txt.Height * 0.5)));
}
}
protected override Size MeasureOverride(Size availableSize)
{
var maxLine = _presenter.MaxLineNumber;
if (maxLine == 0)
return new Size(32, 0);
var typeface = TextView.CreateTypeface();
var test = new FormattedText(
$"{maxLine}",
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
typeface,
_presenter.FontSize,
Brushes.White);
return new Size(test.Width, 0);
}
private readonly MergeDiffPresenter _presenter;
}
public class MergeDiffVerticalSeparatorMargin : AbstractMargin
{
public override void Render(DrawingContext context)
{
var pen = new Pen(Brushes.DarkGray);
context.DrawLine(pen, new Point(0, 0), new Point(0, Bounds.Height));
}
protected override Size MeasureOverride(Size availableSize)
{
return new Size(1, 0);
}
}
public class MergeDiffIndicatorTransformer : DocumentColorizingTransformer
{
public MergeDiffIndicatorTransformer(MergeDiffPresenter presenter)
{
_presenter = presenter;
}
protected override void ColorizeLine(DocumentLine line)
{
var lines = _presenter.DiffLines;
if (lines == null || line.LineNumber > lines.Count)
return;
var info = lines[line.LineNumber - 1];
if (info.Type == Models.TextDiffLineType.Indicator)
{
// Make indicator lines (conflict markers) italic and gray
ChangeLinePart(line.Offset, line.EndOffset, element =>
{
element.TextRunProperties.SetTypeface(new Typeface(
_presenter.FontFamily,
FontStyle.Italic,
FontWeight.Normal));
element.TextRunProperties.SetForegroundBrush(Brushes.Gray);
});
}
}
private readonly MergeDiffPresenter _presenter;
}
public class MergeDiffLineBackgroundRenderer : IBackgroundRenderer
{
public KnownLayer Layer => KnownLayer.Background;
public MergeDiffLineBackgroundRenderer(MergeDiffPresenter presenter)
{
_presenter = presenter;
}
public void Draw(TextView textView, DrawingContext drawingContext)
{
var lines = _presenter.DiffLines;
if (lines == null || _presenter.Document == null || !textView.VisualLinesValid)
return;
if (_presenter.DataContext is not ViewModels.MergeConflictEditor vm)
return;
var width = textView.Bounds.Width;
foreach (var line in textView.VisualLines)
{
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
continue;
var index = line.FirstDocumentLine.LineNumber;
if (index > lines.Count)
break;
var lineIndex = index - 1;
var info = lines[lineIndex];
var lineState = vm.GetLineState(lineIndex);
var startY = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.LineTop) - textView.VerticalOffset;
var endY = line.GetTextLineVisualYPosition(line.TextLines[^1], VisualYPosition.LineBottom) - textView.VerticalOffset;
var rect = new Rect(0, startY, width, endY - startY);
if (lineState == Models.ConflictLineState.ConflictBlockStart)
drawingContext.DrawLine(new Pen(new SolidColorBrush(Colors.Red, 0.6)), new Point(0, startY + 0.5), new Point(width, startY + 0.5));
else if (lineState == Models.ConflictLineState.ConflictBlockEnd)
drawingContext.DrawLine(new Pen(new SolidColorBrush(Colors.Red, 0.6)), new Point(0, endY - 0.5), new Point(width, endY - 0.5));
else if (lineState == Models.ConflictLineState.ResolvedBlockStart)
drawingContext.DrawLine(new Pen(new SolidColorBrush(Colors.Green, 0.6)), new Point(0, startY + 0.5), new Point(width, startY + 0.5));
else if (lineState == Models.ConflictLineState.ResolvedBlockEnd)
drawingContext.DrawLine(new Pen(new SolidColorBrush(Colors.Green, 0.6)), new Point(0, endY - 0.5), new Point(width, endY - 0.5));
if (lineState >= Models.ConflictLineState.ResolvedBlockStart)
drawingContext.DrawRectangle(new SolidColorBrush(Colors.Green, 0.1), null, rect);
else if (lineState >= Models.ConflictLineState.ConflictBlockStart)
drawingContext.DrawRectangle(new SolidColorBrush(Colors.Red, 0.1), null, rect);
var bg = GetBrushByLineType(info.Type);
if (bg != null)
drawingContext.DrawRectangle(bg, null, rect);
}
}
private IBrush GetBrushByLineType(Models.TextDiffLineType type)
{
return type switch
{
Models.TextDiffLineType.None => _presenter.EmptyContentBackground,
Models.TextDiffLineType.Added => _presenter.AddedContentBackground,
Models.TextDiffLineType.Deleted => _presenter.DeletedContentBackground,
Models.TextDiffLineType.Indicator => _presenter.IndicatorBackground,
_ => null,
};
}
private readonly MergeDiffPresenter _presenter;
}
public class MergeConflictMinimap : Control
{
public static readonly StyledProperty<ViewModels.TextDiffDisplayRange> DisplayRangeProperty =
AvaloniaProperty.Register<MergeConflictMinimap, ViewModels.TextDiffDisplayRange>(nameof(DisplayRange));
public static readonly StyledProperty<ViewModels.TextLineRange> DisplayRangeProperty =
AvaloniaProperty.Register<MergeConflictMinimap, ViewModels.TextLineRange>(nameof(DisplayRange));
public ViewModels.TextDiffDisplayRange DisplayRange
public ViewModels.TextLineRange DisplayRange
{
get => GetValue(DisplayRangeProperty);
set => SetValue(DisplayRangeProperty, value);
@@ -829,9 +792,9 @@ namespace SourceGit.Views
return;
// Get the presenter for bounds checking
MergeDiffPresenter presenter = chunk.Panel switch
MergeConflictTextPresenter presenter = chunk.Panel switch
{
Models.ConflictPanelType.Mine => OursPresenter,
Models.ConflictPanelType.Ours => OursPresenter,
Models.ConflictPanelType.Theirs => TheirsPresenter,
Models.ConflictPanelType.Result => ResultPresenter,
_ => null
@@ -840,7 +803,7 @@ namespace SourceGit.Views
// Show the appropriate popup based on panel type and resolved state
Border popup = chunk.Panel switch
{
Models.ConflictPanelType.Mine => MinePopup,
Models.ConflictPanelType.Ours => MinePopup,
Models.ConflictPanelType.Theirs => TheirsPopup,
Models.ConflictPanelType.Result => chunk.IsResolved ? ResultUndoPopup : ResultPopup,
_ => null

View File

@@ -731,7 +731,7 @@ namespace SourceGit.Views
start = index;
}
ctx.DisplayRange = new ViewModels.TextDiffDisplayRange(start, start + count);
ctx.DisplayRange = new ViewModels.TextLineRange(start, start + count);
}
protected void TrySetChunk(ViewModels.TextDiffSelectedChunk chunk)
@@ -1280,10 +1280,10 @@ namespace SourceGit.Views
set => SetValue(DeletedLineBrushProperty, value);
}
public static readonly StyledProperty<ViewModels.TextDiffDisplayRange> DisplayRangeProperty =
AvaloniaProperty.Register<TextDiffViewMinimap, ViewModels.TextDiffDisplayRange>(nameof(DisplayRange));
public static readonly StyledProperty<ViewModels.TextLineRange> DisplayRangeProperty =
AvaloniaProperty.Register<TextDiffViewMinimap, ViewModels.TextLineRange>(nameof(DisplayRange));
public ViewModels.TextDiffDisplayRange DisplayRange
public ViewModels.TextLineRange DisplayRange
{
get => GetValue(DisplayRangeProperty);
set => SetValue(DisplayRangeProperty, value);