microsoft / VSExtensibility

A repo for upcoming changes to extensibility in Visual Studio, the new extensibility model, and language server protocol.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Request for Samples

LeeMSilver opened this issue · comments

I would like to request 2 samples for scenarios I don't see in the current samples (or anywhere else).

  1. An in-proc extension that gets the current Project (Project containing the currently open source-file) and checks if it is an SDK project. This covers 3 scenarios not currently in the samples: 1) getting Project in in-proc extension, 2) getting Project current Project, 3) checking Project's properties.

  2. An extension (I think in-proc/out-of-proc would be the same) that has the following series of statements in a method called by ExecuteCommandAsync()
    -execute code on the UI thread
    -execute one statement on the main thread via await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
    -execute more code on the UI-thread
    -execute one statement on the main thread via await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
    -execute more code on the UI-thread and return to ExecuteCommandAsync()
    I am not sure that this can be done, but if it can there is no sample showing how.

Hi,
regarding 1., could you clarify what are you looking to get from an in-proc sample covering these APIs? For the most part, we provide out-of-proc samples but the API usage should be exactly the same when those same APIs are used from an in-proc extension. From your post, I understand that you found the relevant samples for out-of-proc extensions. What issues are you finding with just using the same code in an in-proc extension?

Regarding 2., I am a bit confused. The "main thread" and the "UI thread" are the same thing, so what you are asking doesn't seem to be any different than simply writing a command that executes something on the UI thread. The comment remover sample does that.

Thanks much for the response.

  1. My extension is in-proc due to its use of await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync() (see explanation of (2) below. a) Following the partial/erroneous documention available I cannot populate IProjectSystemQueryService and hence not access queryService.QueryableSpace (see #358). b) No sample shows how to access the Project to which the currently open source-file belongs. The existing samples have hard-coded project-names. c) No sample shows how to access the Project's properties (e.g. is it an SDK project).
    (b) and (c) are not in-proc specific.

  2. According to what I've been told here (e.g. see #321) "Most DTE operations will require main thread and yes you should invoke them from main thread, you can switch to main thread via await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync()", This implies to me that the 'main' thread is not the same as the 'UI' thread. I'm looking for a sample that calls await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync() multiple times, each time switching back to the thread that called it. Comment-remover switches to the MainThread, but never switches back to the calling thread (unless I'm misunderstanding how SwitchToMainThreadAsync() runs.)

Regarding 1., aren't you able to access the needed project querying capabilities using Extensibility.Workspaces().QueryProjectsAsync like shown in this sample? You wouldn't even need to call SwitchToMainThreadAsync to use that.

Regarding 2., I confirm that the main thread and the UI thread are indeed two names for the same thread. The statement that you quoted doesn't mention the term "UI thread" at all:

Most DTE operations will require main thread and yes you should invoke them from main thread, you can switch to main thread via await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync()

VisualStudio.Extensibility ExecuteCommandAsync methods don't have a guarantee about which thread they are being invoked on. So, if you need to be sure you are on the UI/Main thread, you need to call SwitchToMainThreadAsync. The sample code doesn't move off the UI/Main thread because it doesn't perform any time consuming operation that doesn't require the UI/Main thread.

Thanks for your responses and your patience. Quick FYI: I'm on Hong Kong time -15 hours ahead of PDT / 16 hours ahead of PST.

  1. I did a quick peek at the sample you referenced. When I first browsed it I thought it was only for out-of-proc extensions; but I'll try it on my in-proc extension. It looks like if I only use .With(project => project.Files.With(file => file.FileName)) I should get the result I want. I'll try this out later today. Also, I may not need it anymore, but how do I check if the project is a new-style SDK project? (In general I can't find documentation on how to query specific project properties.)

  2. UI thread == main thread -- understood. In the new model is VisualStudio.Extensibility.ExecuteCommandAsync a replacement for EnvDTE80.DTE2.ExecuteCommand? Since none of my extension's *Async methods ensure they're on the main thread, do I need to ensure I'm on the main thread via SwitchToMainThreadAsync for this one? If yes, I may be calling ExecuteCommandAsync multiple times per command and the command will do time-consuming actions after the call, so do I need to switch back from the main thread each time I call it?

Also, the only reason my extension is in-proc is due to its use of EnvDTE80.DTE2.ExecuteCommand. if I can use VisualStudio.Extensibility.ExecuteCommandAsync instead I'll convert it to out-of-proc and my in-proc comment in (1) will no longer apply.

ExecuteCommandAsync is the name of the method you implement to provide the behavior of a VisualStudio.Extensibility command. It's not a replacement for DTE's ability to execute commands that you can use in in-proc extensions. At the moment VisualStudio.Extensibility doesn't have the ability to invoke the execution of commands.

ExecuteCommandAsync - another big oops -obviously all my commands have this method and I wasn't thinking when I asked about it.

  1. "aren't you able to access the needed project querying capabilities using Extensibility.Workspaces().QueryProjectsAsync": No, because I have an in-proc extension and Extensibility.Workspaces().QueryProjectsAsync is for out-of-proc extensions. The problem is that I am unable to set ProjectQueryableSpace (see #358). Once I can I can follow the sample you provided. I'm looking for a sample that sets ProjectQueryableSpace for an in-proc extension as a) I can find no example of how to do it and the documentation (https://learn.microsoft.com/en-us/visualstudio/extensibility/visualstudio.extensibility/project/project?view=vs-2022#access-the-project-query-space) is incomplete/erroneous.

  2. I need to invoke EnvDTE80.DTE2.ExecuteCommand multiple time per command. #338 illustrates my problem. Again, I can find no example of how to do this - the only examples show only one invocation per command.

No, because I have an in-proc extension and Extensibility.Workspaces().QueryProjectsAsync is for out-of-proc extensions

This is not accurate. The Extensibility object is available in in-proc VisualStudio.Extensibility extensions exactly in the same way as it is in out-of-proc extensions (as long as you are in a command class or another VisuaStudio.Extensibility class that provides access to the Extensibility object). All features available out-of-proc are also available in-proc. All out-of-proc code samples are valid for in-proc usage as well.

I need to invoke EnvDTE80.DTE2.ExecuteCommand multiple time per command

You can invoke multiple commands, you simply write multiple calls to DTE one after another. You don't have to switch off the main thread between them, unless you need to for some other reason. If you need to, the issue you linked has an explanation of how to do it: call await TaskScheduler.Default (see here).

"The Extensibility object is available in in-proc VisualStudio.Extensibility extensions exactly in the same way as it is in out-of-proc extensions (as long as you are in a command class or another VisuaStudio.Extensibility class that provides access to the Extensibility object)". That's good to know . It directly conflicts the documentation I referenced in my previous response.

"You can invoke multiple commands, you simply write multiple calls to DTE one after another". What I wrote may not have been clear: "I need to invoke EnvDTE80.DTE2.ExecuteCommand multiple time per command." Between invocations the extension does more, possibly time-consuming, processing. It never calls EnvDTE80.DTE2.ExecuteCommand multiple consecutive times.

Can you point out where in the documentation exactly it is described that the Extensibility object is not available in-proc?

As I explained above, and it is explained in the issue you linked as well, you can use await TaskScheduler.Default if you want to leave the UI thread to perform some other time consuming work. You can always call SwitchToMainThreadAsync if you need to later return to the UI thread to execute another command. This may not be a good idea though, since the state of the editor may have changed while you relinquished the UI thread (for example the user may have edited the current open file, switched tab, closed tabs, closed the solution, etc.)

It doesn't specifically say the Extensibility object is not available in-proc. However under the title "Project query space access in an out-of-process extension" it says "If you're creating an out-of-process extension, use the following code" (WorkspacesExtensibility workSpace = this.Extensibility.Workspaces();) and under the title "Project query space access in an in-process extension" it has the erroneous/incomplete code for in-process. From those 2 headings I think a reasonable person would conclude that WorkspacesExtensibility workSpace = this.Extensibility.Workspaces(); is only available for out-of-proc and the other title is to be used for in-proc. Perhaps another documentation update is needed on that page.

#352 explains the problem I have using await TaskScheduler.Default just once. I suspect once that is resolved multiple calls to SwitchToMainThreadAsync will no longer be a problem.

@maiak, can we get the incorrect use of "out of process extension" fixed in this doc?
The difference shouldn't be between in-proc and out-of-proc but between VSSDK and VisualStudio.Extensibility.
Thanks

What is/are the downsides of calling DTE2.ExecuteCommand without calling SwitchToMainThreadAsync in both in-proc and out-of-proc extensions? If it is primarily that the user cannot interact w/VS while DTE2.ExecuteCommand is running that is not a problem for me as that is the current extension's behavior.

ISolutionSnapshot and IProjectSnapshot are indeed available to in-proc extensions as demonstrated in the cited example.

However there two fundamental things missing unless you can point me to documentation on how to do it: 1) an ISolutionSnapshot for DTE2.Solution and 2) IProjectSnapshot[] for (Object[])DTE2.ActiveSolutionProjects.

The cited example uses hard-coded sln-file names for populating ISolutionSnapshot and hard-coded cs-file names for populating IProjectSnapshot. 1) It does not show how to access the solution opened in Solution Explorer (that would take care of simulating DTE2.Solution) and 2) it does not show how, when a cs-file is present in multiple IProjectSnapshots how to pick the IProjectSnapshot corresponding to the Project in Soluion Explorer corresponding to the Active WIndow.

An alternative to modifying the sample or creating a new one is to have an Extension method for each of the above 2 cases.

For testing purposes only I can get around the above limitations, but since all my extension's commands check for a change of Solution/Project the above 2 cases must be implemented before the extension can be released.

Sorry, but I don't see where the example uses hard-coded file names.
Did you read through the readme and documentation for project query?

What is/are the downsides of calling DTE2.ExecuteCommand without calling SwitchToMainThreadAsync in both in-proc and out-of-proc extensions?

For in-proc extensions, DTE methods must be called on the UI thread. Out-of-proc extensions don't have DTE support at all.

"For in-proc extensions, DTE methods must be called on the UI thread. Out-of-proc extensions don't have DTE support at all."
OK. Do you know what happens if the DTE method is not called on the UI thread?

"Sorry, but I don't see where the example uses hard-coded file names."
My original message wasn't clear. The cited sample does not use file names at all and gets the info for all the projects in the Solution. But in samples that do specify project/solution-file names, which my extension needs to do the names are hard-coded.
(e.g. ProjectBuildCommand)

const string projectName = "ConsoleApp1";
        
var result = await this.Extensibility.Workspaces().QueryProjectsAsync(
                            project => project.Where(p => p.Name == projectName),
                            cancellationToken);

(e.g. AddSolutionConfigurationCommand)

const string solutionName = "ConsoleApp32";

await this.Extensibility.Workspaces().UpdateSolutionAsync(
            solution => solution.Where(solution => solution.BaseName == solutionName),
            solution => solution.AddSolutionConfiguration("Foo", "Debug", false),
            cancellationToken);

Other samples specifying a specific project/solution-file name have similar logic.

In my existing extension I get this from (Object[])DTE2.ActiveSolutionProjects and DTE2.Solution respectively. I'm trying to remove as many DTE2 dependencies as I can (only leaving DTE.ExecuteCommand until hopefully the new VSExtensibility has support for it) especially as I am still having trouble getting SwitchToMainThreadAsync to work correctly when called only once in a command, let alone the multiple calls most of my commands need.

  1. I know it's Experimental, but the Properties property is missing some elements from the .csproj-file. In my case it would be very useful to have DefineConstants added to Properties.

  2. I am trying to get the list of External-files when the project is created. I've tried
    .With(project => project.ExternalFiles.With(file => file.Path)) (similar to getting project.Files)
    and
    .With(project => project.ExternalFiles.With(path => path.Path))
    In both cases I get the Exception at the bottom of the message. I know I'm doing something wrong, but neither the Readme.md nor the Project Query docs have anything related to this.

= = = = =
Exception thrown
Exception Type: QueryExecutionException
Status Bar: System.Threading.Tasks.Task`1[System.String]
Exception: Microsoft.VisualStudio.ProjectSystem.Query.QueryExecutionException: No data providers in the system to retrieve data for the query. ---> StreamJsonRpc.RemoteInvocationException: No data providers in the system to retrieve data for the query.
= = = = =

  1. "For in-proc extensions, DTE methods must be called on the UI thread." Not that I disbelieved you (others on the team told me the same thing) but I had to see for myself what would happen if I did the call on the non-UI thread.

I executed the following 2 commands via DTE.ExecuteCommand: Edit.Cr and Edit.CrLf without invoking ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(). They ran successfully and the results were exactly what I expected - success (CrLfs were changed to Crs and then Crs were changed back to CrLfs). After each executed I successfully updated ITextDocumentSnapshot.

Is there something I'm missing about not running SwitchToMainThreadAsync(), or was I just lucky, or switching to the UI/main thread is only needed sometimes?

The primary reason I'm trying to switch to the new Extensibility Model is to get rid of logic that uses EnvDTE and DTE2 (except possibly DTE.ExecuteCommand) but it looks like I still have to use those Interfaces to access the open Solution and open Project within it -- that's why I requested Extension Methods to IProject/SolutionSnapshot to return the above 2 entities.