Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
27 changes: 17 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,30 @@
- Debug from .csproj and .sln [#5876](https://github.com/dotnet/vscode-csharp/issues/5876)

# 2.112.x
* Add ability to select a document's project context (PR: [#7328](https://github.com/dotnet/vscode-csharp/pull/7328))
* Fix TFM detection for .NET 10+ to enable launch.json generation (PR: [#8873](https://github.com/dotnet/vscode-csharp/pull/8873))
* Fix JavaScript highlighting in Razor files after C# control structures without braces (PR: [#8865](https://github.com/dotnet/vscode-csharp/pull/8865))
* Update Razor to 10.0.0-preview.26059.2 (PR: [#8877](https://github.com/dotnet/vscode-csharp/pull/8877))
* Encode double slash as underscore slash in hint names (PR: [#12597](https://github.com/dotnet/razor/pull/12597))
* Navigate to a Razor file when GTD/FAR/GTI is run in C# on the class name (PR: [#12580](https://github.com/dotnet/razor/pull/12580))
* Fix rename of components in the global namespace (PR: [#12577](https://github.com/dotnet/razor/pull/12577))
* Return a document symbol representing the "Render" method for a Razor file (PR: [#12568](https://github.com/dotnet/razor/pull/12568))
* Filter our html diagnostics when a tag helper attribute spans multiple lines (PR: [#12654](https://github.com/dotnet/razor/pull/12654))
* Handle Html indentation ourselves, rather than using the IDE formatter (PR: [#12623](https://github.com/dotnet/razor/pull/12623))
* Add new option to control attribute indent style (PR: [#12625](https://github.com/dotnet/razor/pull/12625))
* Fix formatting with adjacent C# templates (PR: [#12636](https://github.com/dotnet/razor/pull/12636))
* Emit the start of multiline implicit expressions the same as explicit. (PR: [#12624](https://github.com/dotnet/razor/pull/12624))
* Rename a .razor file when Roslyn renames the component type name (PR: [#12606](https://github.com/dotnet/razor/pull/12606))
* Rename component tags and type references when a Razor file is renamed (PR: [#12561](https://github.com/dotnet/razor/pull/12561))
* Handle conflict markers (PR: [#12642](https://github.com/dotnet/razor/pull/12642))
* Fix cross project span and edit mapping (PR: [#12614](https://github.com/dotnet/razor/pull/12614))
* Update Roslyn to 5.4.0-2.26060.1 (PR: [#8877](https://github.com/dotnet/vscode-csharp/pull/8877))
* Filter our html diagnostics when a tag helper attribute spans multiple lines (PR: [#12654](https://github.com/dotnet/razor/pull/12654))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed some trailing whitespace from Razor entries.

* Handle Html indentation ourselves, rather than using the IDE formatter (PR: [#12623](https://github.com/dotnet/razor/pull/12623))
* Add new option to control attribute indent style (PR: [#12625](https://github.com/dotnet/razor/pull/12625))
* Fix formatting with adjacent C# templates (PR: [#12636](https://github.com/dotnet/razor/pull/12636))
* Emit the start of multiline implicit expressions the same as explicit. (PR: [#12624](https://github.com/dotnet/razor/pull/12624))
* Rename a .razor file when Roslyn renames the component type name (PR: [#12606](https://github.com/dotnet/razor/pull/12606))
* Rename component tags and type references when a Razor file is renamed (PR: [#12561](https://github.com/dotnet/razor/pull/12561))
* Handle conflict markers (PR: [#12642](https://github.com/dotnet/razor/pull/12642))
* Fix cross project span and edit mapping (PR: [#12614](https://github.com/dotnet/razor/pull/12614))
* Update Roslyn to 5.4.0-2.26062.9 (PR: [#7328](https://github.com/dotnet/vscode-csharp/pull/7328))
* Add a notification handler for project context changed. (PR: [#81942](https://github.com/dotnet/roslyn/pull/81942))
* Ensure IDE does not use .editorconfig for source generated files (PR: [#81911](https://github.com/dotnet/roslyn/pull/81911))
* Send a key across identifying contents of the project context list (PR: [#81940](https://github.com/dotnet/roslyn/pull/81940))
* Include Enc diagnostics in Razor requests (PR: [#81941](https://github.com/dotnet/roslyn/pull/81941))
* Fix completions after attribute list in lambdas (PR: [#81961](https://github.com/dotnet/roslyn/pull/81961))
* Add ProjectContext refresh queue (PR: [#81938](https://github.com/dotnet/roslyn/pull/81938))
* Add back async fixers (PR: [#81835](https://github.com/dotnet/roslyn/pull/81835))
* Fix crash in convert-if-to-switch (PR: [#81724](https://github.com/dotnet/roslyn/pull/81724))
* Add code-folding/structure-guides/sticky-scroll support for extension blocks (PR: [#81667](https://github.com/dotnet/roslyn/pull/81667))
Expand Down
2 changes: 2 additions & 0 deletions l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
"Text editor must be focused to fix all issues": "Text editor must be focused to fix all issues",
"Fix all issues": "Fix all issues",
"Select fix all action": "Select fix all action",
"Select project context": "Select project context",
"C# LSP Trace Logs": "C# LSP Trace Logs",
"Open solution": "Open solution",
"Restart server": "Restart server",
Expand All @@ -162,6 +163,7 @@
"Suppress notification": "Suppress notification",
"Restore {0}": "Restore {0}",
"Restore already in progress": "Restore already in progress",
"Select context": "Select context",
"C# Project Context Status": "C# Project Context Status",
"Active File Context": "Active File Context",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering - could we merge this into the select button?
e.g.
ProjectA (net6.0) Change Active File Context

or maybe we could even put the document name in it?
ProjectA (net6.0) Change Program.cs Context

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, although the select command is only visible when there are multiple project contexts. Would it be jarring for the text to move from the item details to the command and back again as you navigate?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah hmm. I wonder if we should just always show the button even if there's only one. Not sure

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should get it into preview in its current form and work out user experiences issues from there.

"Initializing dotnet-trace.../dotnet-trace is a command name and should not be localized": {
Expand Down
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"workspace"
],
"defaults": {
"roslyn": "5.4.0-2.26060.1",
"roslyn": "5.4.0-2.26062.9",
"omniSharp": "1.39.14",
"razor": "10.0.0-preview.26059.2",
"razorOmnisharp": "7.0.0-preview.23363.1",
Expand Down Expand Up @@ -1867,6 +1867,12 @@
"category": ".NET",
"enablement": "isWorkspaceTrusted && (dotnet.server.activationContext == 'Roslyn' || dotnet.server.activationContext == 'OmniSharp')"
},
{
"command": "csharp.changeProjectContext",
"title": "%command.csharp.changeProjectContext%",
"category": "CSharp",
"enablement": "dotnet.server.activationContext == 'Roslyn'"
},
{
"command": "csharp.listProcess",
"title": "%command.csharp.listProcess%",
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"command.dotnet.generateAssets.currentProject": "Generate Assets for Build and Debug",
"command.dotnet.restore.project": "Restore Project",
"command.dotnet.restore.all": "Restore All Projects",
"command.csharp.changeProjectContext": "Change the active document's project context",
"command.csharp.downloadDebugger": "Download .NET Core Debugger",
"command.csharp.listProcess": "List process for attach",
"command.csharp.listRemoteProcess": "List processes on remote connection for attach",
Expand Down
52 changes: 51 additions & 1 deletion src/lsptoolshost/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { getDotnetInfo } from '../shared/utils/getDotnetInfo';
import { IHostExecutableResolver } from '../shared/constants/IHostExecutableResolver';
import { registerWorkspaceCommands } from './workspace/workspaceCommands';
import { registerServerCommands } from './server/serverCommands';
import { CancellationToken } from 'vscode-languageclient/node';
import { VSProjectContext } from './server/roslynProtocol';

export function registerCommands(
context: vscode.ExtensionContext,
Expand All @@ -18,7 +20,7 @@ export function registerCommands(
outputChannel: vscode.LogOutputChannel,
csharpTraceChannel: vscode.LogOutputChannel
) {
registerExtensionCommands(context, hostExecutableResolver, outputChannel, csharpTraceChannel);
registerExtensionCommands(context, languageServer, hostExecutableResolver, outputChannel, csharpTraceChannel);
registerWorkspaceCommands(context, languageServer);
registerServerCommands(context, languageServer, outputChannel);
}
Expand All @@ -28,10 +30,16 @@ export function registerCommands(
*/
function registerExtensionCommands(
context: vscode.ExtensionContext,
languageServer: RoslynLanguageServer,
hostExecutableResolver: IHostExecutableResolver,
outputChannel: vscode.LogOutputChannel,
csharpTraceChannel: vscode.LogOutputChannel
) {
context.subscriptions.push(
vscode.commands.registerCommand('csharp.changeProjectContext', async (options) =>
changeProjectContext(languageServer, options)
)
);
context.subscriptions.push(
vscode.commands.registerCommand('csharp.reportIssue', async () =>
reportIssue(
Expand All @@ -47,3 +55,45 @@ function registerExtensionCommands(
vscode.commands.registerCommand('csharp.showOutputWindow', async () => outputChannel.show())
);
}
async function changeProjectContext(
languageServer: RoslynLanguageServer,
options: ChangeProjectContextOptions | undefined
): Promise<VSProjectContext | undefined> {
const editor = vscode.window.activeTextEditor;
if (editor === undefined) {
return;
}
const contextList = await languageServer._projectContextService.getProjectContexts(
editor.document.uri,
CancellationToken.None
);
if (contextList === undefined) {
return;
}

let context: VSProjectContext | undefined = undefined;

if (options !== undefined) {
const contextLabel = `${options.projectName} (${options.tfm})`;
context = contextList._vs_projectContexts.find((context) => context._vs_label === contextLabel);
} else {
const items = contextList._vs_projectContexts.map((context) => {
return { label: context._vs_label, context };
});
const selectedItem = await vscode.window.showQuickPick(items, {
placeHolder: vscode.l10n.t('Select project context'),
});
context = selectedItem?.context;
}

if (context === undefined) {
return;
}

await languageServer._projectContextService.setActiveFileContext(contextList, context);
}

interface ChangeProjectContextOptions {
projectName: string;
tfm: string;
}
79 changes: 72 additions & 7 deletions src/lsptoolshost/projectContext/projectContextService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import { TextDocumentIdentifier } from 'vscode-languageserver-protocol';
import { UriConverter } from '../utils/uriConverter';
import { LanguageServerEvents, ServerState } from '../server/languageServerEvents';
import { RoslynLanguageClient } from '../server/roslynLanguageClient';
import { CancellationToken } from 'vscode-languageclient/node';

export interface ProjectContextChangeEvent {
languageId: string;
uri: vscode.Uri;
context: VSProjectContext;
isVerified: boolean;
hasAdditionalContexts: boolean;
}

// We want to verify the project context is in a stable state before warning the user about miscellaneous files.
Expand All @@ -30,6 +32,7 @@ let _verifyTimeout: NodeJS.Timeout | undefined;
let _documentUriToVerify: vscode.Uri | undefined;

export class ProjectContextService {
private readonly _projectContextMap: Map<string, VSProjectContext> = new Map();
private readonly _contextChangeEmitter = new vscode.EventEmitter<ProjectContextChangeEvent>();
private _source = new vscode.CancellationTokenSource();
private readonly _emptyProjectContext: VSProjectContext = {
Expand Down Expand Up @@ -64,6 +67,55 @@ export class ProjectContextService {
return this._contextChangeEmitter.event;
}

public async getDocumentContext(uri: string | vscode.Uri): Promise<VSProjectContext | undefined>;
public async getDocumentContext(
uri: string | vscode.Uri,
contextList?: VSProjectContextList | undefined
): Promise<VSProjectContext>;
public async getDocumentContext(
uri: string | vscode.Uri,
contextList?: VSProjectContextList | undefined
): Promise<VSProjectContext | undefined> {
// To find the current context for the specified document we need to know the list
// of contexts that it is a part of.
contextList ??= await this.getProjectContexts(uri, CancellationToken.None);
if (contextList === undefined) {
return undefined;
}

const key = this.getContextKey(contextList);

// If this list of contexts hasn't been queried before that set the context to the default.
if (!this._projectContextMap.has(key)) {
this._projectContextMap.set(key, contextList._vs_projectContexts[contextList._vs_defaultIndex]);
}

return this._projectContextMap.get(key);
}

private getContextKey(contextList: VSProjectContextList): string {
return contextList._vs_projectContexts
.map((context) => context._vs_label)
.sort()
.join(';');
}

public async setActiveFileContext(contextList: VSProjectContextList, context: VSProjectContext): Promise<void> {
const textEditor = vscode.window.activeTextEditor;
const uri = textEditor?.document?.uri;
const languageId = textEditor?.document?.languageId;
if (!uri || languageId !== 'csharp') {
return;
}

const key = this.getContextKey(contextList);
this._projectContextMap.set(key, context);

this._contextChangeEmitter.fire({ languageId, uri, context, isVerified: true, hasAdditionalContexts: true });

await this._languageServer.refreshFeatureProviders();
}

public async refresh() {
const textEditor = vscode.window.activeTextEditor;
const languageId = textEditor?.document?.languageId;
Expand All @@ -90,30 +142,43 @@ export class ProjectContextService {
}

if (!this._languageServer.isRunning()) {
this._contextChangeEmitter.fire({ languageId, uri, context: this._emptyProjectContext, isVerified: false });
this._contextChangeEmitter.fire({
languageId,
uri,
context: this._emptyProjectContext,
isVerified: false,
hasAdditionalContexts: false,
});
return;
}

const contextList = await this.getProjectContexts(uri, this._source.token);
if (!contextList) {
this._contextChangeEmitter.fire({ languageId, uri, context: this._emptyProjectContext, isVerified: false });
this._contextChangeEmitter.fire({
languageId,
uri,
context: this._emptyProjectContext,
isVerified: false,
hasAdditionalContexts: false,
});
return;
}

const context = contextList._vs_projectContexts[contextList._vs_defaultIndex];
this._contextChangeEmitter.fire({ languageId, uri, context, isVerified: false });
const hasAdditionalContexts = contextList._vs_projectContexts.length > 1;
this._contextChangeEmitter.fire({ languageId, uri, context, isVerified: false, hasAdditionalContexts });

// If we do not recieve a refresh even within the timout period, send a verified event.
_verifyTimeout = setTimeout(() => {
this._contextChangeEmitter.fire({ languageId, uri, context, isVerified: true });
this._contextChangeEmitter.fire({ languageId, uri, context, isVerified: true, hasAdditionalContexts });
}, VerificationDelay);
}

private async getProjectContexts(
uri: vscode.Uri,
public async getProjectContexts(
uri: string | vscode.Uri,
token: vscode.CancellationToken
): Promise<VSProjectContextList | undefined> {
const uriString = UriConverter.serialize(uri);
const uriString = uri instanceof vscode.Uri ? UriConverter.serialize(uri) : uri;
const textDocument = TextDocumentIdentifier.create(uriString);

try {
Expand Down
6 changes: 5 additions & 1 deletion src/lsptoolshost/projectContext/projectContextStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@ export class ProjectContextStatus {
RazorLanguage.documentSelector
);
const projectContextService = languageServer._projectContextService;

const selectContextCommand = {
command: 'csharp.changeProjectContext',
title: vscode.l10n.t('Select context'),
};
const item = vscode.languages.createLanguageStatusItem('csharp.projectContextStatus', documentSelector);
item.name = vscode.l10n.t('C# Project Context Status');
item.detail = vscode.l10n.t('Active File Context');
context.subscriptions.push(item);

projectContextService.onActiveFileContextChanged((e) => {
item.text = e.context._vs_label;
item.command = e.hasAdditionalContexts ? selectContextCommand : undefined;

// Show a warning when the active file is part of the Miscellaneous File workspace and
// project initialization is complete.
Expand Down
Loading