Skip to content

Commit 803df42

Browse files
committed
Adding LSP client completion handler and removing legacy completion support
Pushing as draft initially to get early feedback while I am finishing - completionResolve - type clean-up (switch to vscode-languageserver-protocol types when possible) - provisional completion support
1 parent 64ddced commit 803df42

6 files changed

+229
-498
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
import {
8+
CompletionItem,
9+
CompletionList,
10+
CompletionParams,
11+
CompletionTriggerKind,
12+
RequestType,
13+
} from 'vscode-languageclient';
14+
import { provideCompletionsCommand } from '../../../lsptoolshost/razorCommands';
15+
import { RazorDocumentManager } from '../document/razorDocumentManager';
16+
import { RazorDocumentSynchronizer } from '../document/razorDocumentSynchronizer';
17+
import { RazorLanguageServerClient } from '../razorLanguageServerClient';
18+
import { RazorLogger } from '../razorLogger';
19+
import { SerializableDelegatedCompletionParams } from './serializableDelegatedCompletionParams';
20+
import { SerializableDelegatedCompletionItemResolveParams } from './serializableDelegatedCompletionItemResolveParams';
21+
import { LanguageKind } from '../rpc/languageKind';
22+
import { UriConverter } from '../../../lsptoolshost/uriConverter';
23+
24+
export class CompletionHandler {
25+
private static readonly completionEndpoint = 'razor/completion';
26+
private static readonly completionResolveEndpoint = 'razor/completionItem/resolve';
27+
private completionRequestType: RequestType<SerializableDelegatedCompletionParams, CompletionList, any> =
28+
new RequestType(CompletionHandler.completionEndpoint);
29+
private completionResolveRequestType: RequestType<
30+
SerializableDelegatedCompletionItemResolveParams,
31+
CompletionItem,
32+
any
33+
> = new RequestType(CompletionHandler.completionResolveEndpoint);
34+
// TODO: do we always need empty result members defined? Can we declare type on handler function and return null?
35+
// Also, none of the empty repsonses are declared as static readonly in other handles - should they be?
36+
private static readonly emptyCompletionList: CompletionList = <CompletionList>{};
37+
private static readonly emptyCompletionItem: CompletionItem = <CompletionItem>{};
38+
39+
constructor(
40+
private readonly documentManager: RazorDocumentManager,
41+
private readonly documentSynchronizer: RazorDocumentSynchronizer,
42+
private readonly serverClient: RazorLanguageServerClient,
43+
private readonly logger: RazorLogger
44+
) {}
45+
46+
public async register() {
47+
await this.serverClient.onRequestWithParams<SerializableDelegatedCompletionParams, CompletionList, any>(
48+
this.completionRequestType,
49+
async (request: SerializableDelegatedCompletionParams, token: vscode.CancellationToken) =>
50+
this.provideCompletions(request, token)
51+
);
52+
53+
await this.serverClient.onRequestWithParams<
54+
SerializableDelegatedCompletionItemResolveParams,
55+
CompletionItem,
56+
any
57+
>(
58+
this.completionResolveRequestType,
59+
async (request: SerializableDelegatedCompletionItemResolveParams, token: vscode.CancellationToken) =>
60+
this.provideResolvedCompletionItem(request, token)
61+
);
62+
}
63+
64+
private async provideCompletions(
65+
delegatedCompletionParams: SerializableDelegatedCompletionParams,
66+
token: vscode.CancellationToken
67+
) {
68+
try {
69+
const razorDocumentUri = vscode.Uri.parse(
70+
delegatedCompletionParams.identifier.textDocumentIdentifier.uri,
71+
true
72+
);
73+
const razorDocument = await this.documentManager.getDocument(razorDocumentUri);
74+
if (razorDocument === undefined) {
75+
return CompletionHandler.emptyCompletionList;
76+
}
77+
78+
const textDocument = await vscode.workspace.openTextDocument(razorDocumentUri);
79+
80+
let virtualDocument: any;
81+
if (delegatedCompletionParams.projectedKind === LanguageKind.Html) {
82+
virtualDocument = razorDocument.htmlDocument;
83+
} else if (delegatedCompletionParams.projectedKind === LanguageKind.CSharp) {
84+
virtualDocument = razorDocument.csharpDocument;
85+
} else {
86+
// TODO: equivalent of Debug.Fail?
87+
return CompletionHandler.emptyCompletionList;
88+
}
89+
90+
// TODO: Should we check for null or undefined virtual document like we do in C# code?
91+
92+
const synchronized = await this.documentSynchronizer.trySynchronizeProjectedDocument(
93+
textDocument,
94+
virtualDocument,
95+
delegatedCompletionParams.identifier.version,
96+
token
97+
);
98+
if (!synchronized) {
99+
return CompletionHandler.emptyCompletionList;
100+
}
101+
102+
const virtualDocumentUri = UriConverter.serialize(virtualDocument.uri);
103+
104+
delegatedCompletionParams.identifier.textDocumentIdentifier.uri = virtualDocumentUri;
105+
106+
// TODO: support for provisional edits
107+
108+
// "@" is not a valid trigger character for C# / HTML and therefore we need to translate
109+
// it into a non-trigger invocation.
110+
const modifiedTriggerCharacter =
111+
delegatedCompletionParams.context.triggerCharacter === '@'
112+
? undefined
113+
: delegatedCompletionParams.context.triggerCharacter;
114+
const triggerKind =
115+
delegatedCompletionParams.context.triggerCharacter === '@'
116+
? CompletionTriggerKind.Invoked
117+
: delegatedCompletionParams.context.triggerKind;
118+
119+
let completions: vscode.CompletionList | vscode.CompletionItem[];
120+
121+
if (delegatedCompletionParams.projectedKind === LanguageKind.CSharp) {
122+
const params: CompletionParams = {
123+
context: {
124+
triggerKind: triggerKind,
125+
triggerCharacter: modifiedTriggerCharacter,
126+
},
127+
textDocument: {
128+
uri: virtualDocumentUri,
129+
},
130+
position: delegatedCompletionParams.projectedPosition,
131+
};
132+
133+
completions = await vscode.commands.executeCommand<vscode.CompletionList | vscode.CompletionItem[]>(
134+
provideCompletionsCommand,
135+
params
136+
);
137+
} else {
138+
completions = await vscode.commands.executeCommand<vscode.CompletionList | vscode.CompletionItem[]>(
139+
'vscode.executeCompletionItemProvider',
140+
UriConverter.deserialize(virtualDocumentUri),
141+
delegatedCompletionParams.projectedPosition,
142+
modifiedTriggerCharacter
143+
);
144+
}
145+
146+
const completionItems =
147+
completions instanceof Array
148+
? completions // was vscode.CompletionItem[]
149+
: completions
150+
? completions.items // was vscode.CompletionList
151+
: [];
152+
// TODO: do this conversion for HTML only, C# seems fine without it
153+
const convertedCompletionItems: CompletionItem[] = new Array(completionItems.length);
154+
for (let i = 0; i < completionItems.length; i++) {
155+
const completionItem = completionItems[i];
156+
const convertedCompletionItem = <CompletionItem>{
157+
label: completionItem.label,
158+
kind: completionItem.kind,
159+
commitCharacters: completionItem.commitCharacters,
160+
};
161+
convertedCompletionItems[i] = convertedCompletionItem;
162+
}
163+
164+
const isIncomplete = completions instanceof Array ? false : completions ? completions.isIncomplete : false;
165+
const completionList = <CompletionList>{
166+
isIncomplete: isIncomplete,
167+
items: convertedCompletionItems,
168+
};
169+
170+
return completionList;
171+
} catch (error) {
172+
this.logger.logWarning(`${CompletionHandler.completionEndpoint} failed with ${error}`);
173+
}
174+
175+
return CompletionHandler.emptyCompletionList;
176+
}
177+
178+
private async provideResolvedCompletionItem(
179+
_1: SerializableDelegatedCompletionItemResolveParams,
180+
_2: vscode.CancellationToken
181+
) {
182+
// TODO: add implementation :)
183+
return CompletionHandler.emptyCompletionItem;
184+
}
185+
}

src/razor/src/completion/provisionalCompletionOrchestrator.ts

Lines changed: 0 additions & 175 deletions
This file was deleted.

0 commit comments

Comments
 (0)