Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add textDocument/inlayHints #1249

Closed
wants to merge 9 commits into from
2 changes: 2 additions & 0 deletions _data/specification-3-17-toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@
anchor: textDocument_linkedEditingRange
- title: moniker
anchor: textDocument_moniker
- title: inlay hints
anchor: textDocument_inlayHints
- title: Change Log
anchor: changeLog
children:
Expand Down
177 changes: 177 additions & 0 deletions _specifications/specification-3-17.md
Original file line number Diff line number Diff line change
Expand Up @@ -8217,6 +8217,182 @@ export interface Moniker {

Server implementations of this method should ensure that the moniker calculation matches to those used in the corresponding LSIF implementation to ensure symbols can be associated correctly across IDE sessions and LSIF indexes.

#### <a href="#textDocument_inlayHints" name="textDocument_inlayHints" class="anchor">Inlay hints (:leftwards_arrow_with_hook:)</a>

> *Since version 3.17.0*

Inlay hints are short textual annotations that are attached to ranges in the source code.

**TODO**: Is "inlay hints" too presentational a name? The protocol allows other UI (e.g. pop-up when cursor is in the target range).

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.
* This is not an exhaustive list, servers may use other categories as needed.
*/
export enum InlayHintCategory {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are those categories necessary for a 1st iteration? I don't see categories in "Hover" nor "CodeLens" for instance. I think a 1st iteration should take place without them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, good question. These are present in both VSCode's proposed API and rust-analyzer (though Rust's categories have some overlap with classes, too)

I see two main purposes:

  • allow users to selectively turn on/off hints by category. (This could also/instead be done by hintClass, but it seems more complex).
  • style hints differently (e.g. a subtle color tint on the chips). This seems useful but I haven't seen it in the wild, maybe it isn't?

I find the configurability argument quite compelling, as this is a highly invasive/visible feature.
Categories don't add a lot of spec complexity, and are optional for both client & server.
So I'm inclined to include them but not wedded to it, definitely want to hear more opinions.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the configurability argument quite compelling, as this is a highly invasive/visible feature.

The thing is that to properly handle it, it would require tools to dynamically generate some page which lists the available categories in the LS and allow to enable/disable some of them. The LS would require to allow more introspective than usual; categories/kinds are usually hardcoded pre-identified enums of things shared by many languages more than dynamically filled placeholders for LS-specific stuff.
If we need to distinguish different kinds, I think it's better to do it just like Completion and other do: the protocol defines a static list of supported kinds; that clients can then code a behavior for.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that was definitely the intention here: clients could show fixed checkboxes for Params/Types/Other.
(In fact I thought about using classes for configuration, but it seems too complicated for exactly the reason you mention)

The reason to allow other values is just to make protocol versioning/extensions easier:

  • avoid deadlock where neither clients/servers/spec feel they should be the first to support a new category
  • avoid complicated capability negotiation (completionItemKind.valueSet) to ensure back-compatibility
    It's possible different servers use the same non-standard category name to mean different things, I don't think this is a large risk.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I've tweaked some comments and such, trying to make this clearer)

/**
* The range is an expression passed as an argument to a function.
* The label is the name of the parameter.
*/
'parameter',
/**
* The range is an entity whose type is unknown.
* The label is its inferred type.
*/
'type'
}
```

Hints may be grouped into server-defined _classes_.
Hints in the same class are displayed in similar ways, and the class avoids duplicating this information in each hint.

**TODO**: classes might be useful "subcategories" for users to enable/disable hints. If so, they'd need a description.

**TODO**: are classes actually useful? should we just inline this info into the hints?

```typescript
export interface InlayHintClass {
/**
* The kind of information this hint conveys.
* May be an InlayHintCategory or any other value.
*/
category?: string;

/**
* The placement of the label, when displayed inline.
* "start" and "end" mean insertion at the endpoints of the target range.
* Default is "start".
*/
position?: "start" | "end";

/**
* Text to be displayed before the label when displayed inline.
* Typically this is punctuation allowing it to read naturally as code.
*/
prefix?: string;
/**
* Text to be displayed after the label when displayed inline.
*/
suffix?: string;
}
```

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 {
/**
* Defines the classes of hints the server will provide.
* Individual hints may reference one of these by index.
*/
hintClasses?: InlayHintClass[],

/**
* Whether the server supports retrieving inlay hints for a limited range
* within a document.
*/
range?: boolean;
}
```

_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.
* Servers that do not set InlayHintsOptions.range may ignore this.
*
* TODO: it's more common in LSP to have a separate request - do we need to?
*/
range?: Range;

// TODO: is 'only' useful in practice?
}
```

_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 value to be shown.
*/
label: string;

/**
* The range of text this hint is attached to.
* May affect when the hint is displayed, and the effect of selecting it.
*/
target: Range;

/**
* References an InlayHintClass that provides additional properties.
* This is an index into InlayHintsOptions.hintClasses.
*/
hintClass?: uinteger

/**
* The placement of the label, when displayed inline.
* This overrides the default value set by the class.
*/
position?: Position;
}
```

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

### <a href="#implementationConsiderations" name="implementationConsiderations" class="anchor">Implementation Considerations</a>

Language servers usually run in a separate process and client communicate with them in an asynchronous fashion. Additionally clients usually allow users to interact with the source code even if request results are pending. We recommend the following implementation pattern to avoid that clients apply outdated response results:
Expand All @@ -8240,6 +8416,7 @@ Servers usually support different communication channels (e.g. stdio, pipes, ...
#### <a href="#version_3_17_0" name="version_3_17_0" class="anchor">3.17.0 (xx/xx/xxxx)</a>

* Add support for a completion item label details.
* Add support for inlay hints.

#### <a href="#version_3_16_0" name="version_3_16_0" class="anchor">3.16.0 (12/14/2020)</a>

Expand Down