Skip to content

Commit b26baf6

Browse files
committed
Implement inlay hints
1 parent 77768b8 commit b26baf6

4 files changed

Lines changed: 158 additions & 6 deletions

File tree

packages/client/src/monaco-converter.ts

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
Command, CodeLens, FormattingOptions, TextEdit, WorkspaceEdit, DocumentLinkParams, DocumentLink,
2020
MarkedString, MarkupContent, ColorInformation, ColorPresentation, FoldingRange, FoldingRangeKind,
2121
DiagnosticRelatedInformation, MarkupKind, SymbolKind, DocumentSymbol, CodeAction, SignatureHelpContext, SignatureHelpTriggerKind,
22-
SemanticTokens, InsertTextMode, AnnotatedTextEdit, ChangeAnnotation, CodeDescription
22+
SemanticTokens, InsertTextMode, AnnotatedTextEdit, ChangeAnnotation, CodeDescription, InlayHint, InlayHintLabelPart
2323
} from './services';
2424

2525
export type RecursivePartial<T> = {
@@ -76,6 +76,16 @@ export namespace ProtocolCodeAction {
7676
}
7777
}
7878

79+
export interface ProtocolInlayHint extends monaco.languages.InlayHint {
80+
data?: unknown;
81+
}
82+
export namespace ProtocolInlayHint {
83+
export function is(item: any): item is ProtocolInlayHint {
84+
return !!item && 'data' in item;
85+
}
86+
}
87+
88+
7989
type RangeReplace = { insert: monaco.IRange; replace: monaco.IRange }
8090

8191
function isRangeReplace(v: Partial<monaco.IRange> | RangeReplace): v is RangeReplace {
@@ -127,6 +137,21 @@ export class MonacoToProtocolConverter {
127137
}
128138
}
129139

140+
asLocation(item: monaco.languages.Location): Location;
141+
asLocation(item: undefined | null): undefined;
142+
asLocation(item: monaco.languages.Location | undefined | null): Location | undefined;
143+
asLocation(item: monaco.languages.Location | undefined | null): Location | undefined {
144+
if (!item) {
145+
return undefined;
146+
}
147+
const uri = item.uri.toString();
148+
const range = this.asRange(item.range);
149+
return {
150+
uri,
151+
range
152+
}
153+
}
154+
130155
asTextDocumentIdentifier(model: monaco.editor.IReadOnlyModel): TextDocumentIdentifier {
131156
return {
132157
uri: model.uri.toString()
@@ -493,7 +518,7 @@ export class MonacoToProtocolConverter {
493518
result.diagnostics = this.asDiagnostics(item.diagnostics);
494519
}
495520
if (item.edit) {
496-
throw new Error (`VS Code code actions can only be converted to a protocol code action without an edit.`);
521+
throw new Error(`VS Code code actions can only be converted to a protocol code action without an edit.`);
497522
}
498523
if (item.command) {
499524
result.command = this.asCommand(item.command);
@@ -511,6 +536,34 @@ export class MonacoToProtocolConverter {
511536
}
512537
return result;
513538
}
539+
540+
asInlayHintLabelPart(part: monaco.languages.InlayHintLabelPart): InlayHintLabelPart {
541+
return {
542+
value: part.label,
543+
command: this.asCommand(part.command),
544+
location: this.asLocation(part.location),
545+
tooltip: this.asMarkupContent(part.tooltip)
546+
}
547+
}
548+
549+
asInlayHintLabel(label: string | monaco.languages.InlayHintLabelPart[]): string | InlayHintLabelPart[] {
550+
if (Array.isArray(label)) {
551+
return label.map(part => this.asInlayHintLabelPart(part))
552+
}
553+
return label
554+
}
555+
556+
asInlayHint(item: monaco.languages.InlayHint): InlayHint {
557+
let result = InlayHint.create(
558+
this.asPosition(item.position.lineNumber, item.position.column),
559+
this.asInlayHintLabel(item.label),
560+
item.kind
561+
);
562+
if (ProtocolInlayHint.is(item)) {
563+
if (item.data) { result.data = item.data };
564+
}
565+
return result;
566+
}
514567
}
515568

516569
export class ProtocolToMonacoConverter {
@@ -1235,4 +1288,45 @@ export class ProtocolToMonacoConverter {
12351288
}
12361289
}
12371290

1291+
asInlayHintLabelPart(part: InlayHintLabelPart): monaco.languages.InlayHintLabelPart {
1292+
return {
1293+
label: part.value,
1294+
command: this.asCommand(part.command),
1295+
location: this.asLocation(part.location),
1296+
tooltip: part.tooltip && this.asMarkdownString(part.tooltip)
1297+
}
1298+
}
1299+
1300+
asInlayHintLabel(label: string | InlayHintLabelPart[]): string | monaco.languages.InlayHintLabelPart[] {
1301+
if (Array.isArray(label)) {
1302+
return label.map(part => this.asInlayHintLabelPart(part))
1303+
}
1304+
return label
1305+
}
1306+
1307+
asInlayHint(inlayHint: InlayHint): ProtocolInlayHint {
1308+
return {
1309+
data: inlayHint.data,
1310+
label: this.asInlayHintLabel(inlayHint.label),
1311+
position: this.asPosition(inlayHint.position),
1312+
kind: inlayHint.kind,
1313+
paddingLeft: inlayHint.paddingLeft,
1314+
paddingRight: inlayHint.paddingRight,
1315+
tooltip: inlayHint.tooltip && this.asMarkdownString(inlayHint.tooltip)
1316+
}
1317+
}
1318+
1319+
asInlayHintList(items: InlayHint[]): monaco.languages.InlayHintList;
1320+
asInlayHintList(items: undefined | null): undefined;
1321+
asInlayHintList(items: InlayHint[] | undefined | null): monaco.languages.InlayHintList | undefined;
1322+
asInlayHintList(items: InlayHint[] | undefined | null): monaco.languages.InlayHintList | undefined {
1323+
if (!items) {
1324+
return undefined;
1325+
}
1326+
return {
1327+
hints: items.map((hint) => this.asInlayHint(hint)),
1328+
dispose: () => { }
1329+
};
1330+
}
1331+
12381332
}

packages/client/src/monaco-languages.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
OnTypeFormattingEditProvider, RenameProvider,
1212
DocumentFilter, DocumentSelector, DocumentLinkProvider, ImplementationProvider, TypeDefinitionProvider, DocumentColorProvider,
1313
FoldingRangeProvider, SemanticTokensLegend,
14-
DocumentSemanticTokensProvider, DocumentRangeSemanticTokensProvider, TextDocumentFilter
14+
DocumentSemanticTokensProvider, DocumentRangeSemanticTokensProvider, TextDocumentFilter, InlayHintsProvider
1515
} from "./services";
1616

1717
import { MonacoDiagnosticCollection } from './monaco-diagnostic-collection';
@@ -436,6 +436,36 @@ export class MonacoLanguages implements Languages {
436436
}
437437
}
438438

439+
registerInlayHintsProvider(selector: DocumentSelector, provider: InlayHintsProvider): Disposable {
440+
const inlayHintsProvider = this.createInlayHintsProvider(provider);
441+
return this._monaco.languages.registerInlayHintsProvider(selector, inlayHintsProvider);
442+
}
443+
444+
protected createInlayHintsProvider(provider: InlayHintsProvider): monaco.languages.InlayHintsProvider {
445+
return {
446+
onDidChangeInlayHints: provider.onDidChangeInlayHints,
447+
provideInlayHints: async (model, range, token) => {
448+
const textDocument = this.m2p.asTextDocumentIdentifier(model);
449+
const result = await provider.provideInlayHints({
450+
textDocument,
451+
range: this.m2p.asRange(range)
452+
}, token);
453+
return result && this.p2m.asInlayHintList(result)
454+
},
455+
resolveInlayHint: async (hint: monaco.languages.InlayHint, token) => {
456+
if (provider.resolveInlayHint) {
457+
const documentLink = this.m2p.asInlayHint(hint);
458+
const result = await provider.resolveInlayHint(documentLink, token);
459+
if (result) {
460+
const resolvedInlayHint = this.p2m.asInlayHint(result);
461+
Object.assign(hint, resolvedInlayHint);
462+
}
463+
}
464+
return hint;
465+
}
466+
}
467+
}
468+
439469
protected matchModel(selector: string | DocumentFilter | DocumentSelector, model: MonacoModelIdentifier): boolean {
440470
if (Array.isArray(selector)) {
441471
return selector.some(filter => this.matchModel(filter, model));

packages/client/src/services.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
FoldingRange, FoldingRangeParams, DocumentSymbol, CodeAction,
1818
Declaration, SelectionRangeParams, SelectionRange, SemanticTokensParams,
1919
SemanticTokens, SemanticTokensEdit, SemanticTokensLegend, SemanticTokensRangeParams,
20-
SemanticTokensDeltaParams
20+
SemanticTokensDeltaParams, InlayHint, InlayHintParams
2121
} from 'vscode-languageserver-protocol';
2222

2323
import { TextDocument } from 'vscode-languageserver-textdocument';
@@ -208,6 +208,12 @@ export interface DocumentRangeSemanticTokensProvider {
208208
provideDocumentRangeSemanticTokens(params: SemanticTokensRangeParams, token: CancellationToken): ProviderResult<SemanticTokens>;
209209
}
210210

211+
export interface InlayHintsProvider<T extends InlayHint = InlayHint> {
212+
onDidChangeInlayHints?: Event<void>;
213+
provideInlayHints(params: InlayHintParams, token: CancellationToken): ProviderResult<T[]>;
214+
resolveInlayHint?(hint: T, token: CancellationToken): ProviderResult<T>;
215+
}
216+
211217
export interface Languages {
212218
match(selector: DocumentSelector, document: DocumentIdentifier): boolean;
213219
createDiagnosticCollection?(name?: string): DiagnosticCollection;
@@ -234,6 +240,7 @@ export interface Languages {
234240
registerSelectionRangeProvider?(selector: DocumentSelector, provider: SelectionRangeProvider): Disposable;
235241
registerDocumentSemanticTokensProvider?(selector: DocumentSelector, provider: DocumentSemanticTokensProvider, legend: SemanticTokensLegend): Disposable;
236242
registerDocumentRangeSemanticTokensProvider?(selector: DocumentSelector, provider: DocumentRangeSemanticTokensProvider, legend: SemanticTokensLegend): Disposable;
243+
registerInlayHintsProvider(selector: DocumentSelector, provider: InlayHintsProvider): Disposable;
237244
}
238245

239246
export interface TextDocumentDidChangeEvent {

packages/client/src/vscode-api.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -807,15 +807,36 @@ export function createVSCodeApi(servicesProvider: Services.Provider): typeof vsc
807807
}
808808
}, legend)
809809
},
810+
registerInlayHintsProvider(selector: vscode.DocumentSelector, provider: vscode.InlayHintsProvider) {
811+
const documentSelector = Array.isArray(selector) ? selector : [selector]
812+
if (!DocumentSelector.is(documentSelector)) {
813+
throw new Error('unexpected selector: ' + JSON.stringify(selector));
814+
}
815+
const { languages } = servicesProvider();
816+
if (!languages.registerInlayHintsProvider) {
817+
return Disposable.create(() => { });
818+
}
819+
820+
const resolveInlayHint = provider.resolveInlayHint;
821+
return languages.registerInlayHintsProvider(documentSelector, {
822+
onDidChangeInlayHints: provider.onDidChangeInlayHints,
823+
provideInlayHints({ textDocument, range }, token) {
824+
return provider.provideInlayHints(<any>textDocument, <any>range, token) as any
825+
},
826+
resolveInlayHint: resolveInlayHint ? (link, token) => {
827+
return resolveInlayHint(<any>link, token) as any
828+
} : undefined
829+
})
830+
},
831+
// FIXME: Should be implemented with monaco 0.34
832+
registerInlineValuesProvider: unsupported,
810833
getLanguages: unsupported,
811834
setTextDocumentLanguage: unsupported,
812835
getDiagnostics: unsupported,
813836
setLanguageConfiguration: unsupported,
814837
onDidChangeDiagnostics: unsupported,
815838
registerLinkedEditingRangeProvider: unsupported,
816839
createLanguageStatusItem: unsupported,
817-
registerInlineValuesProvider: unsupported,
818-
registerInlayHintsProvider: unsupported,
819840
registerTypeHierarchyProvider: unsupported
820841
};
821842
function showMessage(type: MessageType, arg0: any, ...arg1: any[]): Thenable<any> {

0 commit comments

Comments
 (0)