microsoft / VSExtensibility

A repo for upcoming changes to extensibility in Visual Studio, the new extensibility model, and language server protocol.
MIT License
351 stars 42 forks source link

'in proc' unable to set ProjectQueryableSpace #358

Open LeeMSilver opened 2 months ago

LeeMSilver commented 2 months ago

'in proc' = = = = = SEE NEXT COMMENT FOR CLEARER EXAMPLE - ignore the rest of this one = = = = = The sample code https://learn.microsoft.com/en-us/visualstudio/extensibility/visualstudio.extensibility/project/project?view=vs-2022#project-query-space-access-in-an-in-process-extension for 'in proc' is not 100% clear to me. It specifies

IProjectSystemQueryService queryService = await package.GetServiceAsync<IProjectSystemQueryService, IProjectSystemQueryService>();
ProjectQueryableSpace workSpace = queryService.QueryableSpace;

not specifing what packages definition is.

What I tried is the following:

[VisualStudioContribution]
internal class DependencyInjectionCommand : CommandLMS
   {
   public DependencyInjectionCommand(AsyncServiceProviderInjection<EnvDTE.DTE, EnvDTE80.DTE2> wDte,
                                     AsyncServiceProviderInjection<AsyncPackage, IProjectSystemQueryService> wProject)
      {
      CmdDTE = wDte;
      project = wProject;
      }

   public override CommandConfiguration CommandConfiguration => new("DependencyInjection")
      {
      Shortcuts = [new CommandShortcutConfiguration(ModifierKey.LeftAlt, Key.X, ModifierKey.LeftAlt, Key.I)],
      };

   internal static AsyncServiceProviderInjection<AsyncPackage, IProjectSystemQueryService> project
      {
      get; set;
      }

   public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken)
      {
      await CmdInvokeA(context, cancellationToken, "DependencyInjection", null);

      IProjectSystemQueryService queryService = await project.GetServiceAsync();

      ProjectWorkSpace = queryService.QueryableSpace;
      }
   }

and F11 IProjectSystemQueryService queryService = await project.GetServiceAsync(); the system does work for a few seconds and then displays the source-file in the Experimental Instance (EI) as if all is ok (The EI is ready for another command) but ProjectWorkspace is not populated and all expressions I enter in the Immediate Wndow give the message Unable to evaluate the expression.

Note that ProjectWorkSpace is defined in a file common to all files in the project as public static ProjectQueryableSpace ProjectWorkSpace { get; set; } = default;

I'm sure I misinterpreted what to do to get the ProjectQueryableSpace but I don't know what.

LeeMSilver commented 2 months ago

Below is a clearer implementation of DependencyInjectionCommand.

[VisualStudioContribution]
internal class DependencyInjectionCommand : CommandLMS
   {
   public DependencyInjectionCommand(AsyncServiceProviderInjection<EnvDTE.DTE, EnvDTE80.DTE2> wDte,
                                     AsyncServiceProviderInjection<AsyncPackage, IProjectSystemQueryService> wProject)
      {
      CmdDTEService = wDte;
      CmdAsyncPackageService = wProject;
      }

   public override CommandConfiguration CommandConfiguration => new("DependencyInjection")
      {
      Shortcuts = [new CommandShortcutConfiguration(ModifierKey.LeftAlt, Key.X, ModifierKey.LeftAlt, Key.I)],
      };

   public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken)
      {
      try
         {
         IProjectSystemQueryService queryService = await CmdAsyncPackageService.GetServiceAsync();

         CmdProjectQueryableSpace = queryService.QueryableSpace;
         }

      catch (Exception wE)
         {
         handleCriticalError("ProjectWorkSpace", wE);
         }

      await CmdInvokeFirstCommandA(context, cancellationToken, "DependencyInjection");

      // -----

      static void handleCriticalError(String wDescription, Exception wException) =>
                                      Status.EmitCriticalError($"""
                                                                Exception in {nameof(CmdInvokeFirstCommandA)} @{wDescription}

                                                                --Exception info--
                                                                {wException}
                                                                """
                                                                );
      }
   }

Ignore all the DTE stuff - that seems to be working ok.

public static AsyncServiceProviderInjection<AsyncPackage, IProjectSystemQueryService> CmdAsyncPackageService { get; set; } is declared in a different file.

In ExecuteCommandAsync at IProjectSystemQueryService queryService = await CmdAsyncPackageService.GetServiceAsync(); the following error is raised: Microsoft.VisualStudio.Shell.ServiceUnavailableException: The AsyncPackage service is unavailable.

The exception wE follows:

         Microsoft.VisualStudio.Shell.ServiceUnavailableException: The AsyncPackage service is unavailable.
            at Microsoft.VisualStudio.Extensibility.VSSdkCompatibility.AsyncServiceProviderInjection`2.<GetServiceAsync>d__3.MoveNext()
         --- End of stack trace from previous location where exception was thrown ---
            at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
            at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
            at Microsoft.VisualStudio.Extensibility.VSSdkCompatibility.AsyncServiceProviderInjection`2.<GetServiceAsync>d__4.MoveNext()
         --- End of stack trace from previous location where exception was thrown ---
            at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
            at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
            at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
            at LMS.Formatter.CommandLMS.<CmdInvokeFirstCommandA>d__78.MoveNext()

Please let me know if there's any other info you need.

LeeMSilver commented 2 months ago

= = = = = Please note that this issue is most critical to me. Since all my extension's commands (except 1 that does nothing but invoke pre-command and post-command processing) access the Solution and Project my testing can proceed no further until this is resolved. = = = = =

I updated the above code based on the updated docs @https://learn.microsoft.com/en-us/visualstudio/extensibility/visualstudio.extensibility/project/project?view=vs-2022. Note that I have re-opened Issue #339 to address problems with the updated docs.

Though not specified explicitly in the above docs, I added the following to the above example

class MyAsyncPackage<IProjectSystemQueryService> : AsyncPackage
   {
   }

because the above docs specify "an instance of AsyncPackage" but that is an abstract class and cannot be instantiated.

I also changed the following in the above example IProjectSystemQueryService queryService = await CmdAsyncPackageService.GetServiceAsync(); to

MyAsyncPackage<IProjectSystemQueryService> package = new();
IProjectSystemQueryService queryService = await package.GetServiceAsync<IProjectSystemQueryService, IProjectSystemQueryService>();

when the second statement is executed the following exception is thrown

Microsoft.VisualStudio.Shell.ServiceUnavailableException: The IProjectSystemQueryService service is unavailable.
  at Microsoft.VisualStudio.Shell.ServiceExtensions.<GetServiceAsync>d__3`2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
  at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
  at LMS.Formatter.CommandLMS.<<CmdInvokeA>g__initializeExtension|73_0>d.MoveNext()
LeeMSilver commented 2 months ago

It would be useful if someone could supply a reference or to running code or a code-snippet showing an in-proc Extension populating ProjectQueryableSpace. The only in-proc in the Samples does not use ProjectQueryableSpace.

BertanAygun commented 1 month ago

Hi, we are going to update the documentation you linked to clarify the sample and suggest using AsyncServiceProviderInjection as you were doing.

For your code, if you change it to things should start working. The first argument is the service type indicator and second argument is the actual interface you query. In this case both happens to be same.

public DependencyInjectionCommand(AsyncServiceProviderInjection<EnvDTE.DTE, EnvDTE80.DTE2> wDte,
                                     AsyncServiceProviderInjection<IProjectSystemQueryService, IProjectSystemQueryService> wProject)

Thanks, Bertan

LeeMSilver commented 1 month ago

What I've done is the following, modifying the example for out-of-proc extensions.

// Get full-name of current Project
// (it would be useful if the new Extensibility-model had a method for doing this and getting the ActiveSolution)
//
EnvDTE.Project projectDTE = (EnvDTE.Project)activeSolutionProjects.GetValue(0);
String projectPathDTE = projectDTE.FullName;

// Get ProjectSnapshot
//
IProjectSnapshot thisProject = null;
IQueryResults<IProjectSnapshot> ips = Microsoft.VisualStudio.Extensibility.QueryProjectsAsync(project => project.Where(project => project.Path == projectPathDTE)
// appropriate .With()'s 
// (it would be useful if there a  .WithAll() or an overload that populated all available .With()'s without the programmer having to specify each one wanted)
, CommandLMS.CommandCancellationToken;

Int32 ipsCount = ((IEnumerable<IProjectSnapshot>)ips).Count();

if (ipsCount == 1)
   {
   thisProject = ((IEnumerable<IProjectSnapshot>)ips).First();
   }
else
   {
   // handle ipsCount == 0 or > 1
   }