diff --git a/src/Models/Conflict.cs b/src/Models/Conflict.cs new file mode 100644 index 00000000..509f633e --- /dev/null +++ b/src/Models/Conflict.cs @@ -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 OursContent { get; set; } = new(); + public List 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 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; + } +} diff --git a/src/Models/MergeConflict.cs b/src/Models/MergeConflict.cs deleted file mode 100644 index 939c761a..00000000 --- a/src/Models/MergeConflict.cs +++ /dev/null @@ -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 Regions { get; set; } = new List(); - - 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, // >>>>>>> - } -} diff --git a/src/ViewModels/MergeConflictEditor.cs b/src/ViewModels/MergeConflictEditor.cs index c53fb3c0..694ac776 100644 --- a/src/ViewModels/MergeConflictEditor.cs +++ b/src/ViewModels/MergeConflictEditor.cs @@ -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 OursContent { get; set; } = new(); - public List 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 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 ConflictRegions => _conflictRegions; + public IReadOnlyList 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(); - _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 _oursDiffLines = []; private List _theirsDiffLines = []; private List _resultDiffLines = []; - private int _diffMaxLineNumber = 0; - private List _conflictRegions = []; - private List _lineTypes = []; + private List _conflictRegions = []; + private List _lineStates = []; private Vector _scrollOffset = Vector.Zero; - private MergeConflictSelectedChunk _selectedChunk; + private Models.ConflictSelectedChunk _selectedChunk; private string _error = string.Empty; } } diff --git a/src/Views/MergeConflictEditor.axaml.cs b/src/Views/MergeConflictEditor.axaml.cs index 2f65326c..712efb7a 100644 --- a/src/Views/MergeConflictEditor.axaml.cs +++ b/src/Views/MergeConflictEditor.axaml.cs @@ -113,24 +113,24 @@ namespace SourceGit.Views set => SetValue(CurrentConflictIndexProperty, value); } - public static readonly StyledProperty SelectedChunkProperty = - AvaloniaProperty.Register(nameof(SelectedChunk)); + public static readonly StyledProperty SelectedChunkProperty = + AvaloniaProperty.Register(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) {