/// <summary> /// View the changes between the revisions, if possible as a diff. /// </summary> /// <param name="fileViewer">Current FileViewer.</param> /// <param name="item">The FileStatusItem to present changes for.</param> /// <param name="defaultText">default text if no diff is possible.</param> /// <param name="openWithDiffTool">The difftool command to open with.</param> /// <returns>Task to view.</returns> public static async Task ViewChangesAsync(this FileViewer fileViewer, FileStatusItem?item, CancellationToken cancellationToken, string defaultText = "", Action?openWithDiffTool = null) { if (item?.Item.IsStatusOnly ?? false) { // Present error (e.g. parsing Git) await fileViewer.ViewTextAsync(item.Item.Name, item.Item.ErrorMessage ?? ""); return; } if (item?.Item is null || item.SecondRevision?.ObjectId is null) { if (!string.IsNullOrWhiteSpace(defaultText)) { await fileViewer.ViewTextAsync(item?.Item?.Name, defaultText); return; } await fileViewer.ClearAsync(); return; } var firstId = item.FirstRevision?.ObjectId ?? item.SecondRevision.FirstParentId; openWithDiffTool ??= OpenWithDiffTool; if (item.Item.IsNew || firstId is null || (!item.Item.IsDeleted && FileHelper.IsImage(item.Item.Name))) { // View blob guid from revision, or file for worktree await fileViewer.ViewGitItemAsync(item, openWithDiffTool); return; } if (item.Item.IsRangeDiff) { // Git range-diff has cubic runtime complexity and can be slow and memory consuming, // give an indication of what is going on string range = item.BaseA is null || item.BaseB is null ? $"{firstId}...{item.SecondRevision.ObjectId}" : $"{item.BaseA}..{firstId} {item.BaseB}..{item.SecondRevision.ObjectId}"; await fileViewer.ViewTextAsync("git-range-diff.sh", $"git range-diff {range}"); string output = await fileViewer.Module.GetRangeDiffAsync( firstId, item.SecondRevision.ObjectId, item.BaseA, item.BaseB, fileViewer.GetExtraDiffArguments(isRangeDiff: true), cancellationToken); // Try set highlighting from first found filename Match match = new Regex(@"\n\s*(@@|##)\s+(?<file>[^#:\n]+)").Match(output ?? ""); string filename = match.Groups["file"].Success ? match.Groups["file"].Value : item.Item.Name; cancellationToken.ThrowIfCancellationRequested(); await fileViewer.ViewRangeDiffAsync(filename, output ?? defaultText); return; } string selectedPatch = (await GetSelectedPatchAsync(fileViewer, firstId, item.SecondRevision.ObjectId, item.Item, cancellationToken)) ?? defaultText; cancellationToken.ThrowIfCancellationRequested(); if (item.Item.IsSubmodule) { await fileViewer.ViewTextAsync(item.Item.Name, text : selectedPatch, openWithDifftool : openWithDiffTool); } else { await fileViewer.ViewPatchAsync(item, text : selectedPatch, openWithDifftool : openWithDiffTool); } return; void OpenWithDiffTool() { fileViewer.Module.OpenWithDifftool( item.Item.Name, item.Item.OldName, firstId?.ToString(), item.SecondRevision.ToString(), isTracked: item.Item.IsTracked); }