Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Diagnostics and Formatting

In this chapter you learn how to read the errors and warnings a language server reports, and how to ask for formatting and structural ranges. Diagnostics are what turn an assistant’s “this should compile” into a checked fact.

Prerequisites

get_diagnostics

Returns the errors, warnings and hints for one file, merged from a fresh pull request and the notifications the server has pushed (for example clippy results from rust-analyzer).

{ "file_path": "/work/app/src/main.rs" }
{
  "diagnostics": [
    {
      "range": {
        "start": { "line": 10, "character": 8 },
        "end": { "line": 10, "character": 24 }
      },
      "severity": "error",
      "message": "cannot find value `undefined_variable` in this scope",
      "code": "E0425"
    }
  ],
  "availability": "published",
  "origin": "pull",
  "indexing_in_progress": false,
  "push_notifications_degraded": false
}

Severity is error, warning, information or hint. Add context: "enclosing_symbol" to see which function each diagnostic is in.

Read the extra fields before you trust an empty list:

FieldValues and meaning
availabilitypublished: the server reported on the file, so an empty list means clean. pending: nothing was published yet. evicted: a publish was dropped to bound the cache. An empty list next to pending or evicted is not a clean file
originpull when a textDocument/diagnostic request answered, push_cache when the server has no pull support and the cache alone answered, cache_after_failed_pull when the pull failed and the cache answered
indexing_in_progresstrue if the server was still indexing during the read, so early errors may be false and real ones may be missing
push_notifications_degradedtrue if the server crashed and restarted during this session, so push-only diagnostics may be missing until mcpls restarts

After an edit. Once mcpls has re-synced a changed file, a pushed diagnostic from an older version of the file is not listed when the server’s pull for the current version no longer reports it, so an error you just fixed does not linger for the 250 to 350 ms a server such as pyright needs to publish again. Only items the same server’s pull reported before are dropped: pushed diagnostics that no pull ever reports (clippy results from rust-analyzer, for example) stay. If the pull failed or the server answers only by push, nothing is dropped and the latest push is the answer. Limits: a pushed item without a source that no earlier pull covered, and items of a server whose pulls never reported that source yet (for example its first pull came back empty), stay until the server publishes again.

Diagnostics from the two sources are deduplicated by severity, code and proximity (within 3 lines), without comparing messages. Two distinct errors with the same code that start within 3 lines appear once, with the pulled message.

get_cached_diagnostics

Returns what mcpls already holds for a file, with no new analysis: the diagnostics the server pushed plus the last full report a get_diagnostics call stored. It is fast and takes only file_path.

While the file’s server is still starting it returns the retryable ServerInitializing error, and if the server failed to start it returns that failure, instead of an empty list. An empty list therefore always means “no diagnostics”. The same cache backs the lsp-diagnostics:// resource; see Diagnostics and Resources.

format_document

Returns the text edits that format a whole file.

Arguments: file_path, tab_size (1 to 32, default 4) and insert_spaces (default true).

{ "file_path": "/work/app/src/main.rs", "tab_size": 4, "insert_spaces": true }

The edits are returned, not applied.

format_range

Returns the edits that format only a range. Arguments: file_path, the four range fields, tab_size and insert_spaces. A line past the end of the file is rejected.

{
  "file_path": "/work/app/src/main.rs",
  "start_line": 12, "start_character": 1,
  "end_line": 20, "end_character": 1
}

Not every server supports range formatting; rust-analyzer does not advertise it.

get_selection_ranges

Returns the ranges that enclose a position, innermost first (expression, statement, block, function, and so on). Pass one of them to get_code_actions or format_range.

{ "file_path": "/work/app/src/main.rs", "line": 14, "character": 9 }

get_folding_ranges

Returns the foldable regions of a file with 1-based lines, a kind and collapsed text, sorted by start line with the longest first. Argument kind is all (default), comment, imports or region, in any case.

{ "file_path": "/work/app/src/lib.rs", "kind": "imports" }

It is a compact way to see the shape of a large file.

What’s Next

Continue with Refactoring to turn what you found into changes.