This document contains notes for language client developers.
The following file types are currently supported and language IDs expected:
terraform
- standard*.tf
config filesterraform-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.
Read more about how we recommend Terraform files to be highlighted in syntax-highlighting.md.
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.
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
.
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.
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.
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.
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
.
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.
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.
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.
Clients are encouraged to implement custom commands in a command palette or similar functionality.
See ./commands.md for more.
See ./telemetry.md.