dotnet / vscode-csharp

Official C# support for Visual Studio Code
MIT License
2.85k stars 669 forks source link

[3rd party] Detecting solution/project changes #5805

Open spouliot opened 1 year ago

spouliot commented 1 year ago

Environment data

C# Extension version: 2.0.212

Issue

The new (2.x) C# extension exports are incompatible with the previous (1.x) extension. Any extension that depends on the exports will not work anymore [1].

With the current (1.x) C# extension it was possible to access eventStream from the exports. This is used to be notified when the active project change, e.g.

omnisharp.eventStream.subscribe((e: BaseEvent) => {
    if (e.type === EventType.WorkspaceInformationUpdated) {
        changeActiveProject((<WorkspaceInformationUpdated>e).info.MsBuild);
    }
});

and this let our extension update itself while keeping in sync with the C# extension. Sadly this member was removed from the exports in 2.x [2].

What would be the right way to be notified when a different project/solution is loaded ?

[1] Unless you disable (or uninstall) C# Dev Kit and set useOmnisharp to true. This is fine as a temporary workaround but it fragment the ecosystem since you can only use one of the two (CDK or Uno) extensions simultaneously.

[2] Even if it still present and used internally by the C# extension. Howver it only seems to work (for project changes) when used with OmniSharp.

CyrusNajmabadi commented 1 year ago

We need to be very careful about pushing out events. This was a severe issue with the roslyn system wrt to performance, and wrt to extensions taking dependencies on things we no longer felt we can maintain (like push-notification systems). This needs to be thought through very carefully.

dibarbet commented 1 year ago

Echoing what Cyrus mentioned - unfortunately the O# event source exposed too many internal implementation details of the O# server that make it incompatible with the new Roslyn language server.

We're definitely not opposed to adding public endpoints to get information from the new server, but it won't look the same as O# and needs to be designed carefully.

@spouliot can you share more details on exactly what information you get today from O# and how you use it? That will help us shape how we design the API.

spouliot commented 1 year ago

@CyrusNajmabadi I agree about the challenges. We're not tied to the current implementation/model, but we do need more information for our extension to remain useful to developers.

@dibarbet We definitively need only a small subset of what O# provided.

can you share more details on exactly what information you get today from O# and how you use it?

We use the following types to subscribe to the WorkspaceInformationUpdated event

import { BaseEvent, WorkspaceInformationUpdated } from './omnisharp/loggingEvents';
import { EventType } from './omnisharp/EventType';

Then we need OmniSharpServer to make requests to fetch the project information

import { OmniSharpServer } from './omnisharp/OmniSharpServer';

like this:

const workspaceInformation: WorkspaceInformationResponse = await omnisharpServer.makeRequest<WorkspaceInformationResponse>(Requests.Projects);
changeActiveProject(workspaceInformation.MsBuild);

The other O# imported types needed are

import { MSBuildProject, MsBuildWorkspaceInformation, Requests, TargetFramework, WorkspaceInformationResponse } from './omnisharp/protocol';

to gather

dibarbet commented 1 year ago

Thanks, that information is super helpful. I can't promise anything in the very short term, we have our hands full with just bugs / feature parity with O# at the moment, but these sound like reasonable scenarios to enable.

cc @jasonmalinowski as well for project info

jasonmalinowski commented 1 year ago

@spouliot: your info there is very helpful -- what were you using Debug/Release configuration for, and what are you using TFM for? Just thinking ahead a bit of how we might be modeling things; depending on your end goal we might try a few different approaches.

spouliot commented 1 year ago

what were you using Debug/Release configuration for,

Since both configurations are defined, by default, in the dotnet/SDK csproj we use this, in our UI, so developers can select the build type of their project.

This is generally Debug, since it's the most useful while developing, so this information is not critical...

However it's useful to do a Release build before running (external) profiling tools. Doing this inside VSCode (without editing tasks.json or launch.json) is kind of nice.

In fact we try to minimize the changes required in the .json files whenever possible, since this is not something dotnet developers are used to do.

and what are you using TFM for?

This is used for the debugger. The Uno Platform is multi-platform (like MAUI) and we provide debugging support (based on the mono debugger) for mobile targets. This is not something available with the C#/DevKit extensions.

A single .csproj file can specify multiple TFM and, knowing them, we can provide a UI to select the available environments, like Android emulators/devices, iOS simulators/devices, to run/debug projects.

That one is really useful since each Uno project can, in theory, only support some of the available platforms. IOW we can show only the platforms that the current project support.

BinToss commented 8 months ago

The new (2.x) C# extension exports are incompatible with the previous (1.x) extension. Any extension that depends on the exports will not work anymore [1]. [1] Unless you disable (or uninstall) C# Dev Kit and set useOmnisharp to true. This is fine as a temporary workaround but it fragment the ecosystem since you can only use one of the two (CDK or Uno) extensions simultaneously.

Speaking of exports, the experimental exports for communicating with the language server are neat. The interface definition for csdevkit's exports is also available in vscode-csharp's source tree. A proper @Types package would be preferred, but this works, too.


Multiple project files may be 'active' as per the 'current' solution file, but third-party extensions may require additional build parameters such as a project, a TFM, and maybe a RID to, for example, render an accurate preview of a UI page...or an approximate preview if the target platform must be simulated (i.e. mobile device UIs).

Currently, ms-dotnettools.csharp/ms-dotnettools.csdevkit only cares about which Solution is selected by the user or workspace configuration (defaultSolution). However, (plugin?) extensions would benefit from relying on a single, central provider of properties and events for common, well-known dotnet build parameters. For instance, Uno Platform contributes a menu to VSCode's status bar . This menu allows the user to select a TargetFramework and see the active selection at all times. At one point, I had multiple extensions installed which contributed selectors like that one. It was awfully cluttered and confusing. image

In addition, A debugging session or preview presenter needs a project file selected. A solution can contain multiple GUI projects. Each project may consume the same file(s) via ProjectReference, Import, or MSBuild Item (e.g. Compile, Page, EmbeddedResource). When user wants to preview an XAML file, the resulting preview may differ depending on selected project, TargetFramework, RID (platform), and more.


API documentation and a TypeScript typedef package (if it exists, it's not published to NPM's or Yarn's main, public registries) would of great help to API users. I've managed to get the omnisharp, csharp, and csdevkit exports' typings ("vscode-csharp": "github:dotnet/vscode-csharp#semver:^2.14.8") which allowed me to start learning the API with less guesswork involved. Next, I'll be looking into how to listen for "OpenSolution" Events and get Solution data.


P.S. If a third-party extension adds csharpExtensionLoadPaths : string[] to their contributes, are the strings expected to be filesystem URIs or some sort of code path? Is it intended to point to the extension's default activate() export? that variable causes Roslyn "extension" modules to be loaded into the Roslyn Language Server process. If a .NET module is loaded into the server and it isn't a Roslyn extension, it causes...issues when the Roslyn process terminates.