Browse the user guide

Language servers

Connect installed language servers for completion, diagnostics, and code actions.

Canopus uses separately installed language servers over LSP 3.17. Configure a server with an argument array or an options object:

{
  "language_servers": {
    "ruby": {
      "command": ["ruby-lsp"],
      "env": {"RUBYOPT": null},
      "initialization_options": {},
      "configuration": {"rubyLsp": {"enabledFeatures": {"diagnostics": true}}}
    }
  }
}

languages.<language>.language_servers.<language> overrides the global entry. An absent or null entry auto-detects the language’s default executable; it does not disable the server. If no executable is found, Canopus leaves that language without a server. Invalid settings keep the previous working connection.

Configure multiple ordered servers with option objects and explicit feature routing. The first matching server handles single-result features such as formatting and definition; completion, diagnostics, and code actions are merged from every matching server:

{
  "language_servers": {
    "ruby": [
      {"command": ["ruby-lsp"], "features": ["completion", "definition", "hover", "formatting"]},
      {"command": ["rubocop", "--lsp"], "features": ["diagnostics", "codeAction"]}
    ]
  }
}

Omit features to route from negotiated server capabilities. The legacy argument array ("ruby": ["ruby-lsp"]) remains a single-server command. The status bar shows each connection, and language.restart_server restarts one selected server without disturbing the others.

Changes to the command, environment, or initialization options restart the connection and reopen its documents. A configuration-only change sends workspace/didChangeConfiguration without restarting. Resource-changing server requests require confirmation as described in workspace edits.

Save-time actions are opt-in and run before the file write. Formatting runs first, followed by the first enabled action returned for each configured kind:

{
  "format_on_save": true,
  "code_actions_on_save": ["source.organizeImports", "source.fixAll"],
  "format_on_save_timeout": 2000
}

The timeout covers the complete sequence. A timeout, stale or invalid edit, or server failure is reported without preventing the save; edits to the saved document accepted before a later failure remain grouped as one undo step. Server edits use the normal version-checked workspace-edit path. Resource operations are rejected during an automatic save action. An untitled document establishes its URI on the first Save As, so its save-time actions begin with the next save.

Published diagnostics are shown as severity-colored wave underlines. The most severe message on each line is also shown at line end by default. Configure this with diagnostics.inline, diagnostics.inline_max_length, and the minimum diagnostics.severity (error, warning, information, or hint).

The Problems panel groups validated local-file diagnostics by file and also accepts task and test sources through the diagnostics registry. Selecting a problem opens its exact source position. problems.filter filters the tree by free text and optional terms such as severity:warning and source:task; the status bar shows current error and warning totals.

Task and test integrations publish the same validated LSP-shaped records with workspace.diagnostics.publish(:task, uri, diagnostics) or :test. Publishing an empty array removes that source’s records for the URI.

Inlay hints are fetched for the visible source lines plus 50 lines of context and shown inline. Label parts with a server-provided location are clickable. Configure type and parameter-name hints, or their display limit, with inlay_hints.types, inlay_hints.parameter_names, and inlay_hints.max_length; set inlay_hints.enabled to false to disable requests.

Code lenses are cached for each document version and shown above their source line. Unresolved lenses are resolved only as they enter the viewport; click a lens to execute its server command. Set code_lens.enabled to false to disable requests and hide cached lenses.

When supported, textDocument/documentHighlight automatically marks reads, writes, and textual occurrences at each visible editor’s caret. Results are kept per split and caret, and are discarded when the caret or document changes, the tab closes, or the language server is replaced.

textDocument/documentLink results are underlined and clickable in each visible editor. Links without a target are resolved only when clicked. Canopus opens HTTP(S) targets through the platform and local file targets in an editor; remote file URIs and executable or custom URI schemes are rejected. Cached links and pending resolutions are discarded after edits, hidden tabs, document close, settings changes, or server replacement.

editor.fold prefers cached textDocument/foldingRange results and folds the smallest range containing the caret’s source line. Servers without folding support, null or failed responses, and unconfigured languages fall back to asynchronous Antares structure analysis; the UI thread never waits for either source.

Alt-Up expands every selection through its nested textDocument/selectionRange chain and Alt-Down restores the exact previous selection. Unsupported, null, failed, or invalid responses fall back to asynchronous Antares structure analysis. Move-line shortcuts use Alt-Shift-Up and Alt-Shift-Down.

F2 asks servers advertising rename preparation to validate the exact document version and caret before opening the rename dialog. The prepared range, placeholder, or client-side identifier pre-fills the dialog; edits, caret moves, hidden tabs, server replacement, and settings reload cancel the snapshot. Servers without preparation support, and unconfigured languages, keep the regular rename dialog.

Run language.linked_editing from the command palette to request textDocument/linkedEditingRange. Canopus maps the current primary selection’s relative anchor and caret to every validated, non-overlapping linked range and creates multiple selections. The request is cancelled when the selection, document, visible tab, settings, or server changes. Continuous linked editing is intentionally left to the editor’s normal multi-selection behavior. If no server is available, or the server does not support the request, returns null, fails, or times out, HTML and XML use a bounded fallback for unambiguous matching opening and closing tag names. HTML names are ASCII case-insensitive; XML names are exact and may contain Unicode. Self-closing and HTML void elements are not linked.

Run language.call_hierarchy or language.type_hierarchy from the command palette to prepare a hierarchy at the caret. If the server returns more than one root, choose one from the palette. The right-side Hierarchy panel loads incoming and outgoing calls, or supertypes and subtypes, only as branches are expanded. Selecting an item opens its local file and source range; remote file URIs are rejected.

Press Cmd-T on macOS or Ctrl-T elsewhere, or run language.workspace_symbols, and enter a query. Canopus requests workspace/symbol from every active server routed to workspaceSymbol under one ten-second deadline, keeps configuration order while removing duplicate results, and uses Spica to refine the returned locations. Selecting a result opens its local file and range. If no routed server is running, or every routed provider fails, a background Alkaid content search supplies bounded local-file matches using the project’s ignore rules. Queries are limited to 256 bytes and results to 10,000 entries.

Sticky scroll prefers textDocument/documentSymbol when the server advertises it and uses cached Antares structure regions otherwise. Requests and normalized symbol trees are tied to the current buffer version and discarded after edits, document close, or server retirement; painting never waits for either source.

Editor positions use UTF-8 byte offsets internally and are converted to LSP UTF-16 positions. Servers selecting another position encoding are rejected. Messages are limited to 32 MiB, crashes fail pending requests, and a connection is restarted at most three times.

Developers can test installed servers with:

ruby script/lsp_interop.rb ruby rust typescript go