Skip to content

Commit 6ca46de

Browse files
committed
Add ability to select a documents project context.
- Adds select project context commands - Adds the select command to the Project Context status item - Updates middleware to send selected context with server requests.
1 parent ff3e0b2 commit 6ca46de

File tree

7 files changed

+117
-12
lines changed

7 files changed

+117
-12
lines changed

Diff for: l10n/bundle.l10n.json

+2
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,12 @@
161161
"Fix All: ": "Fix All: ",
162162
"C# Workspace Status": "C# Workspace Status",
163163
"Open solution": "Open solution",
164+
"Select context": "Select context",
164165
"C# Project Context Status": "C# Project Context Status",
165166
"Active File Context": "Active File Context",
166167
"Pick a fix all scope": "Pick a fix all scope",
167168
"Fix All Code Action": "Fix All Code Action",
169+
"Select project context": "Select project context",
168170
"pipeArgs must be a string or a string array type": "pipeArgs must be a string or a string array type",
169171
"Name not defined in current configuration.": "Name not defined in current configuration.",
170172
"Configuration \"{0}\" in launch.json does not have a {1} argument with {2} for remote process listing.": "Configuration \"{0}\" in launch.json does not have a {1} argument with {2} for remote process listing.",

Diff for: package.json

+6
Original file line numberDiff line numberDiff line change
@@ -1824,6 +1824,12 @@
18241824
"category": ".NET",
18251825
"enablement": "dotnet.server.activationContext == 'Roslyn' || dotnet.server.activationContext == 'OmniSharp'"
18261826
},
1827+
{
1828+
"command": "csharp.changeDocumentContext",
1829+
"title": "%command.csharp.changeDocumentContext%",
1830+
"category": "CSharp",
1831+
"enablement": "dotnet.server.activationContext == 'Roslyn'"
1832+
},
18271833
{
18281834
"command": "csharp.listProcess",
18291835
"title": "%command.csharp.listProcess%",

Diff for: package.nls.json

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"command.dotnet.generateAssets.currentProject": "Generate Assets for Build and Debug",
1111
"command.dotnet.restore.project": "Restore Project",
1212
"command.dotnet.restore.all": "Restore All Projects",
13+
"command.csharp.changeDocumentContext": "Change the active document's project context",
1314
"command.csharp.downloadDebugger": "Download .NET Core Debugger",
1415
"command.csharp.listProcess": "List process for attach",
1516
"command.csharp.listRemoteProcess": "List processes on remote connection for attach",

Diff for: src/lsptoolshost/commands.ts

+38
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { createLaunchTargetForSolution } from '../shared/launchTarget';
1111
import reportIssue from '../shared/reportIssue';
1212
import { getDotnetInfo } from '../shared/utils/getDotnetInfo';
1313
import { IHostExecutableResolver } from '../shared/constants/IHostExecutableResolver';
14+
import { VSProjectContext } from './roslynProtocol';
15+
import { CancellationToken } from 'vscode-languageclient/node';
1416

1517
export function registerCommands(
1618
context: vscode.ExtensionContext,
@@ -41,6 +43,11 @@ export function registerCommands(
4143
context.subscriptions.push(
4244
vscode.commands.registerCommand('dotnet.openSolution', async () => openSolution(languageServer))
4345
);
46+
context.subscriptions.push(
47+
vscode.commands.registerCommand('csharp.changeDocumentContext', async () =>
48+
changeDocumentContext(languageServer)
49+
)
50+
);
4451
context.subscriptions.push(
4552
vscode.commands.registerCommand('csharp.reportIssue', async () =>
4653
reportIssue(
@@ -191,3 +198,34 @@ async function openSolution(languageServer: RoslynLanguageServer): Promise<vscod
191198
return uri;
192199
}
193200
}
201+
202+
async function changeDocumentContext(languageServer: RoslynLanguageServer): Promise<VSProjectContext | undefined> {
203+
const editor = vscode.window.activeTextEditor;
204+
if (!editor) {
205+
return;
206+
}
207+
const projectContexts = await languageServer._projectContextService.getProjectContexts(
208+
editor.document.uri,
209+
CancellationToken.None
210+
);
211+
if (!projectContexts) {
212+
return;
213+
}
214+
215+
const items = projectContexts._vs_projectContexts.map((context) => {
216+
return { label: context._vs_label, context };
217+
});
218+
const selectedItem = await vscode.window.showQuickPick(items, {
219+
placeHolder: vscode.l10n.t('Select project context'),
220+
});
221+
222+
if (selectedItem) {
223+
languageServer._projectContextService.setDocumentContext(
224+
editor.document.uri,
225+
selectedItem.context,
226+
projectContexts._vs_projectContexts.length > 1
227+
);
228+
// TODO: Replace this with proper server-side onDidChange notifications
229+
editor.edit(() => 0);
230+
}
231+
}

Diff for: src/lsptoolshost/languageStatusBar.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ class WorkspaceStatus {
4545
class ProjectContextStatus {
4646
static createStatusItem(context: vscode.ExtensionContext, languageServer: RoslynLanguageServer) {
4747
const projectContextService = languageServer._projectContextService;
48-
48+
const selectContextCommand = {
49+
command: 'csharp.changeDocumentContext',
50+
title: vscode.l10n.t('Select context'),
51+
};
4952
const item = vscode.languages.createLanguageStatusItem(
5053
'csharp.projectContextStatus',
5154
languageServerOptions.documentSelector
@@ -54,8 +57,11 @@ class ProjectContextStatus {
5457
item.detail = vscode.l10n.t('Active File Context');
5558
context.subscriptions.push(item);
5659

57-
projectContextService.onActiveFileContextChanged((e) => {
58-
item.text = e.context._vs_label;
60+
projectContextService.onDocumentContextChanged((e) => {
61+
if (vscode.window.activeTextEditor?.document.uri === e.uri) {
62+
item.text = e.context._vs_label;
63+
item.command = e.hasAdditionalContexts ? selectContextCommand : undefined;
64+
}
5965
});
6066
projectContextService.refresh();
6167
}

Diff for: src/lsptoolshost/roslynLanguageServer.ts

+24-3
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ import { registerRazorCommands } from './razorCommands';
5757
import { registerOnAutoInsert } from './onAutoInsert';
5858
import { registerCodeActionFixAllCommands } from './fixAllCodeAction';
5959
import { commonOptions, languageServerOptions, omnisharpOptions, razorOptions } from '../shared/options';
60-
import { NamedPipeInformation } from './roslynProtocol';
60+
import { NamedPipeInformation, VSTextDocumentIdentifier } from './roslynProtocol';
6161
import { IDisposable } from '../disposable';
6262
import { registerNestedCodeActionCommands } from './nestedCodeAction';
6363
import { registerRestoreCommands } from './restore';
@@ -127,7 +127,7 @@ export class RoslynLanguageServer {
127127
this._buildDiagnosticService = new BuildDiagnosticsService(diagnosticsReportedByBuild);
128128
this.registerDocumentOpenForDiagnostics();
129129

130-
this._projectContextService = new ProjectContextService(this, this._languageServerEvents);
130+
this._projectContextService = new ProjectContextService(this, _languageServerEvents);
131131

132132
// Register Razor dynamic file info handling
133133
this.registerDynamicFileInfo();
@@ -220,6 +220,7 @@ export class RoslynLanguageServer {
220220
};
221221

222222
const documentSelector = languageServerOptions.documentSelector;
223+
let server: RoslynLanguageServer | undefined = undefined;
223224

224225
// Options to control the language client
225226
const clientOptions: LanguageClientOptions = {
@@ -240,6 +241,22 @@ export class RoslynLanguageServer {
240241
protocol2Code: UriConverter.deserialize,
241242
},
242243
middleware: {
244+
async sendRequest(type, param, token, next) {
245+
if (isObject(param)) {
246+
if ('textDocument' in param) {
247+
const textDocument = <VSTextDocumentIdentifier>param.textDocument;
248+
textDocument._vs_projectContext = server?._projectContextService.getDocumentContext(
249+
textDocument.uri
250+
);
251+
} else if ('_vs_textDocument' in param) {
252+
const textDocument = <VSTextDocumentIdentifier>param._vs_textDocument;
253+
textDocument._vs_projectContext = server?._projectContextService.getDocumentContext(
254+
textDocument.uri
255+
);
256+
}
257+
}
258+
return next(type, param, token);
259+
},
243260
workspace: {
244261
configuration: (params) => readConfigurations(params),
245262
},
@@ -256,7 +273,7 @@ export class RoslynLanguageServer {
256273

257274
client.registerProposedFeatures();
258275

259-
const server = new RoslynLanguageServer(client, platformInfo, context, telemetryReporter, languageServerEvents);
276+
server = new RoslynLanguageServer(client, platformInfo, context, telemetryReporter, languageServerEvents);
260277

261278
client.registerFeature(server._onAutoInsertFeature);
262279

@@ -1107,3 +1124,7 @@ function getSessionId(): string {
11071124
export function isString(value: any): value is string {
11081125
return typeof value === 'string' || value instanceof String;
11091126
}
1127+
1128+
export function isObject(value: any): value is object {
1129+
return value !== null && typeof value === 'object';
1130+
}

Diff for: src/lsptoolshost/services/projectContextService.ts

+37-6
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ import { ServerState } from '../serverStateChange';
1414
export interface ProjectContextChangeEvent {
1515
uri: vscode.Uri;
1616
context: VSProjectContext;
17+
hasAdditionalContexts: boolean;
1718
}
1819

1920
export class ProjectContextService {
21+
/** Track the project context for a particular document uri. */
22+
private readonly _documentContexts: { [uri: string]: VSProjectContext } = {};
2023
private readonly _contextChangeEmitter = new vscode.EventEmitter<ProjectContextChangeEvent>();
2124
private _source = new vscode.CancellationTokenSource();
2225

@@ -33,10 +36,27 @@ export class ProjectContextService {
3336
vscode.window.onDidChangeActiveTextEditor(async (_) => this.refresh());
3437
}
3538

36-
public get onActiveFileContextChanged(): vscode.Event<ProjectContextChangeEvent> {
39+
public get onDocumentContextChanged(): vscode.Event<ProjectContextChangeEvent> {
3740
return this._contextChangeEmitter.event;
3841
}
3942

43+
public getDocumentContext(uri: string | vscode.Uri): VSProjectContext | undefined {
44+
const uriString = uri instanceof vscode.Uri ? UriConverter.serialize(uri) : uri;
45+
return this._documentContexts[uriString];
46+
}
47+
48+
public setDocumentContext(
49+
uri: string | vscode.Uri,
50+
context: VSProjectContext,
51+
hasAdditionalContexts: boolean
52+
): void {
53+
const uriString = uri instanceof vscode.Uri ? UriConverter.serialize(uri) : uri;
54+
uri = uri instanceof vscode.Uri ? uri : UriConverter.deserialize(uri);
55+
56+
this._documentContexts[uriString] = context;
57+
this._contextChangeEmitter.fire({ uri, context, hasAdditionalContexts });
58+
}
59+
4060
public async refresh() {
4161
const textEditor = vscode.window.activeTextEditor;
4262
if (textEditor?.document?.languageId !== 'csharp') {
@@ -54,15 +74,26 @@ export class ProjectContextService {
5474
return;
5575
}
5676

57-
const context = contextList._vs_projectContexts[contextList._vs_defaultIndex];
58-
this._contextChangeEmitter.fire({ uri, context });
77+
// Determine if the user has selected a context for this document and whether
78+
// it is still in the list of contexts.
79+
const uriString = UriConverter.serialize(uri);
80+
const selectedContext = this._documentContexts[uriString];
81+
const selectedContextValid = selectedContext
82+
? contextList._vs_projectContexts.some((c) => c._vs_id == selectedContext._vs_id)
83+
: false;
84+
85+
const defaultContext = contextList._vs_projectContexts[contextList._vs_defaultIndex];
86+
const context = selectedContextValid ? selectedContext : defaultContext;
87+
const hasAdditionalContexts = contextList._vs_projectContexts.length > 1;
88+
89+
this._contextChangeEmitter.fire({ uri, context, hasAdditionalContexts });
5990
}
6091

61-
private async getProjectContexts(
62-
uri: vscode.Uri,
92+
public async getProjectContexts(
93+
uri: string | vscode.Uri,
6394
token: vscode.CancellationToken
6495
): Promise<VSProjectContextList | undefined> {
65-
const uriString = UriConverter.serialize(uri);
96+
const uriString = uri instanceof vscode.Uri ? UriConverter.serialize(uri) : uri;
6697
const textDocument = TextDocumentIdentifier.create(uriString);
6798

6899
try {

0 commit comments

Comments
 (0)