feat(merge): diff Merge opens the 3-pane editor + conflict overview ruler
- The Merge button in the Diff window now hands a conflicting merge to the in-app 3-pane editor (MergeModal routes 'conflict' through RequestConflictResolution, the same seam Approve uses) instead of dead-ending on a conflict message. - Add a conflict overview ruler right of the Result pane: a proportional map of every conflict in the file, recolored by resolved state, click a tick to jump — so conflicts are findable in long files without scrolling. - New MergeResolvedEdgeBrush token + conflictMap en/de key. Ui 128 + Loc 16 green.
This commit is contained in:
@@ -156,6 +156,7 @@ public sealed partial class MergeSectionViewModel : ViewModelBase
|
||||
TaskTitle = TaskTitle ?? "",
|
||||
ShowMergeModal = ShowMergeModal,
|
||||
ResolveMergeVm = () => _services.GetRequiredService<MergeModalViewModel>(),
|
||||
RequestConflictResolution = RequestConflictResolution,
|
||||
};
|
||||
}
|
||||
else if (CanDiffMergedRange)
|
||||
|
||||
@@ -72,6 +72,7 @@ public sealed partial class DiffModalViewModel : ViewModelBase
|
||||
public string TaskTitle { get; init; } = "";
|
||||
public Func<MergeModalViewModel, Task>? ShowMergeModal { get; set; }
|
||||
public Func<MergeModalViewModel>? ResolveMergeVm { get; set; }
|
||||
public Func<string, string, Task>? RequestConflictResolution { get; set; }
|
||||
|
||||
public ObservableCollection<DiffFileViewModel> Files { get; } = new();
|
||||
|
||||
@@ -99,10 +100,11 @@ public sealed partial class DiffModalViewModel : ViewModelBase
|
||||
{
|
||||
if (TaskId is null || ShowMergeModal is null || ResolveMergeVm is null) return;
|
||||
var vm = ResolveMergeVm();
|
||||
vm.RequestConflictResolution = RequestConflictResolution;
|
||||
await vm.InitializeAsync(TaskId, TaskTitle);
|
||||
await ShowMergeModal(vm);
|
||||
// The diff is stale once the worktree has been merged away — close it too.
|
||||
if (vm.Merged) CloseAction?.Invoke();
|
||||
// The diff is stale once the worktree merged away or a conflict opened the editor.
|
||||
if (vm.Merged || vm.RoutedToResolver) CloseAction?.Invoke();
|
||||
}
|
||||
|
||||
public async Task LoadAsync(CancellationToken ct = default)
|
||||
|
||||
@@ -28,10 +28,17 @@ public sealed partial class MergeModalViewModel : ViewModelBase
|
||||
|
||||
public Action? CloseAction { get; set; }
|
||||
|
||||
/// Set by the caller to hand a conflicting merge off to the in-app 3-pane editor
|
||||
/// instead of dead-ending on the conflict message.
|
||||
public Func<string, string, Task>? RequestConflictResolution { get; set; }
|
||||
|
||||
/// True once a merge has succeeded — lets the caller (e.g. the diff window)
|
||||
/// close itself after this modal closes.
|
||||
public bool Merged { get; private set; }
|
||||
|
||||
/// True once a conflict has been handed off to the resolver — also a cue to close the diff window.
|
||||
public bool RoutedToResolver { get; private set; }
|
||||
|
||||
public MergeModalViewModel(IWorkerClient worker)
|
||||
{
|
||||
_worker = worker;
|
||||
@@ -96,9 +103,21 @@ public sealed partial class MergeModalViewModel : ViewModelBase
|
||||
});
|
||||
break;
|
||||
case "conflict":
|
||||
HasConflict = true;
|
||||
ConflictFiles = result.ConflictFiles;
|
||||
ErrorMessage = Loc.T("vm.merge.conflict");
|
||||
// Hand off to the in-app 3-pane merge editor when wired (MergeTask aborted
|
||||
// cleanly, so the resolver re-starts the merge leaving conflicts in the tree).
|
||||
if (RequestConflictResolution is not null)
|
||||
{
|
||||
var branch = SelectedBranch!;
|
||||
RoutedToResolver = true;
|
||||
CloseAction?.Invoke();
|
||||
await RequestConflictResolution(TaskId, branch);
|
||||
}
|
||||
else
|
||||
{
|
||||
HasConflict = true;
|
||||
ConflictFiles = result.ConflictFiles;
|
||||
ErrorMessage = Loc.T("vm.merge.conflict");
|
||||
}
|
||||
break;
|
||||
case "blocked":
|
||||
ErrorMessage = Loc.T("vm.merge.blocked", result.ErrorMessage ?? "");
|
||||
|
||||
Reference in New Issue
Block a user