Context: Reference guide for CodeMirror 6 concepts relevant to QNTX ATS query editor implementation.
Related: Issue #13 - Track codemirror-languageserver semantic token support
EditorState - Immutable document state:
EditorView - DOM rendering and event handling:
Pattern:
// State → View → User interaction → Transaction → New State
const state = EditorState.create({doc: "initial text"})
const view = new EditorView({state, parent: container})
// Updates are transactions
view.dispatch({
changes: {from: 0, to: 5, insert: "Hello"}
})
Everything in CodeMirror 6 is an extension - features are composable plugins:
import { lineNumbers } from '@codemirror/view'
import { highlightActiveLineGutter } from '@codemirror/view'
EditorState.create({
extensions: [
lineNumbers(), // Built-in
highlightActiveLineGutter(), // Built-in
myCustomExtension(), // Custom
]
})
Common extension types:
Replace custom DOM manipulation with CodeMirror decorations:
Mark decorations - Wrap text ranges:
Decoration.mark({class: "cm-keyword"}).range(from, to)
Widget decorations - Insert DOM elements:
Decoration.widget({
widget: new MyWidgetClass(),
side: 1 // After position
}).range(pos)
Line decorations - Style entire lines:
Decoration.line({class: "cm-error-line"}).range(pos)
Never mutate view.state directly:
// ❌ Wrong - direct mutation
view.state.doc = newDoc
// ✅ Correct - dispatch transaction
view.dispatch({
changes: {from: 0, to: view.state.doc.length, insert: newDoc}
})
Store extension-specific state that persists across transactions:
import { StateField, StateEffect } from '@codemirror/state'
// Define effect for state updates
const updateDecorations = StateEffect.define()
// StateField holds decorations
const syntaxDecorations = StateField.define({
create() {
return Decoration.none
},
update(value, tr) {
// Apply effects from transaction
for (let effect of tr.effects) {
if (effect.is(updateDecorations)) {
return effect.value
}
}
return value
},
provide: f => EditorView.decorations.from(f)
})
The @codemirror/language-server package provides LSP client:
import { languageServer } from '@codemirror/language-server'
const lspExtension = languageServer({
serverUri: 'ws://localhost:877/lsp',
rootUri: 'file:///',
documentUri: 'file:///query.ats',
languageId: 'ats',
})
Provides:
textDocument/completion → Native completion UItextDocument/hover → Hover tooltipstextDocument/publishDiagnostics → Error squiggles@codemirror/language-server does not yet support semantic tokens (as of v1.18.1).
Workaround: QNTX uses custom parse_request/parse_response WebSocket protocol:
textDocument/semanticTokens/fullCodeMirror 6 Editor
├─ languageServer() extension → /lsp (completions, hover)
└─ Custom WebSocket → /ws (semantic tokens via parse_request)
CodeMirror 6 Editor
└─ languageServer() extension → /lsp (completions, hover, semantic tokens)