@stxt-lang/editor
v0.4.0
Published
The STXT editor for any web page: highlighting, live errors, grammar-driven completion and hover, on CodeMirror 6.
Maintainers
Readme
STXT Editor
The STXT editor for any web page: a <textarea> becomes an editor with
highlighting, errors while typing, and completion and hover driven by the grammar.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@stxt-lang/[email protected]/dist/stxt-editor.css">
<script src="https://cdn.jsdelivr.net/npm/@stxt-lang/[email protected]/dist/stxt-editor.js"></script>
<textarea id="doc" name="doc">
Recipe (com.example.cooking): Pancakes
Serves: four
Difficulty: Easy
</textarea>
<script>
const template = `
Template (@stxt.template): com.example.cooking
Structure >>
Recipe (com.example.cooking):
Serves: (1) NATURAL
Difficulty: (?) ENUM [Easy, Medium, Hard]
`;
const editor = StxtEditor.fromTextArea(document.getElementById("doc"), {
grammars: [template],
onChange: (text, diagnostics) => console.log(diagnostics),
});
</script>Serves: four is underlined: the template says NATURAL.
Status: 0.4.0. The API may still change before 1.0.
What it does
- Highlighting from the parser of
@stxt-lang/core. There is no grammar of the language here. Blocks that a grammar declares asMARKDOWNare highlighted as Markdown. - Errors while typing: syntax errors, and validation errors against the grammars.
- Completion (typing, or Ctrl+Space): the nodes the grammar allows at that point, and the
ENUMvalues. - Hover over a node shows what the parser and its grammar know about it, or only what the grammar says: its description, its type and its values.
- Go to definition (Ctrl+Click or Ctrl+B on the name of a node; Cmd on macOS): to the line of the grammar that declares it.
- Tab inserts indentation: a tab, or four spaces. Switching re-indents the document.
- The textarea stays in sync: a form that contains it still submits the text.
Install
With a <script>
The package carries its files ready for a page, minified. Any CDN that serves npm packages
serves them, under https://cdn.jsdelivr.net/npm/@stxt-lang/[email protected]/:
| File | Global | Size (gzip) | What it has |
|---|---|---|---|
| dist/stxt-editor.js | StxtEditor | 422 kB (134 kB) | The editor and the static highlighting, with CodeMirror and @stxt-lang/core inside |
| dist/stxt-highlight.js | StxtHighlight | 76 kB (21 kB) | Only the static highlighting, without CodeMirror |
| dist/stxt-editor.css | — | 6 kB (2 kB) | The styles of both |
| dist/stxt-editor-dark.css | — | 7 kB (2 kB) | The same styles with the dark theme, instead of stxt-editor.css |
With a bundler
npm install @stxt-lang/editorimport { fromTextArea } from "@stxt-lang/editor";
import "@stxt-lang/editor/stxt-editor.css";The package is ESM, with type declarations. CodeMirror and @stxt-lang/core are dependencies,
not bundled, so a page that already uses CodeMirror loads a single copy.
API
| Call | What it does |
|---|---|
| fromTextArea(textarea, options) | Replaces a textarea with an editor. The textarea is hidden and keeps the text |
| mount(element, options) | Creates an editor inside an element |
Both return an Editor. Every option is optional:
| Option | Default | |
|---|---|---|
| value | the textarea's value, or "" | Initial text |
| grammars | [] | Texts of @stxt.schema or @stxt.template documents |
| validation | true | Validate against the grammars. Syntax errors are always reported |
| indent | "tabs" | What Tab inserts: "tabs" or "spaces" |
| readOnly | false | The text can be read, not edited |
| lineNumbers | true | Show the line numbers. The marks of the lines with problems stay either way |
| developerInfo | true | What the hover shows: the technical card, or only what the grammar says |
| onChange | — | (text, diagnostics) => void, on creation and after every change |
| onGoToDefinition | — | (location) => void, on go to definition into one of grammars |
A grammar written inside the document itself works too. A grammar that lives at a URL is fetched by the page:
const template = await (await fetch("/grammars/cooking.stxt")).text();
editor.setGrammars([template]);| Method | |
|---|---|
| getValue() / setValue(text) | The text. setValue is an edit: it can be undone |
| setGrammars(texts) | Replaces the grammars and validates again |
| setValidation(enabled) | Turns validation on or off |
| setIndent(mode) | Changes what Tab inserts and re-indents the document |
| setReadOnly(readOnly) | Read-only or editable |
| setLineNumbers(show) | Shows or hides the line numbers |
| setDeveloperInfo(show) | Changes what the hover shows |
| getDiagnostics() | The problems of the document |
| getGrammarDiagnostics() | The problems of each grammar, in order |
| focus() / destroy() | destroy removes the editor and shows the textarea again |
| view | The CodeMirror EditorView |
A diagnostic is { line, code, message, severity, source }:
| Field | |
|---|---|
| line | 0-based |
| code | The stable error code of the ecosystem: INDENTATION_MIXED, INVALID_VALUE… |
| severity | "error" or "warning" |
| source | "syntax", "grammar" or "validation" |
Hover
| developerInfo | The hover shows | A node no grammar declares |
|---|---|---|
| true | The technical card: text block or inline node, level, value, canonical name, and from the grammar the type, the values, the children with their cardinality and the description | The card, without the grammar part |
| false | What the grammar says, for whoever writes the document: the description, the type and, for an ENUM, the values | No hover |
const editor = StxtEditor.fromTextArea(textarea, {
grammars: [template],
developerInfo: false,
});The description is the Description of the node in a schema, or its entry in the Description
of a template.
Go to definition
A grammar written inside the document is on the page: the cursor goes to the line. The grammars
of grammars are not, so the editor tells the page, and the page decides what to do:
const editor = StxtEditor.fromTextArea(textarea, {
grammars: [template],
onGoToDefinition: ({ grammar, line, namespace }) => showGrammar(grammar, line),
});| Field | |
|---|---|
| grammar | Index of the grammar in grammars |
| line | Line of that grammar, 0-based: the one that declares the node. Over a namespace, the one of the grammar's root |
| namespace | The namespace the grammar defines |
Without onGoToDefinition, nothing happens for those grammars.
The pieces underneath
For more than one document per view, or another layout, the package also exports what the editor is made of:
Analyzer: parses and validates a set of documents. No DOM, no CodeMirror.createStxtEditor,createStxtExtensions: the CodeMirror extensions.setTokensEffect,toCmDiagnostics: to push an analysis into a view.
Static highlighting
For pages that show STXT and do not edit it, there is a highlighter without CodeMirror:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@stxt-lang/[email protected]/dist/stxt-editor.css">
<script src="https://cdn.jsdelivr.net/npm/@stxt-lang/[email protected]/dist/stxt-highlight.js"></script>
<pre><code class="language-stxt">Recipe (com.example.cooking): Pancakes
Serves: 4</code></pre>
<script>
StxtHighlight.highlightAll();
</script>| Call | What it does |
|---|---|
| highlightAll(selector, options) | Highlights every element that matches. selector defaults to pre code.language-stxt |
| highlight(element, options) | Highlights one element in place |
| highlightText(text, options) | Returns the HTML, without touching the DOM: it also works in Node, on a server or in a build |
The only option is grammars: with them, blocks that a grammar declares as MARKDOWN are
highlighted as Markdown.
With a bundler: import { highlightAll } from "@stxt-lang/editor/highlight".
Themes
The colours are CSS custom properties on .stxt-editor (the editor) and .stxt-highlight
(static highlighting). A page themes both by overriding them:
.stxt-editor,
.stxt-highlight {
--stxt-node: #0d5cb6;
--stxt-value: #d05a80;
--stxt-comment: #008000;
--stxt-background: #fff;
}
.stxt-editor {
height: 22rem;
}Without a height, the editor grows with the text. With one, it scrolls.
| Property | Colours |
|---|---|
| --stxt-node | Names of the nodes |
| --stxt-block | The >> of a text block |
| --stxt-namespace | Namespaces |
| --stxt-value | Values |
| --stxt-comment | Comments |
| --stxt-muted | Punctuation, line numbers, secondary text |
| --stxt-background, --stxt-text, --stxt-border | The box of the editor |
| --stxt-gutter-background, --stxt-active-line, --stxt-active-gutter | The line numbers and the current line |
| --stxt-selected, --stxt-code-background, --stxt-separator | The selected completion, inline Markdown code, the rule of the hover |
| --stxt-font-mono, --stxt-font-body, --stxt-font-size | Fonts |
The defaults are the palette of https://stxt.dev.
Dark theme
stxt-editor-dark.css is the same stylesheet with a dark palette. A page loads it instead of
stxt-editor.css:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@stxt-lang/[email protected]/dist/stxt-editor-dark.css">With a bundler: import "@stxt-lang/editor/stxt-editor-dark.css".
To follow the colour scheme of the system, a page loads both, each with its media:
<link rel="stylesheet" media="(prefers-color-scheme: light)" href="https://cdn.jsdelivr.net/npm/@stxt-lang/[email protected]/dist/stxt-editor.css">
<link rel="stylesheet" media="(prefers-color-scheme: dark)" href="https://cdn.jsdelivr.net/npm/@stxt-lang/[email protected]/dist/stxt-editor-dark.css">The dark palette is the same set of properties, so it is overridden the same way. Static
highlighting only sets the colours of the text: the dark background of a <pre> is the page's.
Development
npm install
npm test # lint + mocha
npm run build # typecheck + lint, then dist/
npm start # build and open examples/ at http://localhost:8080 (PORT changes it)
# examples/index.html is the editor, examples/highlight.html the highlighter;
# index-dark.html and highlight-dark.html are the same pages with the dark themeLicense
MIT © stxt-lang
