Editor

AXIS Pine editor: CodeMirror 6, diagnostics, 80-col ruler, git sync, stats, docked vs popout, editor bridge.

This page

The editor is the source axis of intent: Pine text the operator authorizes the engine to run. Completion and hover can use local builtins metadata and optional remote LSP; evaluation is always an engine plugin.

CodeMirror 6 Pine editor showing a v6 RSI indicator

Abstract

ModuleRole
EditorPane.tsxDocked/standalone chrome, header tools, detach
tabbed-editor.tsxMulti-tab docs, stats strip, Problems, Colors, Symbols, git bar
PyneEditor.tsxCM6 mount (extensions + view APIs)
pyne-language.tsStreamLanguage / highlighting (namespaces, types, multiline strings)
pine-symbols.ts + SymbolEmojiManager.tsxTV-editor-safe glyph catalog + insert panel
pyne-lsp.ts + data/pyne-builtins.jsonOffline completion/hover corpus
column-ruler.ts80-character guide line
minimap.tsOptional right-rail scroll overview
function-outline.ts + FunctionOutline.tsxOptional right-rail function / type / enum tree
diagnostics.tsUnderlines, gutter, jump-to from last run
editor-intel.tsSettings bag for lint / hover / complete / marks / timings
inline-debug.tsEOL chips, pin gutter, line flash
git-sync.ts + ui/EditorGitBar.tsxPull / push / storage status
ui/EditorProblems.tsxCollapsible problems list
editor-bridge.tsCross-window doc + run messages
cm-void.tsVoid-theme CM styling

Conceptual model

Diagram

Rendering…

Why run on main? Chart, bars, and pane manager live in the main window. Popout is a pure editing surface.

Interface surface

Docked mode

  • Right dock by default (panelChrome.editor); may sit side-by-side with other right panels (e.g. Indicators left of Editor)
  • FloatableShell: dock left/right/bottom, float, new window
  • Detach → popup or tab (openEditorWindow)

Popout mode

  • Main shows “Editor detached” chip + Reattach
  • Shared doc key + bridge messages: hello, doc, run, run-status, reattach, popout-opened, popout-closed

Run entry points

  • Editor / topbar Run → runAndApply(getDoc())
  • Popout Run → bridge → main executes
  • Command palette: save library, git push/pull, jump to line, toggles

Header tools (axis-editor-tools)

Icon-only (labels in tooltips / aria-label). Open in new tab is in the panel-icon menu with dock options.

ControlEffect
RunRun script against loaded bars
ScriptlogsOpen last-run log.* panel
ProfilerprofilerEnabled + optional line % gutter; re-run when enabling
DebugInline end-of-line log/error chips (inlineDebugEnabled)
PinsChart markers + 📍 gutter (debugPinsEnabled); Alt+P
Ruler80-column guide (editorRulerEnabled)
Overflow → MinimapRight-rail canvas overview (editorRightRail: minimap); click/drag scrolls
Overflow → Function treeRight-rail outline of functions, methods, types, enums (editorRightRail: outline); click jumps to the declaration. Mutually exclusive with Minimap
Panel icon → Open in new tabEditor in a full browser tab (vs New window popup)

Details: Debugging.

Stats strip

Bottom of tabbed editor (data-testid="axis-editor-stats"):

FieldMeaning
Pos L:CCursor line:column (1-based)
LnTotal lines in document
Words / CharsDocument stats
Diagnostics badgeError/warning count from last run — click jumps to first
ProblemsExpand/collapse problems list
ColorsColor chips / picker / converter for hex and color.*
SymbolsInsert TV-editor-safe arrows, box drawing, marks, spaces, and chart emoji (raw, quoted, or plotchar)
wrapSoft wrap toggle (editorWrapEnabled, default on)

Symbols & emoji

Status-bar Symbols (data-testid="axis-editor-symbols") opens a searchable catalog of glyphs used in Pine Script™ plotchar / label.new / table text.

  • Mono-safe — ~1 cell in IBM Plex Mono / the TV editor (box drawing, ▲▼●◆✓★, arrows).
  • Wide / chart-only — official 🠅🠇 and emoji (🔥🚀📈). Filter with Mono-safe.
  • Click inserts at the cursor (replaces the selection). Right-click copies.
  • Insert mode: raw glyph, quoted string, or a plotchar(cond, "mark", "…", location.belowbar) snippet.

Sources: TradingView® Exploring Unicode, Text and shapes, box-drawing U+2500, PineCoders conventions.

Highlighting

pyne-language.ts is a stateful stream tokenizer: //@ annotations, // and /* */ (including across lines), quoted and """ / ''' strings with \\n escapes, #RRGGBB(AA) colors, namespaces (ta., label.new), types (int / float / string, series / simple in qualifier position, and declared UDTs / enums as typeName, bold in the void theme), control vs definition keywords, and function names before (. Format leaves continuation lines of an open string or block comment untouched.

Four information surfaces

SurfaceWhenAnswers
HoverRest on a symbolKeyword, type, series, namespace, builtin, user input/function, or hex color
Param checklistInside a callUsed / current / unused args with type + default
Pre-evalWhile typing / SaveWhat is wrong before Run
Debug chipsAfter Run, Debug onLast-run log.* / errors on that source line

Hover + param checklist are writing-time docs — not a TradingView® debug API. Pre-eval marks the buffer; chips still read last run. Details: Debugging.

Diagnostics & Problems

Pre-eval (as you type) — schedulePreeval waits the idle delay after the last keystroke (default PREEVAL_IDLE_MS = 1000 ms; tab switch uses a shorter beat). Problems and underlines stay until the next idle check replaces them (optional Clear marks while typing hides them mid-keystroke). Local parse/lint (localPreevaluate) plus POST /lsp/diagnostics when engine=server and remote lint is on. Severity error disables Run when Block Run on errors is on (topbar, editor, Mod-Enter, command palette). Warnings and typos do not block.

Every lint / hover / complete / underline / chip option lives in Settings → Editor (command palette: Open Editor Settings). Defaults match the previous hardcoded behavior.

Local checks: unclosed ( / [ / { and strings, missing entry point, study() → indicator() / strategy(), bare security() → request.security, duplicate indicator() / strategy() / library(), unknown builtin members (strategy.etry, plt → “did you mean …?”). User functions and import aliases are not flagged.

PYNE lint parity offline: a pre-v5 //@version warns as deprecated (W002), and kwargs Pine v6 removed are flagged (W104) — when= on strategy.* order calls and transp= on drawing / plot calls (plot*, fill, bgcolor, barcolor, hline, line.*, box.*, label.*, table.*, polyline.*, linefill.*). color.new / color() keep a valid transp and are never flagged. Both stay on when the Pro API is unreachable, so Pyodide / offline mode reports the same warnings as POST /lsp/diagnostics. Toggles: Warn if //@version is missing (also covers W002) and Warn on v6-removed kwargs.

Merge: remote style-noise C001–C004 is dropped; remote syntax errors win the same span; local unknown-member marks still show when remote is down. Hover tooltips use the diagnostic message (including the suggestion).

After a run, diagnosticsFromLastRun(lastRun, doc) still builds ranges from engine error, meta.errors / diagnostics, and error logs with line refs (line 12:, L12:, …). Live pre-eval for the current buffer is listed first; last-run engine messages are kept when they add detail (dropped only when the buffer no longer matches the stamped run source).

  • CodeMirror: wavy underlines, severity gutter, hover tooltips
  • Problems panel: clickable rows → scrollToLine / jumpToDiagnostic; origin pre-eval vs run when the diagnostic has a source
  • Empty / pending pre-eval stays No problems (no flicker)
  • Unit tests: tests/editor-diagnostics.test.ts, tests/editor-problems.test.ts, tests/preevaluate.test.ts

80-column ruler

columnRulerExtension paints a dashed guide at character column 80 (measured via CM coords). Toggle Ruler or command palette “Toggle Editor Ruler”.

Git sync bar

Tab toolbar Pull / Push / status (EditorGitBar):

  • Uses active storage plugin (local | cloud | git)
  • Push → writeScript (git commits when storage is git)
  • Pull → optional plugin sync('pull') + list/reload bound library id
  • Config stays in Settings / Script Library (no second credentials UI)

Completion corpus

src/editor/data/pyne-builtins.json is synced from PYNE (scripts/sync-pyne-builtins.sh). Offline completion/hover fall back to this catalog; optional remote LSP when the Pro API is available.

Remote LSP bridge

PYNE exposes four HTTP endpoints, and AXIS wires all four: POST /lsp/completion, POST /lsp/hover, POST /lsp/diagnostics, POST /lsp/convert (src/editor/pyne-lsp-client.ts). Everything else the PYNE language server advertises over stdio (signature help, document symbols, formatting, folding, document color, convert-to-v6) has a local CodeMirror implementation instead — pine-call-params.ts, pine-symbols.ts, pine-format.ts, code-folding.ts, pine-colors.ts, pine-convert.ts. Library annotation hover (//@function, //@param, //@returns, //@type, //@field, //@variable, //@description) is parsed locally in pyne-doc-annotations.ts and rendered as verbatim Markdown, matching PYNE's card shape.

The Pyodide runtime vendors pynescript-0.6.29-py3-none-any.whl; the builtins catalog is byte-identical to PYNE's builtin_metadata.json (941 entries).

Navigation

Pine-aware symbol navigation runs locally from one symbol index (src/editor/symbol-index.ts) — no round trip to the Pro API, so it works in Pyodide and offline mode.

ActionKeyAlso
Go to definitionF12Alt-click the symbol
Find referencesShift-F12—
Rename symbolF2—
Occurrence highlightautomaticcursor move / click

Definition resolves a parameter or local inside the enclosing function before a script-level name, so f(n) and g(n) stay distinct. Every occurrence is resolved to the declaration it actually names, so references, highlight and rename skip shadowed uses instead of merging scopes. obj.field resolves through the receiver's declared (or Type.new()-inferred) type; when the receiver has no type information and several types share the field name, the member is left unresolved rather than jumping to the wrong type. Call named arguments (qty = qty) are not treated as variable writes. Rename refuses builtins and builtin namespaces (ta, strategy), Pine keywords, invalid identifiers, type / enum members (a field name is shared by every value of its type), and a new name that is declared or referenced in any scope the rename touches.

The same actions are in the command palette (Go to Definition, Find References, Rename Symbol). Browsers may reserve F12 for devtools — use the palette or Alt-click there.

Library load

loadLibraryDoc(doc, name) on editorRef injects storage reads into the active tab.

Internals

editorRef pattern

App holds { getDoc, setDoc?, loadLibraryDoc?, scrollToLine?, jumpToDiagnostic?, getCursor?, insertAtCursor? } populated by CM mount—avoids full-doc store writes every keystroke (doc also mirrored to EDITOR_DOC_KEY).

Persistence

Key / fieldRole
store.editor + panelChrome.editorDock geometry, open, mode
pynescript.axis.editor.docDraft text
profilerEnabled / inlineDebugEnabled / debugPinsEnabled / editorRulerEnabled / editorWrapEnabledEditor tools

Invariants

  1. Empty doc Run is a no-op at topbar/editor handlers.
  2. Popout does not own PaneManager.
  3. Reattach restores shared doc into docked CM.
  4. AXIS ≠ engine: editor never fully evaluates Pine itself; pre-eval is parse/lint only.
  5. Debug/Pins still read last run; static diagnostics also come from pre-eval.

Failure modes

SymptomMitigation
Two editors divergePrefer bridge doc events; reattach
Detached but no windowPopup blocked; allow popups; reattach
Lost text on crasheditor.doc localStorage; library Save / git push
Blank CM (no lines)Dock column height chain; editor must fill strip
No chips/pinsEnable Debug/Pins; logs need line and/or bar_index

See also