refactor: move data-only structures and enums to SourceGit.Models

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo
2026-01-27 18:20:07 +08:00
parent 4516b0020c
commit 89675c3dac
4 changed files with 127 additions and 175 deletions

79
src/Models/Conflict.cs Normal file
View File

@@ -0,0 +1,79 @@
using System.Collections.Generic;
namespace SourceGit.Models
{
public enum ConflictResolution
{
None,
UseOurs,
UseTheirs,
UseBothMineFirst,
UseBothTheirsFirst,
}
public enum ConflictMarkerType
{
Start, // <<<<<<<
Base, // ||||||| (diff3 style)
Separator, // =======
End, // >>>>>>>
}
public enum ConflictPanelType
{
Mine,
Theirs,
Result
}
public enum ConflictLineState
{
Normal,
ConflictBlockStart,
ConflictBlock,
ConflictBlockEnd,
ResolvedBlockStart,
ResolvedBlock,
ResolvedBlockEnd,
}
public record ConflictSelectedChunk(
double Y,
double Height,
int ConflictIndex,
ConflictPanelType Panel,
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; }
public int EndLineInOriginal { get; set; }
public List<string> OursContent { get; set; } = new();
public List<string> TheirsContent { get; set; } = new();
public bool IsResolved { get; set; } = false;
// Line indices in the built static panels (0-based)
public int PanelStartLine { get; set; } = -1;
public int PanelEndLine { get; set; } = -1;
// Content chosen when resolved (null = unresolved, empty list = deleted)
public List<string> ResolvedContent { get; set; } = null;
// Real markers from the file
public string StartMarker { get; set; } = "<<<<<<<";
public string SeparatorMarker { get; set; } = "=======";
public string EndMarker { get; set; } = ">>>>>>>";
// Track the type of resolution
public ConflictResolution ResolutionType { get; set; } = ConflictResolution.None;
}
}

View File

@@ -1,67 +0,0 @@
using System.Collections.Generic;
namespace SourceGit.Models
{
public enum ConflictResolution
{
None,
UseOurs,
UseTheirs,
UseBothMineFirst,
UseBothTheirsFirst,
}
public class MergeConflictRegion
{
public int StartLine { get; set; } = 0;
public int EndLine { get; set; } = 0;
public bool IsConflict { get; set; } = false;
public ConflictResolution Resolution { get; set; } = ConflictResolution.None;
public string BaseContent { get; set; } = string.Empty;
public string OursContent { get; set; } = string.Empty;
public string TheirsContent { get; set; } = string.Empty;
}
public class MergeConflictDocument
{
public string BaseContent { get; set; } = string.Empty;
public string OursContent { get; set; } = string.Empty;
public string TheirsContent { get; set; } = string.Empty;
public string ResultContent { get; set; } = string.Empty;
public List<MergeConflictRegion> Regions { get; set; } = new List<MergeConflictRegion>();
public int UnresolvedConflictCount
{
get
{
int count = 0;
foreach (var region in Regions)
{
if (region.IsConflict && region.Resolution == ConflictResolution.None)
count++;
}
return count;
}
}
public bool HasUnresolvedConflicts => UnresolvedConflictCount > 0;
}
public class ConflictMarkerInfo
{
public int LineNumber { get; set; }
public int StartOffset { get; set; }
public int EndOffset { get; set; }
public ConflictMarkerType Type { get; set; }
}
public enum ConflictMarkerType
{
Start, // <<<<<<<
Base, // ||||||| (diff3 style)
Separator, // =======
End, // >>>>>>>
}
}

View File

@@ -9,57 +9,6 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
{
public enum MergeConflictPanelType
{
Mine,
Theirs,
Result
}
public enum MergeConflictLineType
{
Normal,
ConflictBlockStart,
ConflictBlock,
ConflictBlockEnd,
ResolvedBlockStart,
ResolvedBlock,
ResolvedBlockEnd,
}
public record MergeConflictSelectedChunk(
double Y,
double Height,
int ConflictIndex,
MergeConflictPanelType Panel,
bool IsResolved
);
// Represents a single conflict region with its original content and panel positions
public class ConflictRegion
{
public int StartLineInOriginal { get; set; }
public int EndLineInOriginal { get; set; }
public List<string> OursContent { get; set; } = new();
public List<string> TheirsContent { get; set; } = new();
public bool IsResolved { get; set; } = false;
// Line indices in the built static panels (0-based)
public int PanelStartLine { get; set; } = -1;
public int PanelEndLine { get; set; } = -1;
// Content chosen when resolved (null = unresolved, empty list = deleted)
public List<string> ResolvedContent { get; set; } = null;
// Real markers from the file
public string StartMarker { get; set; } = "<<<<<<<";
public string SeparatorMarker { get; set; } = "=======";
public string EndMarker { get; set; } = ">>>>>>>";
// Track the type of resolution
public Models.ConflictResolution ResolutionType { get; set; } = Models.ConflictResolution.None;
}
public class MergeConflictEditor : ObservableObject
{
public string FilePath
@@ -139,13 +88,13 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _scrollOffset, value);
}
public MergeConflictSelectedChunk SelectedChunk
public Models.ConflictSelectedChunk SelectedChunk
{
get => _selectedChunk;
set => SetProperty(ref _selectedChunk, value);
}
public IReadOnlyList<ConflictRegion> ConflictRegions => _conflictRegions;
public IReadOnlyList<Models.ConflictRegion> ConflictRegions => _conflictRegions;
public bool HasUnresolvedConflicts => _unresolvedConflictCount > 0;
public bool HasUnsavedChanges => _isModified && !_resultContent.Equals(_originalContent, StringComparison.Ordinal);
public bool CanSave => _unresolvedConflictCount == 0 && _isModified;
@@ -193,11 +142,11 @@ namespace SourceGit.ViewModels
}
}
public MergeConflictLineType GetLineType(int line)
public Models.ConflictLineState GetLineState(int line)
{
if (line >= 0 && line < _lineTypes.Count)
return _lineTypes[line];
return MergeConflictLineType.Normal;
if (line >= 0 && line < _lineStates.Count)
return _lineStates[line];
return Models.ConflictLineState.Normal;
}
public void AcceptOursAtIndex(int conflictIndex)
@@ -380,7 +329,7 @@ namespace SourceGit.ViewModels
if (line.StartsWith("<<<<<<<", StringComparison.Ordinal))
{
var region = new ConflictRegion { StartLineInOriginal = i };
var region = new Models.ConflictRegion { StartLineInOriginal = i };
// Capture the start marker (e.g., "<<<<<<< HEAD")
region.StartMarker = line;
i++;
@@ -554,7 +503,7 @@ namespace SourceGit.ViewModels
// Build RESULT panel aligned with MINE/THEIRS panels
// This ensures all three panels have the same number of lines for scroll sync
var resultLines = new List<Models.TextDiffLine>();
_lineTypes.Clear();
_lineStates.Clear();
if (_oursDiffLines == null || _oursDiffLines.Count == 0)
{
@@ -569,7 +518,7 @@ namespace SourceGit.ViewModels
while (currentLine < _oursDiffLines.Count)
{
// Check if we're at a conflict region
ConflictRegion currentRegion = null;
Models.ConflictRegion currentRegion = null;
if (conflictIdx < _conflictRegions.Count)
{
var region = _conflictRegions[conflictIdx];
@@ -636,41 +585,41 @@ namespace SourceGit.ViewModels
resultLines.Add(new Models.TextDiffLine());
int added = resultLines.Count - oldLineCount;
_lineTypes.Add(MergeConflictLineType.ResolvedBlockStart);
_lineStates.Add(Models.ConflictLineState.ResolvedBlockStart);
for (var i = 0; i < added - 2; i++)
_lineTypes.Add(MergeConflictLineType.ResolvedBlock);
_lineTypes.Add(MergeConflictLineType.ResolvedBlockEnd);
_lineStates.Add(Models.ConflictLineState.ResolvedBlock);
_lineStates.Add(Models.ConflictLineState.ResolvedBlockEnd);
}
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));
_lineTypes.Add(MergeConflictLineType.ConflictBlockStart);
_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++));
_lineTypes.Add(MergeConflictLineType.ConflictBlock);
_lineStates.Add(Models.ConflictLineState.ConflictBlock);
}
// Separator marker between Mine and Theirs
resultLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, currentRegion.SeparatorMarker, 0, 0));
_lineTypes.Add(MergeConflictLineType.ConflictBlock);
_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++));
_lineTypes.Add(MergeConflictLineType.ConflictBlock);
_lineStates.Add(Models.ConflictLineState.ConflictBlock);
}
// End marker (use real marker from file)
resultLines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, currentRegion.EndMarker, 0, 0));
_lineTypes.Add(MergeConflictLineType.ConflictBlockEnd);
_lineStates.Add(Models.ConflictLineState.ConflictBlockEnd);
}
currentLine = currentRegion.PanelEndLine + 1;
@@ -693,7 +642,7 @@ namespace SourceGit.ViewModels
resultLines.Add(new Models.TextDiffLine());
}
_lineTypes.Add(MergeConflictLineType.Normal);
_lineStates.Add(Models.ConflictLineState.Normal);
currentLine++;
}
}
@@ -852,7 +801,7 @@ namespace SourceGit.ViewModels
if (line.StartsWith("<<<<<<<", StringComparison.Ordinal))
{
// Get the current conflict region
ConflictRegion region = null;
Models.ConflictRegion region = null;
if (conflictIdx < _conflictRegions.Count)
region = _conflictRegions[conflictIdx];
@@ -965,14 +914,14 @@ namespace SourceGit.ViewModels
private bool _isModified = false;
private int _unresolvedConflictCount = 0;
private int _currentConflictIndex = -1;
private int _diffMaxLineNumber = 0;
private List<Models.TextDiffLine> _oursDiffLines = [];
private List<Models.TextDiffLine> _theirsDiffLines = [];
private List<Models.TextDiffLine> _resultDiffLines = [];
private int _diffMaxLineNumber = 0;
private List<ConflictRegion> _conflictRegions = [];
private List<MergeConflictLineType> _lineTypes = [];
private List<Models.ConflictRegion> _conflictRegions = [];
private List<Models.ConflictLineState> _lineStates = [];
private Vector _scrollOffset = Vector.Zero;
private MergeConflictSelectedChunk _selectedChunk;
private Models.ConflictSelectedChunk _selectedChunk;
private string _error = string.Empty;
}
}

View File

@@ -113,24 +113,24 @@ namespace SourceGit.Views
set => SetValue(CurrentConflictIndexProperty, value);
}
public static readonly StyledProperty<ViewModels.MergeConflictSelectedChunk> SelectedChunkProperty =
AvaloniaProperty.Register<MergeDiffPresenter, ViewModels.MergeConflictSelectedChunk>(nameof(SelectedChunk));
public static readonly StyledProperty<Models.ConflictSelectedChunk> SelectedChunkProperty =
AvaloniaProperty.Register<MergeDiffPresenter, Models.ConflictSelectedChunk>(nameof(SelectedChunk));
public ViewModels.MergeConflictSelectedChunk SelectedChunk
public Models.ConflictSelectedChunk SelectedChunk
{
get => GetValue(SelectedChunkProperty);
set => SetValue(SelectedChunkProperty, value);
}
protected ViewModels.MergeConflictPanelType PanelType
protected Models.ConflictPanelType PanelType
{
get
{
if (IsResultPanel)
return ViewModels.MergeConflictPanelType.Result;
return Models.ConflictPanelType.Result;
if (IsOldSide)
return ViewModels.MergeConflictPanelType.Mine;
return ViewModels.MergeConflictPanelType.Theirs;
return Models.ConflictPanelType.Mine;
return Models.ConflictPanelType.Theirs;
}
}
@@ -365,7 +365,7 @@ namespace SourceGit.Views
if (isWithinRegion)
{
var newChunk = new ViewModels.MergeConflictSelectedChunk(
var newChunk = new Models.ConflictSelectedChunk(
viewportY, height, i, panelType, region.IsResolved);
// Only update if changed
@@ -505,7 +505,7 @@ namespace SourceGit.Views
}
// Update chunk with new position
var newChunk = new ViewModels.MergeConflictSelectedChunk(
var newChunk = new Models.ConflictSelectedChunk(
viewportY, height, chunk.ConflictIndex, panelType, region.IsResolved);
if (Math.Abs(chunk.Y - newChunk.Y) > 1 || Math.Abs(chunk.Height - newChunk.Height) > 1)
@@ -668,24 +668,24 @@ namespace SourceGit.Views
var lineIndex = index - 1;
var info = lines[lineIndex];
var lineType = vm.GetLineType(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 (lineType == ViewModels.MergeConflictLineType.ConflictBlockStart)
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 (lineType == ViewModels.MergeConflictLineType.ConflictBlockEnd)
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 (lineType == ViewModels.MergeConflictLineType.ResolvedBlockStart)
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 (lineType == ViewModels.MergeConflictLineType.ResolvedBlockEnd)
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 (lineType >= ViewModels.MergeConflictLineType.ResolvedBlockStart)
if (lineState >= Models.ConflictLineState.ResolvedBlockStart)
drawingContext.DrawRectangle(new SolidColorBrush(Colors.Green, 0.1), null, rect);
else if (lineType >= ViewModels.MergeConflictLineType.ConflictBlockStart)
else if (lineState >= Models.ConflictLineState.ConflictBlockStart)
drawingContext.DrawRectangle(new SolidColorBrush(Colors.Red, 0.1), null, rect);
var bg = GetBrushByLineType(info.Type);
@@ -898,29 +898,20 @@ namespace SourceGit.Views
// Get the presenter for bounds checking
MergeDiffPresenter presenter = chunk.Panel switch
{
ViewModels.MergeConflictPanelType.Mine => OursPresenter,
ViewModels.MergeConflictPanelType.Theirs => TheirsPresenter,
ViewModels.MergeConflictPanelType.Result => ResultPresenter,
Models.ConflictPanelType.Mine => OursPresenter,
Models.ConflictPanelType.Theirs => TheirsPresenter,
Models.ConflictPanelType.Result => ResultPresenter,
_ => null
};
// Show the appropriate popup based on panel type and resolved state
Border popup;
if (chunk.Panel == ViewModels.MergeConflictPanelType.Result && chunk.IsResolved)
Border popup = chunk.Panel switch
{
// Show Undo popup for resolved conflicts in Result panel
popup = ResultUndoPopup;
}
else
{
popup = chunk.Panel switch
{
ViewModels.MergeConflictPanelType.Mine => MinePopup,
ViewModels.MergeConflictPanelType.Theirs => TheirsPopup,
ViewModels.MergeConflictPanelType.Result => ResultPopup,
_ => null
};
}
Models.ConflictPanelType.Mine => MinePopup,
Models.ConflictPanelType.Theirs => TheirsPopup,
Models.ConflictPanelType.Result => chunk.IsResolved ? ResultUndoPopup : ResultPopup,
_ => null
};
if (popup != null && presenter != null)
{