Skip to content

Commit

Permalink
Add proposed textDocument/inlayHints to protocol & client.
Browse files Browse the repository at this point in the history
This addresses the FR microsoft/language-server-protocol#956
And corresponds to the spec in microsoft/language-server-protocol#1249
  • Loading branch information
sam-mccall committed May 29, 2021
1 parent 2645fb5 commit 2d8e6da
Show file tree
Hide file tree
Showing 5 changed files with 308 additions and 1 deletion.
5 changes: 4 additions & 1 deletion client/src/common/commonClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,13 @@ export abstract class CommonLanguageClient extends BaseLanguageClient {
// Exporting proposed protocol.

import { DiagnosticFeature } from './proposed.diagnostic';
import { InlayHintsFeature } from './proposed.inlayHints';

export namespace ProposedFeatures {
export function createAll(_client: BaseLanguageClient): (StaticFeature | DynamicFeature<any>)[] {
let result: (StaticFeature | DynamicFeature<any>)[] = [
new DiagnosticFeature(_client)
new DiagnosticFeature(_client),
new InlayHintsFeature(_client),
];
return result;
}
Expand Down
92 changes: 92 additions & 0 deletions client/src/common/proposed.inlayHints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */

import { languages as Languages, Disposable, TextDocument, Range, ProviderResult, InlayHintKind, InlayHint as VInlayHint, InlayHintsProvider } from 'vscode';

import {
ClientCapabilities, CancellationToken, ServerCapabilities, DocumentSelector, Proposed
} from 'vscode-languageserver-protocol';

import { TextDocumentFeature, BaseLanguageClient, Middleware } from './client';
import * as p2c from './protocolConverter';

function ensure<T, K extends keyof T>(target: T, key: K): T[K] {
if (target[key] === void 0) {
target[key] = {} as any;
}
return target[key];
}

export interface ProvideInlayHintsSignature {
(this: void, document: TextDocument, range: Range, token: CancellationToken): ProviderResult<VInlayHint[]>;
}

export interface InlayHintsMiddleware {
provideInlayHints?: (this: void, document: TextDocument, range: Range, token: CancellationToken, next: ProvideInlayHintsSignature) => ProviderResult<VInlayHint[]>;
}

namespace protocol2code {
function asInlayHintKind(_: p2c.Converter, value: string | null | undefined) : InlayHintKind | undefined {
switch (value) {
case Proposed.InlayHintCategory.Parameter:
return InlayHintKind.Parameter;
case Proposed.InlayHintCategory.Type:
return InlayHintKind.Type;
default:
return InlayHintKind.Other;
}
}
export function asInlayHint(converter: p2c.Converter, item: Proposed.InlayHint) : VInlayHint {
const result = new VInlayHint(item.label.trim(), converter.asPosition(item.position), asInlayHintKind(converter, item.category));
result.whitespaceBefore = item.label.startsWith(' ');
result.whitespaceAfter = item.label.endsWith(' ');
return result;
}
}

export class InlayHintsFeature extends TextDocumentFeature<boolean | Proposed.InlayHintsOptions, Proposed.InlayHintsRegistrationOptions, InlayHintsProvider> {

constructor(client: BaseLanguageClient) {
super(client, Proposed.InlayHintsRequest.type);
}

public fillClientCapabilities(capabilities: ClientCapabilities & Proposed.$InlayHintsClientCapabilities): void {
let capability = ensure(ensure(capabilities, 'textDocument')!, 'inlayHints')!;
capability.dynamicRegistration = true;
}

public initialize(capabilities: ServerCapabilities & Proposed.$InlayHintsServerCapabilities, documentSelector: DocumentSelector): void {
let [id, options] = this.getRegistration(documentSelector, capabilities.inlayHintsProvider);
if (!id || !options) {
return;
}
this.register({ id: id, registerOptions: options });
}

protected registerLanguageProvider(options: Proposed.InlayHintsRegistrationOptions): [Disposable, InlayHintsProvider] {
const provider: InlayHintsProvider = {
provideInlayHints: (document, range, token) => {
const client = this._client;
const provideInlayHints: ProvideInlayHintsSignature = (document, range, token) => {
const requestParams: Proposed.InlayHintsParams = {
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
range: client.code2ProtocolConverter.asRange(range)
};
return client.sendRequest(Proposed.InlayHintsRequest.type, requestParams, token).then(
(m: Proposed.InlayHint[]) => m.map(h => protocol2code.asInlayHint(client.protocol2CodeConverter, h)),
(error: any) => {
return client.handleFailedRequest(Proposed.InlayHintsRequest.type, token, error, null);
}
);
};
const middleware = client.clientOptions.middleware as (Middleware & InlayHintsMiddleware) | undefined;
return middleware?.provideInlayHints
? middleware.provideInlayHints(document, range, token, provideInlayHints)
: provideInlayHints(document, range, token);
}
};
return [Languages.registerInlayHintsProvider(options.documentSelector!, provider), provider];
}
}
81 changes: 81 additions & 0 deletions client/typings/vscode-proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,85 @@ declare module 'vscode' {
// todo@API proper event type
export const onDidChangeOpenEditors: Event<void>;
}

//#region https://github.com/microsoft/vscode/issues/16221

// todo@API Split between Inlay- and OverlayHints (InlayHint are for a position, OverlayHints for a non-empty range)
// todo@API add "mini-markdown" for links and styles
// (done) remove description
// (done) rename to InlayHint
// (done) add InlayHintKind with type, argument, etc

export namespace languages {
/**
* Register a inlay hints provider.
*
* Multiple providers can be registered for a language. In that case providers are asked in
* parallel and the results are merged. A failing provider (rejected promise or exception) will
* not cause a failure of the whole operation.
*
* @param selector A selector that defines the documents this provider is applicable to.
* @param provider An inlay hints provider.
* @return A {@link Disposable} that unregisters this provider when being disposed.
*/
export function registerInlayHintsProvider(selector: DocumentSelector, provider: InlayHintsProvider): Disposable;
}

export enum InlayHintKind {
Other = 0,
Type = 1,
Parameter = 2,
}

/**
* Inlay hint information.
*/
export class InlayHint {
/**
* The text of the hint.
*/
text: string;
/**
* The position of this hint.
*/
position: Position;
/**
* The kind of this hint.
*/
kind?: InlayHintKind;
/**
* Whitespace before the hint.
*/
whitespaceBefore?: boolean;
/**
* Whitespace after the hint.
*/
whitespaceAfter?: boolean;

// todo@API make range first argument
constructor(text: string, position: Position, kind?: InlayHintKind);
}

/**
* The inlay hints provider interface defines the contract between extensions and
* the inlay hints feature.
*/
export interface InlayHintsProvider {

/**
* An optional event to signal that inlay hints have changed.
* @see {@link EventEmitter}
*/
onDidChangeInlayHints?: Event<void>;

/**
*
* @param model The document in which the command was invoked.
* @param range The range for which inlay hints should be computed.
* @param token A cancellation token.
* @return A list of inlay hints or a thenable that resolves to such.
*/
provideInlayHints(model: TextDocument, range: Range, token: CancellationToken): ProviderResult<InlayHint[]>;
}
//#endregion
}
15 changes: 15 additions & 0 deletions protocol/src/common/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,19 @@ export namespace Proposed {
export type WorkspaceDiagnosticReport = diag.WorkspaceDiagnosticReport;
export type WorkspaceDiagnosticReportPartialResult = diag.WorkspaceDiagnosticReportPartialResult;
export const DiagnosticRefreshRequest: typeof diag.DiagnosticRefreshRequest = diag.DiagnosticRefreshRequest;
}

import * as inlay from './proposed.inlayHints';

export namespace Proposed {
export type InlayHintCategory = inlay.InlayHintCategory;
export const InlayHintCategory = inlay.InlayHintCategory;
export type InlayHint = inlay.InlayHint;
export type InlayHintsClientCapabilities = inlay.InlayHintsClientCapabilities;
export type InlayHintsOptions = inlay.InlayHintsOptions;
export type InlayHintsRegistrationOptions = inlay.InlayHintsRegistrationOptions;
export type $InlayHintsClientCapabilities = inlay.$InlayHintsClientCapabilities;
export type $InlayHintsServerCapabilities = inlay.$InlayHintsServerCapabilities;
export type InlayHintsParams = inlay.InlayHintsParams;
export const InlayHintsRequest: typeof inlay.InlayHintsRequest = inlay.InlayHintsRequest;
}
116 changes: 116 additions & 0 deletions protocol/src/common/proposed.inlayHints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */

import { ProtocolRequestType } from './messages';
import { Position, Range, TextDocumentIdentifier } from 'vscode-languageserver-types';
import {
WorkDoneProgressOptions, WorkDoneProgressParams, PartialResultParams, TextDocumentRegistrationOptions, TextDocumentClientCapabilities
} from './protocol';

/**
* Well-known kinds of information conveyed by InlayHints.
* Clients may choose which categories to display according to user preferences.
*
* @since 3.17.0
*/
export enum InlayHintCategory {
/**
* The range is an expression passed as an argument to a function.
* The label is the name of the parameter.
*/
Parameter = 'parameter',
/**
* The range is an entity whose type is unknown.
* The label is its inferred type.
*/
Type = 'type'
}

/**
* An inlay hint is a short textual annotation for a range of source code.
*
* @since 3.17.0
*/
export interface InlayHint {
/**
* The text to be shown.
*/
label: string;

/**
* The position within the code this hint is attached to.
*/
position: Position;

/**
* The kind of information this hint conveys.
* May be an InlayHintCategory or any other value, clients should treat
* unrecognized values as if missing.
*/
category?: string;
}


/**
* Client capabilities specific to the inlayHints request.
*
* @since 3.17.0
*/
export interface InlayHintsClientCapabilities {
/**
* Whether implementation supports dynamic registration. If this is set to
* `true` the client supports the new `(TextDocumentRegistrationOptions &
* StaticRegistrationOptions)` return value for the corresponding server
* capability as well.
*/
dynamicRegistration?: boolean;
}

export interface $InlayHintsClientCapabilities {
textDocument?: TextDocumentClientCapabilities & {
inlayHints?: InlayHintsClientCapabilities;
}
}

export interface InlayHintsServerCapabilities {
}

export interface InlayHintsOptions extends WorkDoneProgressOptions {
}

export interface InlayHintsRegistrationOptions extends TextDocumentRegistrationOptions, InlayHintsOptions {
}

export interface $InlayHintsServerCapabilities {
inlayHintsProvider?: InlayHintsOptions;
}

export interface InlayHintsParams extends WorkDoneProgressParams, PartialResultParams {
/**
* The text document.
*/
textDocument: TextDocumentIdentifier;

/**
* The range the inlay hints are requested for.
* If unset, returns all hints for the document.
*/
range?: Range;

/**
* The categories of inlay hints that are interesting to the client.
* The client should filter out hints of other categories, so the server may
* skip computing them.
*/
only?: string[];
}

/**
* The `textDocument/inlayHints` request is sent from the client to the server to retrieve inlay hints for a document.
*/
export namespace InlayHintsRequest {
export const method: 'textDocument/inlayHints' = 'textDocument/inlayHints';
export const type = new ProtocolRequestType<InlayHintsParams, InlayHint[], InlayHint[], void, InlayHintsRegistrationOptions>(method);
}

0 comments on commit 2d8e6da

Please sign in to comment.