VSIX call to track file in Solution Explorer seems to be async in Visual Studio 2022

68 Views Asked by At

I am trying to create a VSIX for Visual Studio 2022 that will select a file in Solution Explorer when invoked by the user from various locations. My thought is to use the "trick" of turning on the setting "Track Active Item in Solution Explorer" temporarily to allow VS to do the complex work of finding the file. Then I would set focus on SE to get the desired outcome. This is very similar to the feature Resharper comes with (but I want it to be free of course).

A very similar technique is used in the TunnelVisionLabs project FindInSolutionExplorer here:

https://github.com/tunnelvisionlabs/FindInSolutionExplorer/blob/master/Tvl.VisualStudio.FindInSolutionExplorer/FindInSolutionExplorerPackage.cs#L76-L87

EnvDTE.Property track = ApplicationObject.get_Properties("Environment", "ProjectsAndSolution").Item("TrackFileSelectionInExplorer");
if (track.Value is bool && !((bool)track.Value))
{
    track.Value = true;
    track.Value = false;
}

// Find the Solution Explorer object
EnvDTE80.Windows2 windows = ApplicationObject.Windows as EnvDTE80.Windows2;
EnvDTE80.Window2 solutionExplorer = FindWindow(windows, EnvDTE.vsWindowType.vsWindowTypeSolutionExplorer);
if (solutionExplorer != null)
    solutionExplorer.Activate();

The first half is the trick. Turn on file tracking (if not already) temporarily and then turn it off with the assumption that it had successfully selected the file in SE. The second half then sets focus on SE.

This works great in VS 2019. However, in VS 2022 it does not. I assume it has to do with 2022 going with the more async handling of extensions. What I have observed is that in order to get the same code to work I need to introduce at significant Task.Delay in between. I came up with something like this:

//Use the VS track file selection to actually select
var trackFileProperty = _visualStudioInstance
  .Properties["Environment", "ProjectsAndSolution"]
  .Item("TrackFileSelectionInExplorer");

//Activate if currently off
var isTrackingSelection = trackFileProperty.Value is true;
if (!isTrackingSelection)
{
  trackFileProperty.Value = true;
  await Task.Delay(500).ConfigureAwait(true); // YIKES!!!!!!!
}

try
{
  // Find the Solution Explorer object
  var windows = (EnvDTE80.Windows2)_visualStudioInstance.Windows;
  var solutionExplorer = FindWindow(windows, vsWindowType.vsWindowTypeSolutionExplorer);
  if (solutionExplorer != null)
  {
      solutionExplorer.Activate();
  }
}
finally
{
  //Restore the prior state if neccessary
  if (!isTrackingSelection)
      await ThreadHelper.JoinableTaskFactory.RunAsync(async () =>
      {
          await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
          trackFileProperty.Value = false;
      });
}

Without the await Task.Delay(500).ConfigureAwait(true);, it is completely unreliable. It might work but often does not. Obviously, an arbitrary delay is a pretty awful solution. What I am hoping for is some compatible async way to allow the property change to propagate before turning it back off.

I have tried things like

await Task.Factory.StartNew(async () =>
{
    await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
 ...

And

ThreadHelper.JoinableTaskFactory.RunAsync(() =>
{
...

But nothing else has worked.

0

There are 0 best solutions below