Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
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;
}
123 changes: 123 additions & 0 deletions protocol/src/common/proposed.inlayHints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#### <a href="#textDocument_inlayHints" name="textDocument_inlayHints" class="anchor">Inlay hints</a>

Inlay hints are short textual annotations that are attached to points in the source code.
These typically spell out some inferred information, such as the parameter name when passing a value to a function.

```typescript
/**
* Well-known kinds of information conveyed by InlayHints.
* Clients may choose which categories to display according to user preferences.
*/
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'
}
```

The `textDocument/inlayHints` request is sent from the client to the server to retrieve inlay hints for a document.

_Client Capabilities_:

* property name (optional): `textDocument.inlayHints`
* property type: `InlayHintsClientCapabilities` defined as follows:

```typescript
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;
}
```

_Server Capability_:

* property name (optional): `inlayHintsProvider`
* property type: `boolean | InlayHintsOptions | InlayHintsRegistrationOptions` is defined as follows:

```typescript
export interface InlayHintsOptions extends WorkDoneProgressOptions {
}
```

_Registration Options_: `InlayHintsRegistrationOptions` defined as follows:

```typescript
export interface InlayHintsRegistrationOptions extends
TextDocumentRegistrationOptions, InlayHintsOptions {
}
```

_Request_:

* method: `textDocument/inlayHints`
* params: `InlayHintsParams` defined as follows:

```typescript
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[];
}
```

_Response_:

* result: `InlayHint[]`
* partial result: `InlayHint[]`
* error: code and message set in case an exception happens during the 'textDocument/inlayHint' request

`InlayHint` is defined as follows:

```typescript
/**
* An inlay hint is a short textual annotation for a range of source code.
*/
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;
}
```

**TODO**: Do we need a `/refresh` server->client call, like with SemanticTokens and Code Lens?

**TODO**: Likely future evolution: add a `/resolve` call enabling interactions with hints. Should we foreshadow this?
Loading