[Formatting]
Document and range formatting via AST parse → NodeUnparser round-trip.
Formatting
Abstract
Formatting is canonical unparse, not a style linter. The handler parses the buffer, runs NodeUnparser, and if the result differs from the source, replaces the document (or the requested line range) with a single TextEdit. There is no prettier-like option matrix; FormattingOptions from the client are currently unused.
Conceptual model
Interface surface
| Method | Handler |
|---|---|
textDocument/formatting | handle_formatting |
textDocument/rangeFormatting | handle_range_formatting |
Capabilities: both providers True in config.py.
Full document
- Parse with filename
"<format>". formatted = NodeUnparser().visit(tree).- If identical to source →
[]. - Else one
TextEditspanning(0,0)→ last line/col withnew_text=formatted.
Range
- Unparse the entire document.
- Slice formatted lines and source lines by the client range’s start/end line indices.
- If the in-range slices match →
[]. - Else replace
params.rangewith the formatted line slice.
This is line-aligned, not AST-node-aligned: partial mid-line ranges may produce surprising splices when unparse changes line structure.
Internals
| Path | Role |
|---|---|
features/formatting.py | LSP handlers |
src/pynescript/ast/helper.py | parse |
src/pynescript/ast/unparser.py | NodeUnparser |
VS Code command pynescript.formatDocument delegates to editor.action.formatDocument, which hits this provider when the language client is active.
Invariants and edge cases
- Parse failure →
[](no partial format; silent no-op from the client’s perspective). - Idempotence goal: format twice should stabilize if unparse is canonical; any non-idempotent unparse is a core bug, not an LSP bug.
- Comments / trivia: fidelity is whatever the unparser preserves — not a full CST pretty-printer.
- Range formatting still requires a full successful parse of the whole file.
- Tab size / insert spaces from
FormattingOptionsare ignored today.
Worked example
Before:
//@version=5
indicator("x")
plot( close )
After full format (illustrative — exact whitespace follows NodeUnparser):
//@version=5
indicator("x")
plot(close)
If source already matches unparse output, the server returns an empty edit list and the editor leaves the buffer alone.
Failure modes
| Symptom | Cause |
|---|---|
| Format does nothing on broken syntax | Expected: exception → [] |
| Range format rewrites wrong lines | Unparse shifted line count; prefer full-document format |
| Client “format on save” loops | Unparse not idempotent — fix unparser, not client |