mirror of
https://fastgit.cc/github.com/sourcegit-scm/sourcegit
synced 2026-04-21 13:20:30 +08:00
code_review: PR #2070
- Remove unused code - Add translations for Chinese (Simplified & Traditional) - UI/UX changes - Fix sync-scroll function sometimes does not work - Re-arrange context menu items for local change Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
@@ -7,10 +7,8 @@ namespace SourceGit.Models
|
||||
None,
|
||||
UseOurs,
|
||||
UseTheirs,
|
||||
UseBoth,
|
||||
UseBothMineFirst,
|
||||
UseBothTheirsFirst,
|
||||
Manual,
|
||||
}
|
||||
|
||||
public class MergeConflictRegion
|
||||
|
||||
@@ -93,6 +93,8 @@
|
||||
<x:String x:Key="Text.ChangeCM.CheckoutFirstParentRevision" xml:space="preserve">Reset to Parent Revision</x:String>
|
||||
<x:String x:Key="Text.ChangeCM.CheckoutThisRevision" xml:space="preserve">Reset to This Revision</x:String>
|
||||
<x:String x:Key="Text.ChangeCM.GenerateCommitMessage" xml:space="preserve">Generate commit message</x:String>
|
||||
<x:String x:Key="Text.ChangeCM.Merge" xml:space="preserve">Merge (Built-in)</x:String>
|
||||
<x:String x:Key="Text.ChangeCM.MergeExternal" xml:space="preserve">Merge (External)</x:String>
|
||||
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">CHANGE DISPLAY MODE</x:String>
|
||||
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">Show as File and Dir List</x:String>
|
||||
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Show as Path List</x:String>
|
||||
@@ -536,6 +538,24 @@
|
||||
<x:String x:Key="Text.Merge.Into" xml:space="preserve">Into:</x:String>
|
||||
<x:String x:Key="Text.Merge.Mode" xml:space="preserve">Merge Option:</x:String>
|
||||
<x:String x:Key="Text.Merge.Source" xml:space="preserve">Source:</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.MineFirst" xml:space="preserve">First Mine, then Theirs</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.TheirsFirst" xml:space="preserve">First Theirs, then Mine</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UseBoth" xml:space="preserve">USE BOTH</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.AllResolved" xml:space="preserve">All conflicts resolved</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.ConflictsRemaining" xml:space="preserve">{0} conflict(s) remaining</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Mine" xml:space="preserve">MINE</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.NextConflict" xml:space="preserve">Next Conflict</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.PrevConflict" xml:space="preserve">Previous Conflict</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Result" xml:space="preserve">RESULT</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.SaveAndStage" xml:space="preserve">SAVE & STAGE</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Theirs" xml:space="preserve">THEIRS</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Title" xml:space="preserve">Merge Conflicts</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UnsavedChanges" xml:space="preserve">Discard unsaved changes?</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UseMine" xml:space="preserve">USE MINE</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UseMine.Tip" xml:space="preserve">Resolve current conflict using Mine version</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UseTheirs" xml:space="preserve">USE THEIRS</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UseTheirs.Tip" xml:space="preserve">Resolve current conflict using Theirs version</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Undo" xml:space="preserve">UNDO</x:String>
|
||||
<x:String x:Key="Text.MergeMultiple" xml:space="preserve">Merge (Multiple)</x:String>
|
||||
<x:String x:Key="Text.MergeMultiple.CommitChanges" xml:space="preserve">Commit all changes</x:String>
|
||||
<x:String x:Key="Text.MergeMultiple.Strategy" xml:space="preserve">Strategy:</x:String>
|
||||
@@ -552,8 +572,7 @@
|
||||
<x:String x:Key="Text.Open.SystemDefaultEditor" xml:space="preserve">Default Editor (System)</x:String>
|
||||
<x:String x:Key="Text.OpenAppDataDir" xml:space="preserve">Open Data Storage Directory</x:String>
|
||||
<x:String x:Key="Text.OpenFile" xml:space="preserve">Open File</x:String>
|
||||
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">Open in Merge Tool</x:String>
|
||||
<x:String x:Key="Text.OpenInBuiltinMergeTool" xml:space="preserve">Open in Built-in Merge Tool</x:String>
|
||||
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">Open in External Merge Tool</x:String>
|
||||
<x:String x:Key="Text.Optional" xml:space="preserve">Optional.</x:String>
|
||||
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">Create New Tab</x:String>
|
||||
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">Bookmark</x:String>
|
||||
@@ -875,24 +894,6 @@
|
||||
<x:String x:Key="Text.TagCM.DeleteMultiple" xml:space="preserve">Delete selected {0} tags...</x:String>
|
||||
<x:String x:Key="Text.TagCM.Merge" xml:space="preserve">Merge ${0}$ into ${1}$...</x:String>
|
||||
<x:String x:Key="Text.TagCM.Push" xml:space="preserve">Push ${0}$...</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.MineFirst" xml:space="preserve">First Mine, then Theirs</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.TheirsFirst" xml:space="preserve">First Theirs, then Mine</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UseBoth" xml:space="preserve">USE BOTH</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.AllResolved" xml:space="preserve">All conflicts resolved</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.ConflictsRemaining" xml:space="preserve">{0} conflict(s) remaining</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Mine" xml:space="preserve">MINE</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.NextConflict" xml:space="preserve">Next Conflict</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.PrevConflict" xml:space="preserve">Previous Conflict</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Result" xml:space="preserve">RESULT</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.SaveAndStage" xml:space="preserve">SAVE & STAGE</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Theirs" xml:space="preserve">THEIRS</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Title" xml:space="preserve">Merge Conflict - {0}</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UnsavedChanges" xml:space="preserve">Discard unsaved changes?</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UseMine" xml:space="preserve">USE MINE</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UseMine.Tip" xml:space="preserve">Resolve current conflict using Mine version</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UseTheirs" xml:space="preserve">USE THEIRS</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UseTheirs.Tip" xml:space="preserve">Resolve current conflict using Theirs version</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Undo" xml:space="preserve">UNDO</x:String>
|
||||
<x:String x:Key="Text.UpdateSubmodules" xml:space="preserve">Update Submodules</x:String>
|
||||
<x:String x:Key="Text.UpdateSubmodules.All" xml:space="preserve">All submodules</x:String>
|
||||
<x:String x:Key="Text.UpdateSubmodules.Init" xml:space="preserve">Initialize as needed</x:String>
|
||||
@@ -936,8 +937,8 @@
|
||||
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithDetachedHead">You are creating commit on a detached HEAD. Do you want to continue?</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithFilter">You have staged {0} file(s) but only {1} file(s) displayed ({2} files are filtered out). Do you want to continue?</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">CONFLICTS DETECTED</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.OpenBuiltinMergeTool" xml:space="preserve">OPEN MERGE TOOL</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.OpenExternalMergeTool" xml:space="preserve">OPEN EXTERNAL MERGETOOL</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.Merge" xml:space="preserve">MERGE</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.MergeExternal" xml:space="preserve">OPEN EXTERNAL MERGETOOL</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts" xml:space="preserve">OPEN ALL CONFLICTS IN EXTERNAL MERGETOOL</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">FILE CONFLICTS ARE RESOLVED</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.UseMine" xml:space="preserve">USE MINE</x:String>
|
||||
|
||||
@@ -97,6 +97,8 @@
|
||||
<x:String x:Key="Text.ChangeCM.CheckoutFirstParentRevision" xml:space="preserve">重置文件到上一版本</x:String>
|
||||
<x:String x:Key="Text.ChangeCM.CheckoutThisRevision" xml:space="preserve">重置文件到该版本</x:String>
|
||||
<x:String x:Key="Text.ChangeCM.GenerateCommitMessage" xml:space="preserve">生成提交信息</x:String>
|
||||
<x:String x:Key="Text.ChangeCM.Merge" xml:space="preserve">解决冲突(内部工具)</x:String>
|
||||
<x:String x:Key="Text.ChangeCM.MergeExternal" xml:space="preserve">解决冲突(外部工具)</x:String>
|
||||
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">切换变更显示模式</x:String>
|
||||
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">文件名+路径列表模式</x:String>
|
||||
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">全路径列表模式</x:String>
|
||||
@@ -540,6 +542,22 @@
|
||||
<x:String x:Key="Text.Merge.Into" xml:space="preserve">目标分支 :</x:String>
|
||||
<x:String x:Key="Text.Merge.Mode" xml:space="preserve">合并方式 :</x:String>
|
||||
<x:String x:Key="Text.Merge.Source" xml:space="preserve">合并目标 :</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.MineFirst" xml:space="preserve">先应用 MINE 后 THEIRS</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.TheirsFirst" xml:space="preserve">先应用 THEIRS 后 MINE</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UseBoth" xml:space="preserve">应用全部</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.AllResolved" xml:space="preserve">所有冲突已解决</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.ConflictsRemaining" xml:space="preserve">{0} 个冲突未解决</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Mine" xml:space="preserve">MINE</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.NextConflict" xml:space="preserve">下一个冲突</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.PrevConflict" xml:space="preserve">上一个冲突</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Result" xml:space="preserve">合并结果</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.SaveAndStage" xml:space="preserve">保存并暂存</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Theirs" xml:space="preserve">THEIRS</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Title" xml:space="preserve">合并冲突</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UnsavedChanges" xml:space="preserve">放弃所有更改?</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UseMine" xml:space="preserve">仅应用 MINE</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UseTheirs" xml:space="preserve">仅应用 THEIRS</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Undo" xml:space="preserve">撤销更改</x:String>
|
||||
<x:String x:Key="Text.MergeMultiple" xml:space="preserve">合并(多目标)</x:String>
|
||||
<x:String x:Key="Text.MergeMultiple.CommitChanges" xml:space="preserve">提交变化</x:String>
|
||||
<x:String x:Key="Text.MergeMultiple.Strategy" xml:space="preserve">合并策略 :</x:String>
|
||||
@@ -921,7 +939,8 @@
|
||||
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithDetachedHead">您正在向一个游离的 HEAD 提交变更,是否继续提交?</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithFilter" xml:space="preserve">当前有 {0} 个文件在暂存区中,但仅显示了 {1} 个文件({2} 个文件被过滤掉了),是否继续提交?</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">检测到冲突</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.OpenExternalMergeTool" xml:space="preserve">打开合并工具</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.Merge" xml:space="preserve">解决冲突</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.MergeExternal" xml:space="preserve">使用外部工具解决冲突</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts" xml:space="preserve">打开合并工具解决冲突</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">文件冲突已解决</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.UseMine" xml:space="preserve">使用 MINE</x:String>
|
||||
|
||||
@@ -97,6 +97,8 @@
|
||||
<x:String x:Key="Text.ChangeCM.CheckoutFirstParentRevision" xml:space="preserve">重設檔案到上一版本</x:String>
|
||||
<x:String x:Key="Text.ChangeCM.CheckoutThisRevision" xml:space="preserve">重設檔案為此版本</x:String>
|
||||
<x:String x:Key="Text.ChangeCM.GenerateCommitMessage" xml:space="preserve">產生提交訊息</x:String>
|
||||
<x:String x:Key="Text.ChangeCM.Merge" xml:space="preserve">解決衝突 (內建工具)</x:String>
|
||||
<x:String x:Key="Text.ChangeCM.MergeExternal" xml:space="preserve">解決衝突 (外部工具)</x:String>
|
||||
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">切換變更顯示模式</x:String>
|
||||
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">檔案名稱 + 路徑列表模式</x:String>
|
||||
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">全路徑列表模式</x:String>
|
||||
@@ -540,6 +542,22 @@
|
||||
<x:String x:Key="Text.Merge.Into" xml:space="preserve">目標分支:</x:String>
|
||||
<x:String x:Key="Text.Merge.Mode" xml:space="preserve">合併方式:</x:String>
|
||||
<x:String x:Key="Text.Merge.Source" xml:space="preserve">合併來源:</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.MineFirst" xml:space="preserve">先應用 MINE,再應用 THEIRS</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.TheirsFirst" xml:space="preserve">先應用 THEIRS,再應用 MINE</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UseBoth" xml:space="preserve">應用兩側</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.AllResolved" xml:space="preserve">所有衝突已經解決</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.ConflictsRemaining" xml:space="preserve">{0} 個衝突尚未解決</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Mine" xml:space="preserve">MINE</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.NextConflict" xml:space="preserve">下一個衝突</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.PrevConflict" xml:space="preserve">上一個衝突</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Result" xml:space="preserve">合併结果</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.SaveAndStage" xml:space="preserve">保存並暫存</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Theirs" xml:space="preserve">THEIRS</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Title" xml:space="preserve">解決衝突</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UnsavedChanges" xml:space="preserve">放棄所有更改?</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UseMine" xml:space="preserve">僅應用 MINE</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.UseTheirs" xml:space="preserve">僅應用 THEIRS</x:String>
|
||||
<x:String x:Key="Text.MergeConflictEditor.Undo" xml:space="preserve">撤銷更改</x:String>
|
||||
<x:String x:Key="Text.MergeMultiple" xml:space="preserve">合併 (多個來源)</x:String>
|
||||
<x:String x:Key="Text.MergeMultiple.CommitChanges" xml:space="preserve">提交變更</x:String>
|
||||
<x:String x:Key="Text.MergeMultiple.Strategy" xml:space="preserve">合併策略:</x:String>
|
||||
@@ -921,7 +939,8 @@
|
||||
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithDetachedHead">您正在向一个分離狀態的 HEAD 提交變更,您確定要繼續提交嗎?</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithFilter" xml:space="preserve">您已暫存 {0} 個檔案,但只顯示 {1} 個檔案 ({2} 個檔案被篩選器隱藏)。您確定要繼續提交嗎?</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">偵測到衝突</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.OpenExternalMergeTool" xml:space="preserve">使用外部合併工具開啟</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.Merge" xml:space="preserve">解決衝突</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.MergeExternal" xml:space="preserve">使用外部工具解決衝突</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts" xml:space="preserve">使用外部合併工具開啟</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">檔案衝突已解決</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.UseMine" xml:space="preserve">使用我方版本 (ours)</x:String>
|
||||
|
||||
@@ -54,17 +54,12 @@ namespace SourceGit.ViewModels
|
||||
private set;
|
||||
} = false;
|
||||
|
||||
public bool CanUseExternalMergeTool
|
||||
public bool CanMerge
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = false;
|
||||
|
||||
public bool CanUseBuiltinMergeTool
|
||||
{
|
||||
get => CanUseExternalMergeTool;
|
||||
}
|
||||
|
||||
public string FilePath
|
||||
{
|
||||
get => _change.Path;
|
||||
@@ -79,7 +74,7 @@ namespace SourceGit.ViewModels
|
||||
var isSubmodule = repo.Submodules.Find(x => x.Path.Equals(change.Path, StringComparison.Ordinal)) != null;
|
||||
if (!isSubmodule && (_change.ConflictReason is Models.ConflictReason.BothAdded or Models.ConflictReason.BothModified))
|
||||
{
|
||||
CanUseExternalMergeTool = true;
|
||||
CanMerge = true;
|
||||
IsResolved = new Commands.IsConflictResolved(repo.FullPath, change).GetResult();
|
||||
}
|
||||
|
||||
@@ -123,14 +118,20 @@ namespace SourceGit.ViewModels
|
||||
await _wc.UseMineAsync([_change]);
|
||||
}
|
||||
|
||||
public async Task OpenExternalMergeToolAsync()
|
||||
public async Task MergeAsync()
|
||||
{
|
||||
await _wc.UseExternalMergeToolAsync(_change);
|
||||
if (CanMerge)
|
||||
{
|
||||
var ctx = new MergeConflictEditor(_repo, _change.Path);
|
||||
await ctx.LoadAsync();
|
||||
await App.ShowDialog(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
public MergeConflictEditor CreateBuiltinMergeViewModel()
|
||||
public async Task MergeExternalAsync()
|
||||
{
|
||||
return new MergeConflictEditor(_repo, _change.Path);
|
||||
if (CanMerge)
|
||||
await _wc.UseExternalMergeToolAsync(_change);
|
||||
}
|
||||
|
||||
private Repository _repo = null;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Avalonia;
|
||||
@@ -52,8 +51,6 @@ namespace SourceGit.ViewModels
|
||||
|
||||
public class MergeConflictEditor : ObservableObject
|
||||
{
|
||||
public string Title => App.Text("MergeConflictEditor.Title", _filePath);
|
||||
|
||||
public string FilePath
|
||||
{
|
||||
get => _filePath;
|
||||
@@ -566,96 +563,6 @@ namespace SourceGit.ViewModels
|
||||
ResultDiffLines = resultLines;
|
||||
}
|
||||
|
||||
public void AcceptOurs()
|
||||
{
|
||||
if (_conflictRegions.Count == 0)
|
||||
return;
|
||||
|
||||
bool anyResolved = false;
|
||||
foreach (var region in _conflictRegions)
|
||||
{
|
||||
if (!region.IsResolved)
|
||||
{
|
||||
region.ResolvedContent = new List<string>(region.OursContent);
|
||||
region.IsResolved = true;
|
||||
region.ResolutionType = Models.ConflictResolution.UseOurs;
|
||||
anyResolved = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyResolved)
|
||||
{
|
||||
RebuildResultContent();
|
||||
BuildAlignedResultPanel();
|
||||
UpdateConflictInfo();
|
||||
IsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void AcceptTheirs()
|
||||
{
|
||||
if (_conflictRegions.Count == 0)
|
||||
return;
|
||||
|
||||
bool anyResolved = false;
|
||||
foreach (var region in _conflictRegions)
|
||||
{
|
||||
if (!region.IsResolved)
|
||||
{
|
||||
region.ResolvedContent = new List<string>(region.TheirsContent);
|
||||
region.IsResolved = true;
|
||||
region.ResolutionType = Models.ConflictResolution.UseTheirs;
|
||||
anyResolved = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyResolved)
|
||||
{
|
||||
RebuildResultContent();
|
||||
BuildAlignedResultPanel();
|
||||
UpdateConflictInfo();
|
||||
IsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void AcceptCurrentOurs()
|
||||
{
|
||||
if (_currentConflictIndex < 0 || _currentConflictIndex >= _conflictRegions.Count)
|
||||
return;
|
||||
|
||||
var region = _conflictRegions[_currentConflictIndex];
|
||||
if (region.IsResolved)
|
||||
return;
|
||||
|
||||
region.ResolvedContent = new List<string>(region.OursContent);
|
||||
region.IsResolved = true;
|
||||
region.ResolutionType = Models.ConflictResolution.UseOurs;
|
||||
|
||||
RebuildResultContent();
|
||||
BuildAlignedResultPanel();
|
||||
UpdateConflictInfo();
|
||||
IsModified = true;
|
||||
}
|
||||
|
||||
public void AcceptCurrentTheirs()
|
||||
{
|
||||
if (_currentConflictIndex < 0 || _currentConflictIndex >= _conflictRegions.Count)
|
||||
return;
|
||||
|
||||
var region = _conflictRegions[_currentConflictIndex];
|
||||
if (region.IsResolved)
|
||||
return;
|
||||
|
||||
region.ResolvedContent = new List<string>(region.TheirsContent);
|
||||
region.IsResolved = true;
|
||||
region.ResolutionType = Models.ConflictResolution.UseTheirs;
|
||||
|
||||
RebuildResultContent();
|
||||
BuildAlignedResultPanel();
|
||||
UpdateConflictInfo();
|
||||
IsModified = true;
|
||||
}
|
||||
|
||||
public IReadOnlyList<ConflictRegion> GetConflictRegions() => _conflictRegions;
|
||||
|
||||
public void AcceptOursAtIndex(int conflictIndex)
|
||||
@@ -671,10 +578,10 @@ namespace SourceGit.ViewModels
|
||||
region.IsResolved = true;
|
||||
region.ResolutionType = Models.ConflictResolution.UseOurs;
|
||||
|
||||
IsModified = true;
|
||||
RebuildResultContent();
|
||||
BuildAlignedResultPanel();
|
||||
UpdateConflictInfo();
|
||||
IsModified = true;
|
||||
}
|
||||
|
||||
public void AcceptTheirsAtIndex(int conflictIndex)
|
||||
@@ -690,66 +597,10 @@ namespace SourceGit.ViewModels
|
||||
region.IsResolved = true;
|
||||
region.ResolutionType = Models.ConflictResolution.UseTheirs;
|
||||
|
||||
IsModified = true;
|
||||
RebuildResultContent();
|
||||
BuildAlignedResultPanel();
|
||||
UpdateConflictInfo();
|
||||
IsModified = true;
|
||||
}
|
||||
|
||||
public void AcceptBothMineFirst()
|
||||
{
|
||||
if (_conflictRegions.Count == 0)
|
||||
return;
|
||||
|
||||
bool anyResolved = false;
|
||||
foreach (var region in _conflictRegions)
|
||||
{
|
||||
if (!region.IsResolved)
|
||||
{
|
||||
var combined = new List<string>(region.OursContent);
|
||||
combined.AddRange(region.TheirsContent);
|
||||
region.ResolvedContent = combined;
|
||||
region.IsResolved = true;
|
||||
region.ResolutionType = Models.ConflictResolution.UseBothMineFirst;
|
||||
anyResolved = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyResolved)
|
||||
{
|
||||
RebuildResultContent();
|
||||
BuildAlignedResultPanel();
|
||||
UpdateConflictInfo();
|
||||
IsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void AcceptBothTheirsFirst()
|
||||
{
|
||||
if (_conflictRegions.Count == 0)
|
||||
return;
|
||||
|
||||
bool anyResolved = false;
|
||||
foreach (var region in _conflictRegions)
|
||||
{
|
||||
if (!region.IsResolved)
|
||||
{
|
||||
var combined = new List<string>(region.TheirsContent);
|
||||
combined.AddRange(region.OursContent);
|
||||
region.ResolvedContent = combined;
|
||||
region.IsResolved = true;
|
||||
region.ResolutionType = Models.ConflictResolution.UseBothTheirsFirst;
|
||||
anyResolved = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyResolved)
|
||||
{
|
||||
RebuildResultContent();
|
||||
BuildAlignedResultPanel();
|
||||
UpdateConflictInfo();
|
||||
IsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void AcceptBothMineFirstAtIndex(int conflictIndex)
|
||||
@@ -767,10 +618,10 @@ namespace SourceGit.ViewModels
|
||||
region.IsResolved = true;
|
||||
region.ResolutionType = Models.ConflictResolution.UseBothMineFirst;
|
||||
|
||||
IsModified = true;
|
||||
RebuildResultContent();
|
||||
BuildAlignedResultPanel();
|
||||
UpdateConflictInfo();
|
||||
IsModified = true;
|
||||
}
|
||||
|
||||
public void AcceptBothTheirsFirstAtIndex(int conflictIndex)
|
||||
@@ -788,10 +639,10 @@ namespace SourceGit.ViewModels
|
||||
region.IsResolved = true;
|
||||
region.ResolutionType = Models.ConflictResolution.UseBothTheirsFirst;
|
||||
|
||||
IsModified = true;
|
||||
RebuildResultContent();
|
||||
BuildAlignedResultPanel();
|
||||
UpdateConflictInfo();
|
||||
IsModified = true;
|
||||
}
|
||||
|
||||
public void UndoResolutionAtIndex(int conflictIndex)
|
||||
@@ -807,10 +658,10 @@ namespace SourceGit.ViewModels
|
||||
region.IsResolved = false;
|
||||
region.ResolutionType = Models.ConflictResolution.None;
|
||||
|
||||
IsModified = true;
|
||||
RebuildResultContent();
|
||||
BuildAlignedResultPanel();
|
||||
UpdateConflictInfo();
|
||||
IsModified = true;
|
||||
}
|
||||
|
||||
public void GotoPrevConflict()
|
||||
@@ -910,7 +761,6 @@ namespace SourceGit.ViewModels
|
||||
if (string.IsNullOrEmpty(_resultContent))
|
||||
{
|
||||
UnresolvedConflictCount = 0;
|
||||
_totalConflicts = 0;
|
||||
CurrentConflictIndex = -1;
|
||||
UpdateResolvedRanges();
|
||||
return;
|
||||
@@ -918,14 +768,16 @@ namespace SourceGit.ViewModels
|
||||
|
||||
// Count unresolved conflicts in current content
|
||||
var markers = Commands.QueryConflictContent.GetConflictMarkers(_resultContent);
|
||||
var conflictStarts = markers.Where(m => m.Type == Models.ConflictMarkerType.Start).ToList();
|
||||
var conflictStarts = new List<Models.ConflictMarkerInfo>();
|
||||
foreach (var m in markers)
|
||||
{
|
||||
if (m.Type == Models.ConflictMarkerType.Start)
|
||||
conflictStarts.Add(m);
|
||||
}
|
||||
|
||||
int unresolvedCount = conflictStarts.Count;
|
||||
UnresolvedConflictCount = unresolvedCount;
|
||||
|
||||
// Total conflicts is the original count (never changes)
|
||||
_totalConflicts = _conflictRegions.Count;
|
||||
|
||||
// Mark which original conflicts are resolved
|
||||
// A conflict is resolved if its start marker no longer exists in _resultContent
|
||||
var currentLines = _resultContent.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
|
||||
@@ -952,8 +804,20 @@ namespace SourceGit.ViewModels
|
||||
j++;
|
||||
}
|
||||
|
||||
if (currentOurs.Count == region.OursContent.Count &&
|
||||
currentOurs.SequenceEqual(region.OursContent))
|
||||
if (currentOurs.Count != region.OursContent.Count)
|
||||
continue;
|
||||
|
||||
var allEquals = true;
|
||||
for (var k = 0; k < currentOurs.Count; k++)
|
||||
{
|
||||
if (!currentOurs[k].Equals(region.OursContent[k], StringComparison.Ordinal))
|
||||
{
|
||||
allEquals = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allEquals)
|
||||
{
|
||||
region.IsResolved = false;
|
||||
break;
|
||||
@@ -1127,7 +991,6 @@ namespace SourceGit.ViewModels
|
||||
private int _currentConflictLine = -1;
|
||||
private int _currentConflictStartLine = -1;
|
||||
private int _currentConflictEndLine = -1;
|
||||
private int _totalConflicts = 0;
|
||||
private List<Models.TextDiffLine> _oursDiffLines = [];
|
||||
private List<Models.TextDiffLine> _theirsDiffLines = [];
|
||||
private List<Models.TextDiffLine> _resultDiffLines = [];
|
||||
|
||||
@@ -124,26 +124,82 @@
|
||||
<TextBlock Margin="6,0,0,0" Text="{DynamicResource Text.WorkingCopy.Conflicts.UseMine}" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Classes="flat primary"
|
||||
Margin="8,0,0,0"
|
||||
Click="OnOpenBuiltinMergeTool"
|
||||
IsVisible="{Binding CanUseBuiltinMergeTool}"
|
||||
HotKey="{OnPlatform Ctrl+Shift+M, macOS=⌘+Shift+M}">
|
||||
<SplitButton Height="28"
|
||||
Margin="8,0,0,0" Padding="8,0"
|
||||
Click="OnMerge"
|
||||
IsVisible="{Binding CanMerge, Mode=OneWay}">
|
||||
<SplitButton.Styles>
|
||||
<Style Selector="SplitButton">
|
||||
<Setter Property="MinHeight" Value="24"/>
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Grid ColumnDefinitions="*,1,Auto">
|
||||
<Button x:Name="PART_PrimaryButton"
|
||||
Grid.Column="0"
|
||||
Classes="flat primary"
|
||||
MinWidth="32"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
Command="{TemplateBinding Command}"
|
||||
CommandParameter="{TemplateBinding CommandParameter}"
|
||||
CornerRadius="3,0,0,3"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Focusable="False"
|
||||
KeyboardNavigation.IsTabStop="False" />
|
||||
|
||||
<Button x:Name="PART_SecondaryButton"
|
||||
Grid.Column="2"
|
||||
Classes="flat primary"
|
||||
Width="32"
|
||||
CornerRadius="0,3,3,0"
|
||||
Padding="0"
|
||||
Focusable="False"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
KeyboardNavigation.IsTabStop="False">
|
||||
<Path Height="12" Width="12"
|
||||
Margin="0,4,0,0"
|
||||
Fill="{DynamicResource AccentButtonForeground}"
|
||||
Data="{DynamicResource Icons.Down}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
|
||||
<Style Selector="^:disabled /template/ Button">
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Border2}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="^:disabled TextBlock">
|
||||
<Setter Property="Foreground" Value="{DynamicResource Brush.FG2}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="^:disabled Path">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.FG2}"/>
|
||||
</Style>
|
||||
</Style>
|
||||
</SplitButton.Styles>
|
||||
|
||||
<SplitButton.Flyout>
|
||||
<MenuFlyout Placement="BottomEdgeAlignedLeft">
|
||||
<MenuItem Header="{DynamicResource Text.WorkingCopy.Conflicts.MergeExternal}"
|
||||
Click="OnMergeExternal">
|
||||
<MenuItem.Icon>
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.OpenWith}"/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</SplitButton.Flyout>
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Conflict}"/>
|
||||
<TextBlock Margin="6,0,0,0" Text="{DynamicResource Text.WorkingCopy.Conflicts.OpenBuiltinMergeTool}" VerticalAlignment="Center"/>
|
||||
<TextBlock Margin="6,0,0,0" Text="{DynamicResource Text.WorkingCopy.Conflicts.Merge}"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Classes="flat"
|
||||
Margin="8,0,0,0"
|
||||
Click="OnOpenExternalMergeTool"
|
||||
IsVisible="{Binding CanUseExternalMergeTool}"
|
||||
HotKey="{OnPlatform Ctrl+Shift+D, macOS=⌘+Shift+D}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.OpenWith}"/>
|
||||
<TextBlock Margin="6,0,0,0" Text="{DynamicResource Text.WorkingCopy.Conflicts.OpenExternalMergeTool}" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</SplitButton>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
@@ -37,25 +37,18 @@ namespace SourceGit.Views
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private async void OnOpenExternalMergeTool(object _, RoutedEventArgs e)
|
||||
private async void OnMerge(object _, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.Conflict vm)
|
||||
await vm.OpenExternalMergeToolAsync();
|
||||
await vm.MergeAsync();
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private async void OnOpenBuiltinMergeTool(object _, RoutedEventArgs e)
|
||||
private async void OnMergeExternal(object _, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.Conflict vm)
|
||||
{
|
||||
var mergeVm = vm.CreateBuiltinMergeViewModel();
|
||||
await mergeVm.LoadAsync();
|
||||
|
||||
var window = TopLevel.GetTopLevel(this) as Window;
|
||||
var mergeWindow = new MergeConflictEditor { DataContext = mergeVm };
|
||||
await mergeWindow.ShowDialog(window);
|
||||
}
|
||||
await vm.MergeExternalAsync();
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
x:DataType="vm:MergeConflictEditor"
|
||||
x:Name="ThisControl"
|
||||
Icon="/App.ico"
|
||||
Title="{Binding Title}"
|
||||
Title="{DynamicResource Text.MergeConflictEditor.Title}"
|
||||
MinWidth="1024" MinHeight="600">
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
||||
<Grid RowDefinitions="Auto,32,*,Auto">
|
||||
<!-- TitleBar -->
|
||||
<Grid Grid.Row="0" Height="28" IsVisible="{Binding !#ThisControl.UseSystemWindowFrame}">
|
||||
<Border Background="{DynamicResource Brush.TitleBar}"
|
||||
@@ -26,7 +26,7 @@
|
||||
IsVisible="{OnPlatform True, macOS=False}"/>
|
||||
|
||||
<TextBlock Classes="bold"
|
||||
Text="{Binding Title}"
|
||||
Text="{DynamicResource Text.MergeConflictEditor.Title}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
IsHitTestVisible="False"/>
|
||||
|
||||
@@ -34,163 +34,76 @@
|
||||
</Grid>
|
||||
|
||||
<!-- Toolbar -->
|
||||
<Border Grid.Row="1" Padding="8" Background="{DynamicResource Brush.ToolBar}" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border2}">
|
||||
<Border Grid.Row="1" Padding="8,0" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border2}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<!-- File Path -->
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.File}"/>
|
||||
<TextBlock Margin="8,0,0,0" Text="{Binding FilePath}" VerticalAlignment="Center"/>
|
||||
<TextBlock Margin="8" Text="{Binding FilePath}" VerticalAlignment="Center"/>
|
||||
<v:LoadingIcon Width="14" Height="14" IsVisible="{Binding IsLoading}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Navigation and Actions -->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="8">
|
||||
<!-- Navigation -->
|
||||
<Button Classes="icon_button"
|
||||
Width="24"
|
||||
ToolTip.Tip="{DynamicResource Text.MergeConflictEditor.PrevConflict}"
|
||||
Click="OnGotoPrevConflict"
|
||||
IsEnabled="{Binding HasPrevConflict}">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Up}" Margin="0,6,0,0"/>
|
||||
<Path Width="12" Height="12" Margin="0,6,0,0" Data="{StaticResource Icons.Up}"/>
|
||||
</Button>
|
||||
<Button Classes="icon_button"
|
||||
Width="24"
|
||||
ToolTip.Tip="{DynamicResource Text.MergeConflictEditor.NextConflict}"
|
||||
Click="OnGotoNextConflict"
|
||||
IsEnabled="{Binding HasNextConflict}">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Down}" Margin="0,6,0,0"/>
|
||||
<Path Width="12" Height="12" Margin="0,6,0,0" Data="{StaticResource Icons.Down}"/>
|
||||
</Button>
|
||||
|
||||
<!-- Info Bar -->
|
||||
<TextBlock Text="{Binding StatusText}" VerticalAlignment="Center"/>
|
||||
|
||||
<Rectangle Width="1" Fill="{DynamicResource Brush.Border2}" Margin="4,4"/>
|
||||
<Rectangle Width="1" Fill="{DynamicResource Brush.Border2}" Margin="4,6"/>
|
||||
|
||||
<!-- Save -->
|
||||
<Button Classes="flat primary"
|
||||
Margin="0,2" Padding="6,3"
|
||||
Content="{DynamicResource Text.MergeConflictEditor.SaveAndStage}"
|
||||
Click="OnSaveAndStage"
|
||||
IsEnabled="{Binding CanSave}"/>
|
||||
IsEnabled="{Binding CanSave, Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Main Content -->
|
||||
<Grid Grid.Row="2">
|
||||
<!-- Loading Indicator -->
|
||||
<v:LoadingIcon Width="48" Height="48" IsVisible="{Binding IsLoading}"/>
|
||||
|
||||
<!-- Editor Panels -->
|
||||
<Grid RowDefinitions="*,*" IsVisible="{Binding !IsLoading}">
|
||||
<!-- Mine and Theirs Panels (Side-by-Side) -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="*,*" Margin="4,4,4,2">
|
||||
<!-- Mine (Ours) Panel -->
|
||||
<Border Grid.Column="0" Margin="0,0,2,0">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Border Grid.Row="0" Padding="8,4" Background="{DynamicResource Brush.Diff.MineHeader}">
|
||||
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.Mine}"
|
||||
Foreground="White"
|
||||
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}"
|
||||
IsOldSide="True"
|
||||
FontFamily="{DynamicResource Fonts.Monospace}"
|
||||
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
|
||||
AddedContentBackground="{DynamicResource Brush.Diff.AddedBG}"
|
||||
DeletedContentBackground="{DynamicResource Brush.Diff.MineBG}"
|
||||
AddedHighlightBrush="{DynamicResource Brush.Diff.AddedHighlight}"
|
||||
DeletedHighlightBrush="{DynamicResource Brush.Diff.MineBG}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"/>
|
||||
<Border x:Name="MinePopup"
|
||||
IsVisible="False"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right"
|
||||
Background="{DynamicResource Brush.ToolBar}"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4"
|
||||
Padding="4,2"
|
||||
BoxShadow="0 2 8 0 #40000000">
|
||||
<Button Classes="flat primary"
|
||||
Content="{DynamicResource Text.MergeConflictEditor.UseMine}"
|
||||
Click="OnUseMineFromHover"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Theirs Panel -->
|
||||
<Border Grid.Column="1" Margin="2,0,0,0">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Border Grid.Row="0" Padding="8,4" Background="{DynamicResource Brush.Diff.TheirsHeader}">
|
||||
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.Theirs}"
|
||||
Foreground="White"
|
||||
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}"
|
||||
IsOldSide="False"
|
||||
FontFamily="{DynamicResource Fonts.Monospace}"
|
||||
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
|
||||
AddedContentBackground="{DynamicResource Brush.Diff.TheirsBG}"
|
||||
DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}"
|
||||
AddedHighlightBrush="{DynamicResource Brush.Diff.TheirsBG}"
|
||||
DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"/>
|
||||
<Border x:Name="TheirsPopup"
|
||||
IsVisible="False"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right"
|
||||
Background="{DynamicResource Brush.ToolBar}"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4"
|
||||
Padding="4,2"
|
||||
BoxShadow="0 2 8 0 #40000000">
|
||||
<Button Classes="flat primary"
|
||||
Content="{DynamicResource Text.MergeConflictEditor.UseTheirs}"
|
||||
Click="OnUseTheirsFromHover"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Result Panel -->
|
||||
<Border Grid.Row="1" Margin="4,2,4,4">
|
||||
<Grid Grid.Row="2" RowDefinitions="*,*">
|
||||
<!-- Mine and Theirs Panels (Side-by-Side) -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="*,*" Margin="4,4,4,2">
|
||||
<!-- Mine (Ours) Panel -->
|
||||
<Border Grid.Column="0" Margin="0,0,2,0">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Border Grid.Row="0" Padding="8,4" Background="{DynamicResource Brush.FG1}">
|
||||
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.Result}"
|
||||
Foreground="{DynamicResource Brush.Window}"
|
||||
FontWeight="Bold"/>
|
||||
<Border Grid.Row="0" Padding="8,4" Background="{DynamicResource Brush.Diff.MineHeader}">
|
||||
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.Mine}"
|
||||
Foreground="White"
|
||||
FontWeight="Bold"/>
|
||||
</Border>
|
||||
<Grid Grid.Row="1">
|
||||
<v:MergeDiffPresenter x:Name="ResultPresenter"
|
||||
PanelType="Result"
|
||||
DiffLines="{Binding ResultDiffLines, Mode=OneWay}"
|
||||
<v:MergeDiffPresenter x:Name="OursPresenter"
|
||||
PanelType="Mine"
|
||||
DiffLines="{Binding OursDiffLines, Mode=OneWay}"
|
||||
MaxLineNumber="{Binding DiffMaxLineNumber}"
|
||||
FileName="{Binding FilePath}"
|
||||
IsOldSide="False"
|
||||
IsResultPanel="True"
|
||||
IsOldSide="True"
|
||||
FontFamily="{DynamicResource Fonts.Monospace}"
|
||||
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
|
||||
AddedContentBackground="{DynamicResource Brush.Diff.AddedBG}"
|
||||
DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}"
|
||||
DeletedContentBackground="{DynamicResource Brush.Diff.MineBG}"
|
||||
AddedHighlightBrush="{DynamicResource Brush.Diff.AddedHighlight}"
|
||||
DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}"
|
||||
IndicatorBackground="{DynamicResource Brush.Diff.EmptyBG}"
|
||||
MineContentBackground="{DynamicResource Brush.Diff.MineBG}"
|
||||
TheirsContentBackground="{DynamicResource Brush.Diff.TheirsBG}"
|
||||
DeletedHighlightBrush="{DynamicResource Brush.Diff.MineBG}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"/>
|
||||
<Border x:Name="ResultPopup"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
Background="{DynamicResource Brush.Contents}"/>
|
||||
<Border x:Name="MinePopup"
|
||||
IsVisible="False"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right"
|
||||
@@ -200,42 +113,39 @@
|
||||
CornerRadius="4"
|
||||
Padding="4,2"
|
||||
BoxShadow="0 2 8 0 #40000000">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<Button Classes="flat primary"
|
||||
Content="{DynamicResource Text.MergeConflictEditor.UseMine}"
|
||||
Click="OnUseMineFromHover"/>
|
||||
<Button Classes="flat primary"
|
||||
Content="{DynamicResource Text.MergeConflictEditor.UseTheirs}"
|
||||
Click="OnUseTheirsFromHover"/>
|
||||
<Button Classes="flat primary">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.UseBoth}"/>
|
||||
<Path Width="8" Height="8" Data="{StaticResource Icons.Down}" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<Button.Flyout>
|
||||
<MenuFlyout Placement="BottomEdgeAlignedLeft">
|
||||
<MenuItem Click="OnUseBothMineFirstFromHover">
|
||||
<MenuItem.Icon>
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.February}" Fill="{DynamicResource Brush.Diff.MineHeader}"/>
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.Header>
|
||||
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.AcceptBoth.MineFirst}"/>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
<MenuItem Click="OnUseBothTheirsFirstFromHover">
|
||||
<MenuItem.Icon>
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.February}" Fill="{DynamicResource Brush.Diff.TheirsHeader}"/>
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.Header>
|
||||
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.AcceptBoth.TheirsFirst}"/>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<Button Classes="flat primary"
|
||||
Content="{DynamicResource Text.MergeConflictEditor.UseMine}"
|
||||
Click="OnUseMine"/>
|
||||
</Border>
|
||||
<Border x:Name="ResultUndoPopup"
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Theirs Panel -->
|
||||
<Border Grid.Column="1" Margin="2,0,0,0">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Border Grid.Row="0" Padding="8,4" Background="{DynamicResource Brush.Diff.TheirsHeader}">
|
||||
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.Theirs}"
|
||||
Foreground="White"
|
||||
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}"
|
||||
IsOldSide="False"
|
||||
FontFamily="{DynamicResource Fonts.Monospace}"
|
||||
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
|
||||
AddedContentBackground="{DynamicResource Brush.Diff.TheirsBG}"
|
||||
DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}"
|
||||
AddedHighlightBrush="{DynamicResource Brush.Diff.TheirsBG}"
|
||||
DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
Background="{DynamicResource Brush.Contents}"/>
|
||||
<Border x:Name="TheirsPopup"
|
||||
IsVisible="False"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right"
|
||||
@@ -245,16 +155,105 @@
|
||||
CornerRadius="4"
|
||||
Padding="4,2"
|
||||
BoxShadow="0 2 8 0 #40000000">
|
||||
<Button Classes="flat"
|
||||
Content="{DynamicResource Text.MergeConflictEditor.Undo}"
|
||||
Click="OnUndoResolution"/>
|
||||
<Button Classes="flat primary"
|
||||
Content="{DynamicResource Text.MergeConflictEditor.UseTheirs}"
|
||||
Click="OnUseTheirs"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
|
||||
<!-- Result Panel -->
|
||||
<Border Grid.Row="1" Margin="4,2,4,4">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Border Grid.Row="0" Padding="8,4" Background="{DynamicResource Brush.FG1}">
|
||||
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.Result}"
|
||||
Foreground="{DynamicResource Brush.Window}"
|
||||
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}"
|
||||
IsOldSide="False"
|
||||
IsResultPanel="True"
|
||||
FontFamily="{DynamicResource Fonts.Monospace}"
|
||||
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
|
||||
AddedContentBackground="{DynamicResource Brush.Diff.AddedBG}"
|
||||
DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}"
|
||||
AddedHighlightBrush="{DynamicResource Brush.Diff.AddedHighlight}"
|
||||
DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}"
|
||||
IndicatorBackground="{DynamicResource Brush.Diff.EmptyBG}"
|
||||
MineContentBackground="{DynamicResource Brush.Diff.MineBG}"
|
||||
TheirsContentBackground="{DynamicResource Brush.Diff.TheirsBG}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
Background="{DynamicResource Brush.Contents}"/>
|
||||
<Border x:Name="ResultPopup"
|
||||
IsVisible="False"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right"
|
||||
Background="{DynamicResource Brush.ToolBar}"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4"
|
||||
Padding="4,2"
|
||||
BoxShadow="0 2 8 0 #40000000">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<Button Classes="flat primary"
|
||||
Content="{DynamicResource Text.MergeConflictEditor.UseMine}"
|
||||
Click="OnUseMine"/>
|
||||
<Button Classes="flat primary"
|
||||
Content="{DynamicResource Text.MergeConflictEditor.UseTheirs}"
|
||||
Click="OnUseTheirs"/>
|
||||
<Button Classes="flat primary">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.UseBoth}"/>
|
||||
<Path Width="12" Height="12" Margin="4,4,0,0" Data="{StaticResource Icons.Down}"/>
|
||||
</StackPanel>
|
||||
<Button.Flyout>
|
||||
<MenuFlyout Placement="BottomEdgeAlignedLeft">
|
||||
<MenuItem Click="OnUseBothMineFirst">
|
||||
<MenuItem.Icon>
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.February}" Fill="{DynamicResource Brush.Diff.MineHeader}"/>
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.Header>
|
||||
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.AcceptBoth.MineFirst}"/>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
<MenuItem Click="OnUseBothTheirsFirst">
|
||||
<MenuItem.Icon>
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.February}" Fill="{DynamicResource Brush.Diff.TheirsHeader}"/>
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.Header>
|
||||
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.AcceptBoth.TheirsFirst}"/>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border x:Name="ResultUndoPopup"
|
||||
IsVisible="False"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right"
|
||||
Background="{DynamicResource Brush.ToolBar}"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4"
|
||||
Padding="4,2"
|
||||
BoxShadow="0 2 8 0 #40000000">
|
||||
<Button Classes="flat"
|
||||
Content="{DynamicResource Text.MergeConflictEditor.Undo}"
|
||||
Click="OnUndoResolution"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</v:ChromelessWindow>
|
||||
|
||||
@@ -2,14 +2,15 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
using AvaloniaEdit;
|
||||
using AvaloniaEdit.Document;
|
||||
using AvaloniaEdit.Editing;
|
||||
@@ -188,11 +189,23 @@ namespace SourceGit.Views
|
||||
|
||||
TextArea.TextView.Margin = new Thickness(4, 0);
|
||||
TextArea.LeftMargins.Add(new MergeDiffLineNumberMargin(this));
|
||||
TextArea.LeftMargins.Add(new MergeDiffVerticalSeparatorMargin(this));
|
||||
TextArea.LeftMargins.Add(new MergeDiffVerticalSeparatorMargin());
|
||||
TextArea.TextView.BackgroundRenderers.Add(new MergeDiffLineBackgroundRenderer(this));
|
||||
TextArea.TextView.LineTransformers.Add(new MergeDiffIndicatorTransformer(this));
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
|
||||
_scrollViewer = e.NameScope.Find<ScrollViewer>("PART_ScrollViewer");
|
||||
if (_scrollViewer != null)
|
||||
{
|
||||
_scrollViewer.ScrollChanged += OnTextViewScrollChanged;
|
||||
_scrollViewer.Bind(ScrollViewer.OffsetProperty, new Binding("ScrollOffset", BindingMode.OneWay));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
@@ -208,16 +221,8 @@ namespace SourceGit.Views
|
||||
TextArea.TextView.VisualLinesChanged += OnTextViewVisualLinesChanged;
|
||||
}
|
||||
|
||||
public ScrollViewer GetScrollViewer()
|
||||
{
|
||||
_scrollViewer ??= this.FindDescendantOfType<ScrollViewer>();
|
||||
return _scrollViewer;
|
||||
}
|
||||
|
||||
protected override void OnUnloaded(RoutedEventArgs e)
|
||||
{
|
||||
_scrollViewer = null;
|
||||
|
||||
TextArea.TextView.ContextRequested -= OnTextViewContextRequested;
|
||||
TextArea.TextView.PointerMoved -= OnTextViewPointerMoved;
|
||||
TextArea.TextView.PointerExited -= OnTextViewPointerExited;
|
||||
@@ -310,8 +315,7 @@ namespace SourceGit.Views
|
||||
|
||||
private void OnTextViewPointerMoved(object sender, PointerEventArgs e)
|
||||
{
|
||||
var window = this.FindAncestorOfType<MergeConflictEditor>();
|
||||
if (window?.DataContext is not ViewModels.MergeConflictEditor vm)
|
||||
if (DataContext is not ViewModels.MergeConflictEditor vm)
|
||||
return;
|
||||
|
||||
if (vm.IsLoading)
|
||||
@@ -430,8 +434,7 @@ namespace SourceGit.Views
|
||||
|
||||
private void OnTextViewPointerWheelChanged(object sender, PointerWheelEventArgs e)
|
||||
{
|
||||
var window = this.FindAncestorOfType<MergeConflictEditor>();
|
||||
if (window?.DataContext is not ViewModels.MergeConflictEditor vm)
|
||||
if (DataContext is not ViewModels.MergeConflictEditor vm)
|
||||
return;
|
||||
|
||||
if (vm.SelectedChunk == null || vm.SelectedChunk.Panel != PanelType)
|
||||
@@ -443,8 +446,7 @@ namespace SourceGit.Views
|
||||
|
||||
private void OnTextViewVisualLinesChanged(object sender, EventArgs e)
|
||||
{
|
||||
var window = this.FindAncestorOfType<MergeConflictEditor>();
|
||||
if (window?.DataContext is not ViewModels.MergeConflictEditor vm)
|
||||
if (DataContext is not ViewModels.MergeConflictEditor vm)
|
||||
return;
|
||||
|
||||
if (vm.SelectedChunk == null || vm.SelectedChunk.Panel != PanelType)
|
||||
@@ -454,6 +456,23 @@ namespace SourceGit.Views
|
||||
UpdateSelectedChunkPosition(vm);
|
||||
}
|
||||
|
||||
private void OnTextViewScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
if (_scrollViewer == null || DataContext is not ViewModels.MergeConflictEditor vm)
|
||||
return;
|
||||
|
||||
if (vm.ScrollOffset.NearlyEquals(_scrollViewer.Offset))
|
||||
return;
|
||||
|
||||
if (IsPointerOver || e.OffsetDelta.SquaredLength > 1.0f)
|
||||
{
|
||||
vm.ScrollOffset = _scrollViewer.Offset;
|
||||
|
||||
if (!TextArea.TextView.IsPointerOver)
|
||||
vm.SelectedChunk = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSelectedChunkPosition(ViewModels.MergeConflictEditor vm)
|
||||
{
|
||||
var chunk = vm.SelectedChunk;
|
||||
@@ -616,11 +635,6 @@ namespace SourceGit.Views
|
||||
|
||||
public class MergeDiffVerticalSeparatorMargin : AbstractMargin
|
||||
{
|
||||
public MergeDiffVerticalSeparatorMargin(MergeDiffPresenter presenter)
|
||||
{
|
||||
_presenter = presenter;
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
var pen = new Pen(Brushes.DarkGray);
|
||||
@@ -631,8 +645,6 @@ namespace SourceGit.Views
|
||||
{
|
||||
return new Size(1, 0);
|
||||
}
|
||||
|
||||
private readonly MergeDiffPresenter _presenter;
|
||||
}
|
||||
|
||||
public class MergeDiffIndicatorTransformer : DocumentColorizingTransformer
|
||||
@@ -817,107 +829,93 @@ namespace SourceGit.Views
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnOpened(EventArgs e)
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
base.OnOpened(e);
|
||||
|
||||
// Get presenter references
|
||||
_oursPresenter = this.FindControl<MergeDiffPresenter>("OursPresenter");
|
||||
_theirsPresenter = this.FindControl<MergeDiffPresenter>("TheirsPresenter");
|
||||
_resultPresenter = this.FindControl<MergeDiffPresenter>("ResultPresenter");
|
||||
|
||||
// Get popup references
|
||||
_minePopup = this.FindControl<Border>("MinePopup");
|
||||
_theirsPopup = this.FindControl<Border>("TheirsPopup");
|
||||
_resultPopup = this.FindControl<Border>("ResultPopup");
|
||||
_resultUndoPopup = this.FindControl<Border>("ResultUndoPopup");
|
||||
|
||||
// Defer scroll sync setup to ensure ScrollViewers are available in the visual tree
|
||||
Avalonia.Threading.Dispatcher.UIThread.Post(SetupScrollSync,
|
||||
Avalonia.Threading.DispatcherPriority.Loaded);
|
||||
base.OnDataContextChanged(e);
|
||||
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm)
|
||||
{
|
||||
ScrollToCurrentConflict();
|
||||
vm.PropertyChanged += OnViewModelPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupScrollSync()
|
||||
protected override void OnSizeChanged(SizeChangedEventArgs e)
|
||||
{
|
||||
var oursScroll = _oursPresenter?.GetScrollViewer();
|
||||
var theirsScroll = _theirsPresenter?.GetScrollViewer();
|
||||
var resultScroll = _resultPresenter?.GetScrollViewer();
|
||||
base.OnSizeChanged(e);
|
||||
|
||||
// Wheel events for scroll sync
|
||||
if (_oursPresenter != null)
|
||||
_oursPresenter.AddHandler(PointerWheelChangedEvent, OnPresenterPointerWheelChanged, RoutingStrategies.Tunnel);
|
||||
if (_theirsPresenter != null)
|
||||
_theirsPresenter.AddHandler(PointerWheelChangedEvent, OnPresenterPointerWheelChanged, RoutingStrategies.Tunnel);
|
||||
if (_resultPresenter != null)
|
||||
_resultPresenter.AddHandler(PointerWheelChangedEvent, OnPresenterPointerWheelChanged, RoutingStrategies.Tunnel);
|
||||
|
||||
// ScrollChanged for scrollbar drag sync
|
||||
if (oursScroll != null)
|
||||
oursScroll.ScrollChanged += OnScrollChanged;
|
||||
if (theirsScroll != null)
|
||||
theirsScroll.ScrollChanged += OnScrollChanged;
|
||||
if (resultScroll != null)
|
||||
resultScroll.ScrollChanged += OnScrollChanged;
|
||||
}
|
||||
|
||||
private void OnScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
if (_isSyncingScroll || sender is not ScrollViewer source)
|
||||
return;
|
||||
|
||||
// Sync on any scroll change (scrollbar drag, programmatic, etc.)
|
||||
SyncAllScrollViewers(source.Offset);
|
||||
}
|
||||
|
||||
private void OnPresenterPointerWheelChanged(object sender, PointerWheelEventArgs e)
|
||||
{
|
||||
var oursScroll = _oursPresenter?.GetScrollViewer();
|
||||
var theirsScroll = _theirsPresenter?.GetScrollViewer();
|
||||
var resultScroll = _resultPresenter?.GetScrollViewer();
|
||||
|
||||
var delta = e.Delta.Y * 50;
|
||||
var currentOffset = oursScroll?.Offset ?? Vector.Zero;
|
||||
var newOffset = new Vector(currentOffset.X, Math.Max(0, currentOffset.Y - delta));
|
||||
|
||||
SyncAllScrollViewers(newOffset);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void SyncAllScrollViewers(Vector offset)
|
||||
{
|
||||
if (_isSyncingScroll)
|
||||
return;
|
||||
|
||||
_isSyncingScroll = true;
|
||||
try
|
||||
if (!_execSizeChanged)
|
||||
{
|
||||
var oursScroll = _oursPresenter?.GetScrollViewer();
|
||||
var theirsScroll = _theirsPresenter?.GetScrollViewer();
|
||||
var resultScroll = _resultPresenter?.GetScrollViewer();
|
||||
_execSizeChanged = true;
|
||||
ScrollToCurrentConflict();
|
||||
}
|
||||
}
|
||||
|
||||
// Direct offset assignment for immediate sync
|
||||
if (oursScroll != null)
|
||||
oursScroll.Offset = offset;
|
||||
if (theirsScroll != null)
|
||||
theirsScroll.Offset = offset;
|
||||
if (resultScroll != null)
|
||||
resultScroll.Offset = offset;
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
|
||||
// Also update ViewModel for state tracking
|
||||
if (e.Handled)
|
||||
return;
|
||||
|
||||
var vm = DataContext as ViewModels.MergeConflictEditor;
|
||||
if (vm == null)
|
||||
return;
|
||||
|
||||
var modifier = OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control;
|
||||
|
||||
if (e.KeyModifiers == modifier)
|
||||
{
|
||||
if (e.Key == Key.S && vm.CanSave)
|
||||
{
|
||||
OnSaveAndStage(null, null);
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == Key.Up && vm.HasPrevConflict)
|
||||
{
|
||||
vm.GotoPrevConflict();
|
||||
UpdateCurrentConflictHighlight();
|
||||
ScrollToCurrentConflict();
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == Key.Down && vm.HasNextConflict)
|
||||
{
|
||||
vm.GotoNextConflict();
|
||||
UpdateCurrentConflictHighlight();
|
||||
ScrollToCurrentConflict();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async void OnClosing(WindowClosingEventArgs e)
|
||||
{
|
||||
base.OnClosing(e);
|
||||
|
||||
if (_forceClose)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm)
|
||||
vm.ScrollOffset = offset;
|
||||
vm.PropertyChanged -= OnViewModelPropertyChanged;
|
||||
|
||||
return;
|
||||
}
|
||||
finally
|
||||
|
||||
e.Cancel = true;
|
||||
|
||||
var result = await App.AskConfirmAsync(App.Text("MergeConflictEditor.UnsavedChanges"));
|
||||
if (result)
|
||||
{
|
||||
_isSyncingScroll = false;
|
||||
_forceClose = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
base.OnClosed(e);
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
private void OnViewModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(ViewModels.MergeConflictEditor.IsLoading))
|
||||
@@ -946,204 +944,6 @@ namespace SourceGit.Views
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateResolvedRanges()
|
||||
{
|
||||
if (DataContext is not ViewModels.MergeConflictEditor vm)
|
||||
return;
|
||||
|
||||
var allRanges = vm.AllConflictRanges;
|
||||
|
||||
if (_oursPresenter != null)
|
||||
_oursPresenter.AllConflictRanges = allRanges;
|
||||
if (_theirsPresenter != null)
|
||||
_theirsPresenter.AllConflictRanges = allRanges;
|
||||
// Note: Result panel doesn't use conflict ranges since it shows current state
|
||||
}
|
||||
|
||||
private void UpdateCurrentConflictHighlight()
|
||||
{
|
||||
if (DataContext is not ViewModels.MergeConflictEditor vm)
|
||||
return;
|
||||
|
||||
var startLine = vm.CurrentConflictStartLine;
|
||||
var endLine = vm.CurrentConflictEndLine;
|
||||
|
||||
if (_oursPresenter != null)
|
||||
{
|
||||
_oursPresenter.CurrentConflictStartLine = startLine;
|
||||
_oursPresenter.CurrentConflictEndLine = endLine;
|
||||
}
|
||||
if (_theirsPresenter != null)
|
||||
{
|
||||
_theirsPresenter.CurrentConflictStartLine = startLine;
|
||||
_theirsPresenter.CurrentConflictEndLine = endLine;
|
||||
}
|
||||
if (_resultPresenter != null)
|
||||
{
|
||||
_resultPresenter.CurrentConflictStartLine = startLine;
|
||||
_resultPresenter.CurrentConflictEndLine = endLine;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePopupVisibility()
|
||||
{
|
||||
// Hide all popups first
|
||||
if (_minePopup != null)
|
||||
_minePopup.IsVisible = false;
|
||||
if (_theirsPopup != null)
|
||||
_theirsPopup.IsVisible = false;
|
||||
if (_resultPopup != null)
|
||||
_resultPopup.IsVisible = false;
|
||||
if (_resultUndoPopup != null)
|
||||
_resultUndoPopup.IsVisible = false;
|
||||
|
||||
if (DataContext is not ViewModels.MergeConflictEditor vm)
|
||||
return;
|
||||
|
||||
var chunk = vm.SelectedChunk;
|
||||
if (chunk == null)
|
||||
return;
|
||||
|
||||
// Get the presenter for bounds checking
|
||||
MergeDiffPresenter presenter = chunk.Panel switch
|
||||
{
|
||||
ViewModels.MergeConflictPanelType.Mine => _oursPresenter,
|
||||
ViewModels.MergeConflictPanelType.Theirs => _theirsPresenter,
|
||||
ViewModels.MergeConflictPanelType.Result => _resultPresenter,
|
||||
_ => null
|
||||
};
|
||||
|
||||
// Show the appropriate popup based on panel type and resolved state
|
||||
Border popup;
|
||||
if (chunk.Panel == ViewModels.MergeConflictPanelType.Result && chunk.IsResolved)
|
||||
{
|
||||
// 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
|
||||
};
|
||||
}
|
||||
|
||||
if (popup != null && presenter != null)
|
||||
{
|
||||
// Position popup - clamp to visible area
|
||||
var top = chunk.Y + (chunk.Height >= 36 ? 8 : 2);
|
||||
|
||||
// Clamp top to ensure popup is visible
|
||||
var popupHeight = popup.Bounds.Height > 0 ? popup.Bounds.Height : 32;
|
||||
var presenterHeight = presenter.Bounds.Height;
|
||||
top = Math.Max(4, Math.Min(top, presenterHeight - popupHeight - 4));
|
||||
|
||||
popup.Margin = new Thickness(0, top, 24, 0);
|
||||
popup.IsVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUseMineFromHover(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm && vm.SelectedChunk is { } chunk)
|
||||
{
|
||||
var savedOffset = SaveScrollOffset();
|
||||
vm.AcceptOursAtIndex(chunk.ConflictIndex);
|
||||
UpdateCurrentConflictHighlight();
|
||||
UpdateResolvedRanges();
|
||||
RestoreScrollOffset(savedOffset);
|
||||
vm.SelectedChunk = null;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnUseTheirsFromHover(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm && vm.SelectedChunk is { } chunk)
|
||||
{
|
||||
var savedOffset = SaveScrollOffset();
|
||||
vm.AcceptTheirsAtIndex(chunk.ConflictIndex);
|
||||
UpdateCurrentConflictHighlight();
|
||||
UpdateResolvedRanges();
|
||||
RestoreScrollOffset(savedOffset);
|
||||
vm.SelectedChunk = null;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnUndoResolution(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm && vm.SelectedChunk is { } chunk)
|
||||
{
|
||||
var savedOffset = SaveScrollOffset();
|
||||
vm.UndoResolutionAtIndex(chunk.ConflictIndex);
|
||||
UpdateCurrentConflictHighlight();
|
||||
UpdateResolvedRanges();
|
||||
RestoreScrollOffset(savedOffset);
|
||||
vm.SelectedChunk = null;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override async void OnClosing(WindowClosingEventArgs e)
|
||||
{
|
||||
base.OnClosing(e);
|
||||
|
||||
if (_forceClose)
|
||||
return;
|
||||
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm && vm.HasUnsavedChanges())
|
||||
{
|
||||
e.Cancel = true;
|
||||
var result = await App.AskConfirmAsync(App.Text("MergeConflictEditor.UnsavedChanges"));
|
||||
if (result)
|
||||
{
|
||||
_forceClose = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
|
||||
if (e.Handled)
|
||||
return;
|
||||
|
||||
var vm = DataContext as ViewModels.MergeConflictEditor;
|
||||
if (vm == null)
|
||||
return;
|
||||
|
||||
var modifier = OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control;
|
||||
|
||||
if (e.KeyModifiers == modifier)
|
||||
{
|
||||
if (e.Key == Key.S && vm.CanSave)
|
||||
{
|
||||
_ = SaveAndCloseAsync();
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == Key.Up && vm.HasPrevConflict)
|
||||
{
|
||||
vm.GotoPrevConflict();
|
||||
UpdateCurrentConflictHighlight();
|
||||
ScrollToCurrentConflict();
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == Key.Down && vm.HasNextConflict)
|
||||
{
|
||||
vm.GotoNextConflict();
|
||||
UpdateCurrentConflictHighlight();
|
||||
ScrollToCurrentConflict();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGotoPrevConflict(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm && vm.HasPrevConflict)
|
||||
@@ -1166,135 +966,77 @@ namespace SourceGit.Views
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnUseCurrentMine(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm)
|
||||
{
|
||||
var savedOffset = SaveScrollOffset();
|
||||
vm.AcceptCurrentOurs();
|
||||
UpdateCurrentConflictHighlight();
|
||||
UpdateResolvedRanges();
|
||||
RestoreScrollOffset(savedOffset);
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnUseCurrentTheirs(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm)
|
||||
{
|
||||
var savedOffset = SaveScrollOffset();
|
||||
vm.AcceptCurrentTheirs();
|
||||
UpdateCurrentConflictHighlight();
|
||||
UpdateResolvedRanges();
|
||||
RestoreScrollOffset(savedOffset);
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnAcceptMine(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm)
|
||||
{
|
||||
var savedOffset = SaveScrollOffset();
|
||||
vm.AcceptOurs();
|
||||
UpdateCurrentConflictHighlight();
|
||||
UpdateResolvedRanges();
|
||||
RestoreScrollOffset(savedOffset);
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnAcceptTheirs(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm)
|
||||
{
|
||||
var savedOffset = SaveScrollOffset();
|
||||
vm.AcceptTheirs();
|
||||
UpdateCurrentConflictHighlight();
|
||||
UpdateResolvedRanges();
|
||||
RestoreScrollOffset(savedOffset);
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnAcceptBothMineFirst(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm)
|
||||
{
|
||||
var savedOffset = SaveScrollOffset();
|
||||
vm.AcceptBothMineFirst();
|
||||
UpdateCurrentConflictHighlight();
|
||||
UpdateResolvedRanges();
|
||||
RestoreScrollOffset(savedOffset);
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnAcceptBothTheirsFirst(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm)
|
||||
{
|
||||
var savedOffset = SaveScrollOffset();
|
||||
vm.AcceptBothTheirsFirst();
|
||||
UpdateCurrentConflictHighlight();
|
||||
UpdateResolvedRanges();
|
||||
RestoreScrollOffset(savedOffset);
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnUseBothMineFirstFromHover(object sender, RoutedEventArgs e)
|
||||
private void OnUseMine(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm && vm.SelectedChunk is { } chunk)
|
||||
{
|
||||
var savedOffset = SaveScrollOffset();
|
||||
var savedOffset = vm.ScrollOffset;
|
||||
vm.AcceptOursAtIndex(chunk.ConflictIndex);
|
||||
UpdateCurrentConflictHighlight();
|
||||
UpdateResolvedRanges();
|
||||
vm.SelectedChunk = null;
|
||||
vm.ScrollOffset = savedOffset;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnUseTheirs(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm && vm.SelectedChunk is { } chunk)
|
||||
{
|
||||
var savedOffset = vm.ScrollOffset;
|
||||
vm.AcceptTheirsAtIndex(chunk.ConflictIndex);
|
||||
UpdateCurrentConflictHighlight();
|
||||
UpdateResolvedRanges();
|
||||
vm.SelectedChunk = null;
|
||||
vm.ScrollOffset = savedOffset;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnUseBothMineFirst(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm && vm.SelectedChunk is { } chunk)
|
||||
{
|
||||
var savedOffset = vm.ScrollOffset;
|
||||
vm.AcceptBothMineFirstAtIndex(chunk.ConflictIndex);
|
||||
UpdateCurrentConflictHighlight();
|
||||
UpdateResolvedRanges();
|
||||
RestoreScrollOffset(savedOffset);
|
||||
vm.SelectedChunk = null;
|
||||
vm.ScrollOffset = savedOffset;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnUseBothTheirsFirstFromHover(object sender, RoutedEventArgs e)
|
||||
private void OnUseBothTheirsFirst(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm && vm.SelectedChunk is { } chunk)
|
||||
{
|
||||
var savedOffset = SaveScrollOffset();
|
||||
var savedOffset = vm.ScrollOffset;
|
||||
vm.AcceptBothTheirsFirstAtIndex(chunk.ConflictIndex);
|
||||
UpdateCurrentConflictHighlight();
|
||||
UpdateResolvedRanges();
|
||||
RestoreScrollOffset(savedOffset);
|
||||
vm.SelectedChunk = null;
|
||||
vm.ScrollOffset = savedOffset;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private Vector SaveScrollOffset()
|
||||
private void OnUndoResolution(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm)
|
||||
return vm.ScrollOffset;
|
||||
return new Vector(0, 0);
|
||||
}
|
||||
|
||||
private void RestoreScrollOffset(Vector offset)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm && vm.SelectedChunk is { } chunk)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm)
|
||||
vm.ScrollOffset = offset;
|
||||
}, Avalonia.Threading.DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
private async void OnSaveAndStage(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await SaveAndCloseAsync();
|
||||
var savedOffset = vm.ScrollOffset;
|
||||
vm.UndoResolutionAtIndex(chunk.ConflictIndex);
|
||||
UpdateCurrentConflictHighlight();
|
||||
UpdateResolvedRanges();
|
||||
vm.SelectedChunk = null;
|
||||
vm.ScrollOffset = savedOffset;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private async Task SaveAndCloseAsync()
|
||||
private async void OnSaveAndStage(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm)
|
||||
{
|
||||
@@ -1309,50 +1051,97 @@ namespace SourceGit.Views
|
||||
|
||||
private void ScrollToCurrentConflict()
|
||||
{
|
||||
if (DataContext is ViewModels.MergeConflictEditor vm && vm.CurrentConflictLine >= 0)
|
||||
if (IsLoaded && DataContext is ViewModels.MergeConflictEditor vm && vm.CurrentConflictLine >= 0)
|
||||
{
|
||||
if (_oursPresenter != null)
|
||||
{
|
||||
var lineHeight = _oursPresenter.TextArea.TextView.DefaultLineHeight;
|
||||
var vOffset = lineHeight * vm.CurrentConflictLine;
|
||||
var targetOffset = new Vector(0, Math.Max(0, vOffset - _oursPresenter.Bounds.Height * 0.3));
|
||||
SyncAllScrollViewers(targetOffset);
|
||||
}
|
||||
var lineHeight = OursPresenter.TextArea.TextView.DefaultLineHeight;
|
||||
var vOffset = lineHeight * vm.CurrentConflictLine;
|
||||
vm.ScrollOffset = new Vector(0, Math.Max(0, vOffset - OursPresenter.Bounds.Height * 0.3));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
private void UpdateResolvedRanges()
|
||||
{
|
||||
var oursScroll = _oursPresenter?.GetScrollViewer();
|
||||
var theirsScroll = _theirsPresenter?.GetScrollViewer();
|
||||
var resultScroll = _resultPresenter?.GetScrollViewer();
|
||||
if (DataContext is not ViewModels.MergeConflictEditor vm)
|
||||
return;
|
||||
|
||||
if (_oursPresenter != null)
|
||||
_oursPresenter.RemoveHandler(PointerWheelChangedEvent, OnPresenterPointerWheelChanged);
|
||||
if (_theirsPresenter != null)
|
||||
_theirsPresenter.RemoveHandler(PointerWheelChangedEvent, OnPresenterPointerWheelChanged);
|
||||
if (_resultPresenter != null)
|
||||
_resultPresenter.RemoveHandler(PointerWheelChangedEvent, OnPresenterPointerWheelChanged);
|
||||
var allRanges = vm.AllConflictRanges;
|
||||
OursPresenter.AllConflictRanges = allRanges;
|
||||
TheirsPresenter.AllConflictRanges = allRanges;
|
||||
}
|
||||
|
||||
if (oursScroll != null)
|
||||
oursScroll.ScrollChanged -= OnScrollChanged;
|
||||
if (theirsScroll != null)
|
||||
theirsScroll.ScrollChanged -= OnScrollChanged;
|
||||
if (resultScroll != null)
|
||||
resultScroll.ScrollChanged -= OnScrollChanged;
|
||||
private void UpdateCurrentConflictHighlight()
|
||||
{
|
||||
if (DataContext is not ViewModels.MergeConflictEditor vm)
|
||||
return;
|
||||
|
||||
base.OnClosed(e);
|
||||
GC.Collect();
|
||||
var startLine = vm.CurrentConflictStartLine;
|
||||
var endLine = vm.CurrentConflictEndLine;
|
||||
OursPresenter.CurrentConflictStartLine = startLine;
|
||||
OursPresenter.CurrentConflictEndLine = endLine;
|
||||
TheirsPresenter.CurrentConflictStartLine = startLine;
|
||||
TheirsPresenter.CurrentConflictEndLine = endLine;
|
||||
ResultPresenter.CurrentConflictStartLine = startLine;
|
||||
ResultPresenter.CurrentConflictEndLine = endLine;
|
||||
}
|
||||
|
||||
private void UpdatePopupVisibility()
|
||||
{
|
||||
// Hide all popups first
|
||||
MinePopup.IsVisible = false;
|
||||
TheirsPopup.IsVisible = false;
|
||||
ResultPopup.IsVisible = false;
|
||||
ResultUndoPopup.IsVisible = false;
|
||||
|
||||
if (DataContext is not ViewModels.MergeConflictEditor vm)
|
||||
return;
|
||||
|
||||
var chunk = vm.SelectedChunk;
|
||||
if (chunk == null)
|
||||
return;
|
||||
|
||||
// Get the presenter for bounds checking
|
||||
MergeDiffPresenter presenter = chunk.Panel switch
|
||||
{
|
||||
ViewModels.MergeConflictPanelType.Mine => OursPresenter,
|
||||
ViewModels.MergeConflictPanelType.Theirs => TheirsPresenter,
|
||||
ViewModels.MergeConflictPanelType.Result => ResultPresenter,
|
||||
_ => null
|
||||
};
|
||||
|
||||
// Show the appropriate popup based on panel type and resolved state
|
||||
Border popup;
|
||||
if (chunk.Panel == ViewModels.MergeConflictPanelType.Result && chunk.IsResolved)
|
||||
{
|
||||
// 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
|
||||
};
|
||||
}
|
||||
|
||||
if (popup != null && presenter != null)
|
||||
{
|
||||
// Position popup - clamp to visible area
|
||||
var top = chunk.Y + (chunk.Height >= 36 ? 8 : 2);
|
||||
|
||||
// Clamp top to ensure popup is visible
|
||||
var popupHeight = popup.Bounds.Height > 0 ? popup.Bounds.Height : 32;
|
||||
var presenterHeight = presenter.Bounds.Height;
|
||||
top = Math.Max(4, Math.Min(top, presenterHeight - popupHeight - 4));
|
||||
|
||||
popup.Margin = new Thickness(0, top, 24, 0);
|
||||
popup.IsVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _forceClose = false;
|
||||
private bool _isSyncingScroll = false;
|
||||
private MergeDiffPresenter _oursPresenter;
|
||||
private MergeDiffPresenter _theirsPresenter;
|
||||
private MergeDiffPresenter _resultPresenter;
|
||||
private Border _minePopup;
|
||||
private Border _theirsPopup;
|
||||
private Border _resultPopup;
|
||||
private Border _resultUndoPopup;
|
||||
private bool _execSizeChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,48 +260,22 @@ namespace SourceGit.Views
|
||||
{
|
||||
var change = selectedUnstaged[0];
|
||||
var path = Native.OS.GetAbsPath(repo.FullPath, change.Path);
|
||||
TryAddOpenFileToContextMenu(menu, path);
|
||||
|
||||
if (!change.IsConflicted || change.ConflictReason is Models.ConflictReason.BothAdded or Models.ConflictReason.BothModified)
|
||||
if (!change.IsConflicted)
|
||||
{
|
||||
if (change.IsConflicted)
|
||||
TryAddOpenFileToContextMenu(menu, path);
|
||||
|
||||
var diffWithMerger = new MenuItem();
|
||||
diffWithMerger.Header = App.Text("OpenInExternalMergeTool");
|
||||
diffWithMerger.Icon = App.CreateMenuIcon("Icons.OpenWith");
|
||||
diffWithMerger.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+D" : "Ctrl+Shift+D";
|
||||
diffWithMerger.Click += (_, ev) =>
|
||||
{
|
||||
var isBinary = new Commands.IsBinary(repo.FullPath, "HEAD", change.Path).GetResultAsync().GetAwaiter().GetResult();
|
||||
if (!isBinary)
|
||||
{
|
||||
var openBuiltinMerger = new MenuItem();
|
||||
openBuiltinMerger.Header = App.Text("OpenInBuiltinMergeTool");
|
||||
openBuiltinMerger.Icon = App.CreateMenuIcon("Icons.Conflict");
|
||||
openBuiltinMerger.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+M" : "Ctrl+Shift+M";
|
||||
openBuiltinMerger.Click += async (_, e) =>
|
||||
{
|
||||
var mergeVm = new ViewModels.MergeConflictEditor(repo, change.Path);
|
||||
await mergeVm.LoadAsync();
|
||||
|
||||
var window = TopLevel.GetTopLevel(this) as Window;
|
||||
var mergeWindow = new MergeConflictEditor { DataContext = mergeVm };
|
||||
await mergeWindow.ShowDialog(window);
|
||||
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(openBuiltinMerger);
|
||||
}
|
||||
}
|
||||
var openMerger = new MenuItem();
|
||||
openMerger.Header = App.Text("OpenInExternalMergeTool");
|
||||
openMerger.Icon = App.CreateMenuIcon("Icons.OpenWith");
|
||||
openMerger.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+D" : "Ctrl+Shift+D";
|
||||
openMerger.Click += async (_, e) =>
|
||||
{
|
||||
if (change.IsConflicted)
|
||||
await vm.UseExternalMergeToolAsync(change);
|
||||
else
|
||||
vm.UseExternalDiffTool(change, true);
|
||||
|
||||
e.Handled = true;
|
||||
vm.UseExternalDiffTool(change, false);
|
||||
ev.Handled = true;
|
||||
};
|
||||
menu.Items.Add(openMerger);
|
||||
|
||||
menu.Items.Add(diffWithMerger);
|
||||
}
|
||||
|
||||
var explore = new MenuItem();
|
||||
@@ -361,6 +335,36 @@ namespace SourceGit.Views
|
||||
|
||||
menu.Items.Add(useTheirs);
|
||||
menu.Items.Add(useMine);
|
||||
|
||||
if (change.ConflictReason is Models.ConflictReason.BothAdded or Models.ConflictReason.BothModified)
|
||||
{
|
||||
var isBinary = new Commands.IsBinary(repo.FullPath, "HEAD", change.Path).GetResultAsync().GetAwaiter().GetResult();
|
||||
if (!isBinary)
|
||||
{
|
||||
var mergeBuiltin = new MenuItem();
|
||||
mergeBuiltin.Header = App.Text("ChangeCM.Merge");
|
||||
mergeBuiltin.Icon = App.CreateMenuIcon("Icons.Conflict");
|
||||
mergeBuiltin.Click += async (_, e) =>
|
||||
{
|
||||
var ctx = new ViewModels.MergeConflictEditor(repo, change.Path);
|
||||
await ctx.LoadAsync();
|
||||
await App.ShowDialog(ctx);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(mergeBuiltin);
|
||||
}
|
||||
|
||||
var mergeExternal = new MenuItem();
|
||||
mergeExternal.Header = App.Text("ChangeCM.MergeExternal");
|
||||
mergeExternal.Icon = App.CreateMenuIcon("Icons.OpenWith");
|
||||
mergeExternal.Click += async (_, e) =>
|
||||
{
|
||||
await vm.UseExternalMergeToolAsync(change);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(mergeExternal);
|
||||
}
|
||||
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
}
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user