Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions P4EditVS/Commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,12 @@ private void ExecuteCommand(SelectedFile selectedFile, int commandId, bool immed
case DiffCommandId:
case CtxtDiffCommandId:
{
if (_package.GetUseVisualStudioDiff())
{
DiffAgainstHaveInVisualStudio(filePath, fileFolder, globalOptions, commandId);
return;
}

commandline = string.Format("p4vc {0} diffhave \"{1}\"", globalOptions, filePath);
handler = CreateCommandRunnerResultHandler(GetBriefCommandDescription(commandId, filePath));
}
Expand Down Expand Up @@ -901,6 +907,145 @@ private void SetStatusBarTextForRunnerResult(Runner.RunnerResult result, string
// https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference
private static readonly Regex AlsoOpenedByRegex = new Regex(@"^... (?:.*) - also opened by (?<user>.*)$");

// VSDIFFOPT flag value from Microsoft.VisualStudio.Shell.Interop.__VSDIFFSERVICEOPTIONS.
// Declared explicitly to avoid SDK-version differences between VS2019 / VS2022 builds.
private const uint VSDIFFOPT_LeftFileIsTemporary = 0x00000010;

private void DiffAgainstHaveInVisualStudio(string filePath, string fileFolder, string globalOptions, int commandId)
{
ThreadHelper.ThrowIfNotOnUIThread();

string commandDescription = GetBriefCommandDescription(commandId, filePath);

string tempFile;
try
{
// Preserve the original extension so VS picks the right diff content type.
string ext = Path.GetExtension(filePath);
tempFile = Path.Combine(Path.GetTempPath(), string.Format("P4EditVS_{0}_have{1}", Guid.NewGuid().ToString("N"), ext));
}
catch (Exception ex)
{
OutputWindow.WriteLine("Diff: failed to build temp path: {0}", ex.Message);
SetStatusBarText(FAILURE_PREFIX + commandDescription, true);
return;
}

string commandline = string.Format("p4 {0} print -q -o \"{1}\" \"{2}#have\"", globalOptions, tempFile, filePath);
string fileName = Path.GetFileName(filePath);

Action<Runner.RunnerResult> handler = (Runner.RunnerResult result) =>
{
if (!ShowRunnerResultOutput(result, commandDescription))
{
SafeDeleteFile(tempFile);
return;
}

if (result.ExitCode != 0 || !File.Exists(tempFile))
{
SafeDeleteFile(tempFile);
SetStatusBarTextForRunnerResult(result, commandDescription);
return;
}

try
{
OpenVisualStudioDiff(tempFile, filePath, fileName);
SetStatusBarText(SUCCESS_PREFIX + commandDescription, false);
}
catch (Exception ex)
{
OutputWindow.WriteLine("Diff: failed to open VS diff: {0}", ex);
SafeDeleteFile(tempFile);
SetStatusBarText(FAILURE_PREFIX + commandDescription, true);
}
};

var runner = Runner.Create(commandline, fileFolder, handler, null, null);
OutputWindow.WriteLine("{0}: started at {1}: {2}", runner.JobId, DateTime.Now, commandline);
Runner.Run(runner, true, _package.GetCommandTimeoutSeconds(), true);
}

private void OpenVisualStudioDiff(string haveTempPath, string workspacePath, string fileName)
{
ThreadHelper.ThrowIfNotOnUIThread();

var diffService = ServiceProvider.GetService(typeof(SVsDifferenceService)) as IVsDifferenceService;
if (diffService == null)
{
throw new InvalidOperationException("SVsDifferenceService not available");
}

string leftLabel = fileName + "#have";
string rightLabel = fileName;
string caption = string.Format("Diff - {0}", fileName);
string tooltip = string.Format("{0} vs {1}", leftLabel, workspacePath);
string inlineLabel = null;
string roles = null;

diffService.OpenComparisonWindow2(
haveTempPath,
workspacePath,
caption,
tooltip,
leftLabel,
rightLabel,
inlineLabel,
roles,
VSDIFFOPT_LeftFileIsTemporary);

// VSDIFFOPT_LeftFileIsTemporary is unreliable, and per-frame close
// hooks race with the diff editor still holding the file handle.
// Defer deletion until VS shuts down — by then all editors are gone.
RegisterDiffTempFile(haveTempPath);
}

private static readonly List<string> _diffTempFiles = new List<string>();

private static void RegisterDiffTempFile(string path)
{
if (string.IsNullOrEmpty(path)) return;
lock (_diffTempFiles)
{
_diffTempFiles.Add(path);
}
}

public static void CleanupDiffTempFiles()
{
List<string> paths;
lock (_diffTempFiles)
{
paths = new List<string>(_diffTempFiles);
_diffTempFiles.Clear();
}

foreach (var p in paths)
{
try
{
if (File.Exists(p)) File.Delete(p);
}
catch
{
// best effort — %TEMP% gets cleaned eventually
}
}
}

private void SafeDeleteFile(string path)
{
try
{
if (path != null && File.Exists(path)) File.Delete(path);
}
catch (Exception)
{
// Best effort — VS will clean up on shutdown if marked temporary.
}
}

private void HandleCheckOutRunnerResult(Runner.RunnerResult result, string filePath)
{
ThreadHelper.ThrowIfNotOnUIThread();
Expand Down
25 changes: 25 additions & 0 deletions P4EditVS/P4EditVS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,15 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
await Commands.InitializeAsync(this);
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
Commands.CleanupDiffTempFiles();
}
base.Dispose(disposing);
}

private OptionPageGrid GetOptionsPage()
{
return (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
Expand Down Expand Up @@ -533,6 +542,11 @@ public bool GetUseP4V2024_1_OpenInP4V()
return GetOptionsPage().UseP4V2024_1_OpenInP4V;
}

public bool GetUseVisualStudioDiff()
{
return GetOptionsPage().UseVisualStudioDiff;
}


#region Visual Studio suo interface

Expand Down Expand Up @@ -813,6 +827,17 @@ public bool UseP4V2024_1_OpenInP4V
set { _useP4V2024_1_OpenInP4V = value; }
}

private bool _useVisualStudioDiff = true;

[Category("Options")]
[DisplayName("Use Visual Studio Diff")]
[Description("Use the built-in Visual Studio diff viewer for 'Diff Against Have Revision' instead of launching p4vc diffhave in P4V. The have revision is fetched via 'p4 print' into a temporary file.")]
public bool UseVisualStudioDiff
{
get { return _useVisualStudioDiff; }
set { _useVisualStudioDiff = value; }
}

private string _userName = "";
private string _clientName = "";
private string _server = "";
Expand Down
2 changes: 1 addition & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net

pool:
vmImage: 'windows-2019'
vmImage: 'windows-latest'

variables:
solution: '**/*.sln'
Expand Down