Skip to content

Latest commit

 

History

History
197 lines (127 loc) · 12.3 KB

language-clients.md

File metadata and controls

197 lines (127 loc) · 12.3 KB

Language Client Implementation Notes

This document contains notes for language client developers.

Language IDs

The following file types are currently supported and language IDs expected:

  • terraform - standard *.tf config files
  • terraform-vars - variable files (*.tfvars)

Client can choose to highlight other files locally, but such other files must not be send to the server as the server isn't equipped to handle those.

Clients specifically should not send *.tf.json, *.tfvars.json nor Packer HCL config nor any other HCL config files as the server is not equipped to handle these file types.

Configuration

Unless the client allows the end-user to pass arbitrary config options (e.g. generic Sublime Text LSP package without Terraform LSP package), the client should expose configuration as per SETTINGS.md.

Client should match the option names exactly, and if possible match the underlying data structures too. i.e. if a field is documented as ignoreDirectoryNames, it should be exposed as ignoreDirectoryNames, not IgnoreDirectoryNames, or ignore_directory_names. This is to avoid user confusion when the server refers to any config option in informative, warning, or error messages.

Client may use a flat structure using the . (single dot) as a separator between the object name and option nested under it, such as { "foo.bar": "..." } instead of { "foo": { "bar": "..." } }. This is acceptable in situations when using objects is not possible or practical (e.g. VS Code wouldn't display objects in the Settings UI).

The server will generally refer to options using the . address, for simplicity and avoidance of doubts.

Watched Files

The server (>= 0.28.0) supports workspace/didChangeWatchedFiles notifications. This allows IntelliSense to remain accurate e.g. when switching branches in VCS or when there are any other changes made to these files outside the editor.

If the client implements file watcher, it should watch for any changes in **/*.tf and **/*.tfvars files in the workspace.

Client should not send changes for any other files.

Syntax Highlighting

Read more about how we recommend Terraform files to be highlighted in syntax-highlighting.md.

Internal parser

The server expects clients to use standard text synchronization LSP methods for synchronizing the above supported files.

Server will itself parse the whole module in order to provide completion/hover and referencing throughout the module, not just within opened files.

As a result the server maintains an "overlay virtual filesystem" for any files that client sends via LSP and where appropriate such files are treated as the main source of truth, so that functionality can be provided even before files are saved to disk.

Use Client/Server Capabilities

Please always make sure that your client reads and reflects ServerCapabilities and never makes blind assumptions about what is or is not supported.

The server will always read ClientCapabilities and make decisions about whether to provide any LSP features based on those capabilities, so make sure these are accurate.

For example the server will not provide completion snippets unless the client explicitly communicates it supports them via CompletionClientCapabilities.

Multiple Folders

Language server supports multiple folders natively from version 0.19.

Client is expected to always launch a single instance of the server and check for workspace.workspaceFolders.supported server capability, and then:

  • launch any more instances (one instance per folder) if multiple folders are not supported
  • avoid launching any more instances if multiple folders are supported

It is assumed that paths to these folders will be provided as part of workspaceFolders in the initialize request per LSP.

Code Actions

The server implements a set of opt-in code actions which perform different actions for the user. The code action request is sent from the client to the server to compute commands for a given text document and range. These commands are typically code fixes to either fix problems or to beautify/refactor code.

See code-actions for a list of supported code actions.

A Code Action is an action that changes content in the active editor. Each Code Action is grouped into kinds that have a command and/or a series of edits. They are triggered either by the user or through events.

Documentation for code actions outside of VS Code is unfortunately very limited beyond description of the LSP methods. VS Code internally makes certain assumptions. We follow these assumptions (as documented below) and we recommend other clients to follow these assumptions for best experience, unless/until LSP documentation recommends otherwise.

Triggers

In VS Code, code action can be invoked manually or automatically based on the respective CodeActionTriggerKind.

Manually invoked actions come from the contextual in-line💡 icon inside the editor, and are chosen by the user. The user can choose which action is invoked and then invoke it. However, in order for the client to display the contextual actions, the client requests LS to "pre-calculate" any actions relevant to the cursor position. Then, when the user selects the action in the UI, the client applies the edits or executes the command as provided by the server.

Automatically triggered actions come from events such as "on save", as configured via the editor.codeActionsOnSave setting. These usually do not give much choice to the user, they are either on or off, as they cannot accept user input. For example, formatting a document or removing simple style errors don't prompt for user action before or during execution.

Kinds

Each Code Action has a CodeActionKind. Code Action Kinds are a hierarchical list of identifiers separated by .. For example in refactor.extract.function: refactor is the trunk, extract is the branch, and function is the leaf. The branches and leaves are the point of intended customization, you add new branches and/or leaves for each type of function you perform. In this example, a new code action that operated on variables would be called refactor.extract.variable.

Custom Kinds

Adding new roots or branches of the hierarchy is not emphasized or really encouraged. In most cases they are meant to be concepts that can be applied generically, not specifically to a certain language. For example, extracting a value to a variable is something common to most languages. You do not need a refactor.extract.golang.variable to extract a variable in Go, it still makes sense to use refactor.extract.variable whether in Go or in PowerShell.

Keeping to existing kinds also helps in registration of supported code actions. If you register support for source.fixAll instead of source.fixAll.languageId, then a user that has source.fixAll in their codeActionsOnSave does not need to add specific lines for your extension, all supported extensions are called on save. Another reason is VS Code won't list your custom supported code actions inside editor.codeActionsOnSave, and there isn't a way for the extension to get them there. The user will have to consult your documentation to find out what actions are supported and add them without intellisense.

A reason to add custom kinds is if the action is sufficiently different from an existing base action. For example, formatting of the current file on save. The interpretation of source.fixAll is to apply any/all actions that address an existing diagnostic and have a clear fix that does not require user input. Formatting therefore doesn't fit the interpretation of source.fixAll.

A custom kind source.formatAll.terraform may format code. A user can request both source.fixAll and source.formatAll.terraform via their editor/client settings and the server would run source.formatAll.terraform only. Other servers may run source.fixAll but not source.formatAll.terraform, assuming they do not support that custom code action kind.

Unlike generic kinds, custom ones are only discoverable in server-specific documentation and only relevant to the server.

Execution of Code Actions

A request can have zero or more code actions to perform and the LS is responsible for processing all requested actions. The client will send a list of any code actions to execute (which may also be empty).

An empty list means execute anything supported and return the list of edits to the client. This often comes from manually invoked actions by the user. This is the easiest situation for the LS to choose what to do. The LS has a list of supported actions it can execute, so it executes and returns the edits. However, such actions will not include any that either require user input or make a change that could introduce an error (creating files, changing code structure, etc).

A list of actions means to execute anything in that list, which is actually interpreted as execute the hierarchy of a code action from these actions in the list. For example, if a client requests a refactor action the LS should return all of the possible refactor actions it can execute (refactor.extract, refactor.inline, refactor.rewrite, etc.), and the user can choose which to execute. A client that sends refactor.extract.method would receive just that single code action from the LS. So a request with one action could return ten results, or just one.

Clients are expected to filter actions to only send unique ones. For example, the user may have configured both source.fixAll and source.fixAll.eslint which are equivalent from the perspective of a single server (eslint). The client should favour the most specific (in this case source.fixAll.eslint) if possible, such that the server doesn't have to perform any de-duplication and the same action doesn't run multiple times.

Clients may also impose a timeout for returning response for any of these requests. If the LS takes too long to process and return an action, the client may either give up and not do anything, or (preferably) display a progress indicator. This timeout may be configurable by the user, but ideally the default one is sufficient.

Code Lens

Reference Counts (opt-in)

The server implements an opt-in code lens which displays number of references to any "root level" targettable block or attribute, such as local value, variable, resource etc.

LSP has not standardized client-side command IDs nor does it provide mechanism for negotiating what the right command ID is and whether it's available. This is why client has to opt-in by providing a command ID in experimental client capabilities.

For example:

{
    "capabilities": {
        "experimental": {
            "showReferencesCommandId": "client.showReferences"
        }
    }
}

This enables the code lens.

The client-side command is executed with 2 arguments (position, reference context):

[
    {
        "line": 0,
        "character": 8
    },
    {
        "includeDeclaration": false
    }
]

These arguments are to be passed by the client to a subsequent textDocument/references request back to the server to obtain the list of references relevant to that position and finally display received references in the editor.

See example implementation in the Terraform VS Code extension.

Custom Commands

Clients are encouraged to implement custom commands in a command palette or similar functionality.

See ./commands.md for more.

Telemetry

See ./telemetry.md.