@synclineapi/editor 4.0.3 → 4.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +217 -4
- package/dist/syncline-editor.es.js +3907 -3178
- package/dist/syncline-editor.es.js.map +1 -1
- package/dist/syncline-editor.umd.js +178 -173
- package/dist/syncline-editor.umd.js.map +1 -1
- package/dist/types/index.d.ts +391 -179
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ Ships as both an **ES module** and **UMD bundle**, runs entirely inside a **Shad
|
|
|
22
22
|
- [Editing](#editing)
|
|
23
23
|
- [Features](#features-config)
|
|
24
24
|
- [Syntax & Autocomplete](#syntax--autocomplete-customisation)
|
|
25
|
+
- [Custom Syntax Highlighter — provideTokens](#custom-syntax-highlighter--providetokens)
|
|
25
26
|
- [Token Colors](#token-colors-config)
|
|
26
27
|
- [Theme](#theme-config)
|
|
27
28
|
- [Callbacks](#callbacks)
|
|
@@ -61,6 +62,7 @@ Ships as both an **ES module** and **UMD bundle**, runs entirely inside a **Shad
|
|
|
61
62
|
- [Dynamic Completion Provider](#dynamic-completion-provider)
|
|
62
63
|
- [provideCompletions](#providecompletions)
|
|
63
64
|
- [CompletionContext](#completioncontext)
|
|
65
|
+
- [Custom Syntax Highlighter — provideTokens](#custom-syntax-highlighter--providetokens)
|
|
64
66
|
- [Events & Callbacks](#events--callbacks)
|
|
65
67
|
- [Advanced Features](#advanced-features)
|
|
66
68
|
- [Multi-cursor](#multi-cursor)
|
|
@@ -89,6 +91,7 @@ Ships as both an **ES module** and **UMD bundle**, runs entirely inside a **Shad
|
|
|
89
91
|
- [Keyboard Shortcuts](#keyboard-shortcuts)
|
|
90
92
|
- [Recipes](#recipes)
|
|
91
93
|
- [Monaco-style Editor Embed](#monaco-style-editor-embed)
|
|
94
|
+
- [Markdown Editor](#markdown-editor)
|
|
92
95
|
- [DSL / SQL Editor](#dsl--sql-editor)
|
|
93
96
|
- [Read-Only Code Viewer](#read-only-code-viewer)
|
|
94
97
|
- [Custom Theme from Scratch](#custom-theme-from-scratch-recipe)
|
|
@@ -109,6 +112,7 @@ Ships as both an **ES module** and **UMD bundle**, runs entirely inside a **Shad
|
|
|
109
112
|
| **Shadow DOM** | Fully encapsulated styles — no CSS leakage in either direction |
|
|
110
113
|
| **Dual build** | ES module + UMD bundle, full TypeScript declarations |
|
|
111
114
|
| **Syntax highlighting** | TypeScript, JavaScript, CSS, JSON, Markdown — nine token classes |
|
|
115
|
+
| **`provideTokens` callback** | Fully override the built-in tokeniser for any language — pass `(line, language) => TokenSegment[]` |
|
|
112
116
|
| **Token color overrides** | Override individual token colours on top of any theme without replacing it |
|
|
113
117
|
| **6 built-in themes** | VR Dark, VS Code Dark+, Monokai, Dracula, GitHub Light, Solarized Light |
|
|
114
118
|
| **Custom themes** | Full `ThemeDefinition` API — every CSS variable exposed |
|
|
@@ -274,6 +278,7 @@ Default `autoClosePairs`:
|
|
|
274
278
|
| `replaceBuiltins` | `boolean` | `false` | When `true`, `completions` replaces the built-in language keywords/types entirely. |
|
|
275
279
|
| `provideCompletions` | `(ctx: CompletionContext) => CompletionItem[] \| null` | `undefined` | Dynamic callback — called on every popup open; return `null` to fall through to defaults. |
|
|
276
280
|
| `provideHover` | `(ctx: HoverContext) => HoverDoc \| null` | `undefined` | Dynamic hover callback — return a `HoverDoc` to show a tooltip for any word not covered by built-in docs or the `completions` array. |
|
|
281
|
+
| `provideTokens` | `(line: string, language: string) => TokenSegment[]` | `undefined` | Fully override syntax tokenisation. Called for every visible line on every render. Return an array of `TokenSegment` objects. When set, the built-in tokeniser is **never called** — the callback owns all highlighting for every language. See [Custom Syntax Highlighter](#custom-syntax-highlighter--providetokens). |
|
|
277
282
|
| `maxCompletions` | `number` | `14` | Maximum items shown in the autocomplete popup. |
|
|
278
283
|
|
|
279
284
|
### Token Colors Config
|
|
@@ -1039,6 +1044,120 @@ createEditor(container, { emmet: false }); // disable
|
|
|
1039
1044
|
|
|
1040
1045
|
---
|
|
1041
1046
|
|
|
1047
|
+
## Custom Syntax Highlighter — `provideTokens`
|
|
1048
|
+
|
|
1049
|
+
When the built-in tokeniser doesn't cover your language, supply a `provideTokens` callback and own syntax highlighting entirely:
|
|
1050
|
+
|
|
1051
|
+
```ts
|
|
1052
|
+
provideTokens?: (line: string, language: string) => TokenSegment[]
|
|
1053
|
+
```
|
|
1054
|
+
|
|
1055
|
+
- Called for **every visible line** on every render pass.
|
|
1056
|
+
- When set, the built-in tokeniser is **never called** — your callback is the sole source of token segments.
|
|
1057
|
+
- Any line not fully covered by your segments renders as plain text for those characters.
|
|
1058
|
+
- Runs synchronously on the render thread — keep it fast (no I/O, no heavy regex backtracking).
|
|
1059
|
+
- The `language` argument is the active `EditorConfig.language` string — use it when you register one callback for multiple languages.
|
|
1060
|
+
|
|
1061
|
+
### `TokenSegment` shape
|
|
1062
|
+
|
|
1063
|
+
```ts
|
|
1064
|
+
interface TokenSegment {
|
|
1065
|
+
cls: TokenClass; // which colour to apply — see table below
|
|
1066
|
+
start: number; // start character index in the line (inclusive)
|
|
1067
|
+
end: number; // end character index in the line (exclusive)
|
|
1068
|
+
}
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
Segments may overlap; later segments in the array take priority. Gaps between segments render as plain text (`--tok-op` colour).
|
|
1072
|
+
|
|
1073
|
+
### `TokenClass` values
|
|
1074
|
+
|
|
1075
|
+
| `cls` | CSS variable | Typical use |
|
|
1076
|
+
|---|---|---|
|
|
1077
|
+
| `'kw'` | `--tok-kw` | Keywords, control flow |
|
|
1078
|
+
| `'str'` | `--tok-str` | String and template literals |
|
|
1079
|
+
| `'cmt'` | `--tok-cmt` | Comments |
|
|
1080
|
+
| `'fn'` | `--tok-fn` | Function names, calls |
|
|
1081
|
+
| `'num'` | `--tok-num` | Numeric literals |
|
|
1082
|
+
| `'cls'` | `--tok-cls` | Class / type names |
|
|
1083
|
+
| `'op'` | `--tok-op` | Operators, punctuation, plain text |
|
|
1084
|
+
| `'typ'` | `--tok-typ` | Type annotations |
|
|
1085
|
+
| `'dec'` | `--tok-dec` | Decorators / annotations |
|
|
1086
|
+
|
|
1087
|
+
### Markdown example
|
|
1088
|
+
|
|
1089
|
+
The built-in `'markdown'` language produces no highlighting — it runs the generic path. Use `provideTokens` to add full Markdown highlighting:
|
|
1090
|
+
|
|
1091
|
+
```ts
|
|
1092
|
+
import { createEditor } from 'syncline-editor';
|
|
1093
|
+
import type { TokenSegment } from 'syncline-editor';
|
|
1094
|
+
|
|
1095
|
+
createEditor(container, {
|
|
1096
|
+
language: 'markdown',
|
|
1097
|
+
provideTokens: (line): TokenSegment[] => {
|
|
1098
|
+
const segs: TokenSegment[] = [];
|
|
1099
|
+
|
|
1100
|
+
// ATX headings # / ## / … → keyword colour
|
|
1101
|
+
if (/^#{1,6}(\s|$)/.test(line)) {
|
|
1102
|
+
segs.push({ cls: 'kw', start: 0, end: line.length });
|
|
1103
|
+
return segs;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// Fenced code block delimiter ``` or ~~~
|
|
1107
|
+
if (/^(`{3,}|~{3,})/.test(line)) {
|
|
1108
|
+
segs.push({ cls: 'cmt', start: 0, end: line.length });
|
|
1109
|
+
return segs;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// Blockquote > …
|
|
1113
|
+
if (/^>/.test(line)) {
|
|
1114
|
+
segs.push({ cls: 'cmt', start: 0, end: line.length });
|
|
1115
|
+
return segs;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// Inline patterns — scan with regex
|
|
1119
|
+
const inlineRules: [RegExp, TokenSegment['cls']][] = [
|
|
1120
|
+
[/`[^`]+`/g, 'str'], // inline code
|
|
1121
|
+
[/\*\*[^*]+\*\*/g, 'kw'], // bold
|
|
1122
|
+
[/\*[^*]+\*/g, 'typ'], // italic
|
|
1123
|
+
[/~~[^~]+~~/g, 'cmt'], // strikethrough
|
|
1124
|
+
[/!?\[[^\]]*\]\([^)]*\)/g, 'fn'], // links / images
|
|
1125
|
+
[/^([-*+]|\d+\.)\s/, 'dec'], // list marker
|
|
1126
|
+
];
|
|
1127
|
+
|
|
1128
|
+
for (const [re, cls] of inlineRules) {
|
|
1129
|
+
let m: RegExpExecArray | null;
|
|
1130
|
+
const g = new RegExp(re.source, 'g');
|
|
1131
|
+
while ((m = g.exec(line)) !== null) {
|
|
1132
|
+
segs.push({ cls, start: m.index, end: m.index + m[0].length });
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
return segs;
|
|
1137
|
+
},
|
|
1138
|
+
});
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
### Switching the callback at runtime
|
|
1142
|
+
|
|
1143
|
+
`updateConfig` clears the token cache before applying the new callback, so the next render is always fresh:
|
|
1144
|
+
|
|
1145
|
+
```ts
|
|
1146
|
+
// Switch from Markdown highlighting to a plain-text view
|
|
1147
|
+
editor.updateConfig({ provideTokens: undefined });
|
|
1148
|
+
|
|
1149
|
+
// Swap to a different highlighter entirely
|
|
1150
|
+
editor.updateConfig({ provideTokens: myNewHighlighter });
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
### Performance tips
|
|
1154
|
+
|
|
1155
|
+
- Avoid constructing new `RegExp` objects inside the callback on every call — define them outside and reuse.
|
|
1156
|
+
- Return an **empty array** `[]` for lines you intentionally want as plain text rather than pushing an `op` segment for the whole line.
|
|
1157
|
+
- For large files, keep per-line work to O(line-length) — the callback is invoked once per visible row per frame.
|
|
1158
|
+
|
|
1159
|
+
---
|
|
1160
|
+
|
|
1042
1161
|
## Dynamic Completion Provider
|
|
1043
1162
|
|
|
1044
1163
|
Use `provideCompletions` for fully context-aware completions that change based on the cursor position, current prefix, or document content. When this callback returns a non-null array, it **completely overrides** all other completion sources.
|
|
@@ -1407,15 +1526,17 @@ createEditor(container, {
|
|
|
1407
1526
|
| `Ctrl / Cmd + Z` | Undo | `undoBatchMs`, `maxUndoHistory` |
|
|
1408
1527
|
| `Ctrl / Cmd + Y` or `Ctrl + Shift + Z` | Redo | — |
|
|
1409
1528
|
| `Ctrl / Cmd + A` | Select all | — |
|
|
1410
|
-
| `Ctrl / Cmd + C` | Copy | — |
|
|
1411
|
-
| `Ctrl / Cmd + X` | Cut | `readOnly` |
|
|
1412
|
-
| `Ctrl / Cmd + V` | Paste | `readOnly` |
|
|
1529
|
+
| `Ctrl / Cmd + C` | Copy selection; **with no selection copies the entire current line** (pasting inserts it above the cursor line) | — |
|
|
1530
|
+
| `Ctrl / Cmd + X` | Cut selection; **with no selection cuts the entire current line** | `readOnly` |
|
|
1531
|
+
| `Ctrl / Cmd + V` | Paste; if clipboard holds a whole-line copy, inserts the line above the cursor line and shifts cursor down | `readOnly` |
|
|
1413
1532
|
| `Ctrl / Cmd + F` | Open find bar | `find` |
|
|
1414
1533
|
| `Ctrl / Cmd + H` | Open find + replace | `findReplace` |
|
|
1415
1534
|
| `Ctrl / Cmd + G` | Open Go to Line prompt | `goToLine` |
|
|
1416
|
-
| `Ctrl / Cmd + D` | Select next occurrence | `multiCursor` |
|
|
1535
|
+
| `Ctrl / Cmd + D` | Select next occurrence (first press selects current word; each subsequent press adds next occurrence) | `multiCursor` |
|
|
1417
1536
|
| `Ctrl / Cmd + Shift + D` | Duplicate line | `readOnly` |
|
|
1418
1537
|
| `Ctrl / Cmd + K` | Delete line | `readOnly` |
|
|
1538
|
+
| `Cmd + Enter` | Insert new line below cursor (without moving to end of line) | `readOnly` |
|
|
1539
|
+
| `Cmd + Shift + Enter` | Insert new line above cursor | `readOnly` |
|
|
1419
1540
|
| `Ctrl / Cmd + /` | Toggle line comment | `lineCommentToken`, `readOnly` |
|
|
1420
1541
|
| `Alt / Option + Z` | Toggle word wrap | — |
|
|
1421
1542
|
| `Alt / Option + ↑` | Move current line (or selected block) up | `readOnly` |
|
|
@@ -1446,12 +1567,84 @@ createEditor(container, {
|
|
|
1446
1567
|
|
|
1447
1568
|
All mutating shortcuts are silently blocked when `readOnly: true`. Navigation, selection, and `Ctrl+C` always work.
|
|
1448
1569
|
|
|
1570
|
+
> **Linewise copy/cut/paste** (when there is no active selection) follows VS Code exactly: `Ctrl/Cmd+C` copies the whole line including its newline; `Ctrl/Cmd+X` cuts it; `Ctrl/Cmd+V` inserts the line *above* the current line and moves the cursor to the original line — so the document is shifted down by one but the cursor text is unchanged.
|
|
1571
|
+
|
|
1449
1572
|
---
|
|
1450
1573
|
|
|
1451
1574
|
## Recipes
|
|
1452
1575
|
|
|
1453
1576
|
Real-world patterns you can copy and adapt.
|
|
1454
1577
|
|
|
1578
|
+
### Markdown Editor
|
|
1579
|
+
|
|
1580
|
+
Full-featured Markdown editor with custom syntax highlighting via `provideTokens`, appropriate language settings, and bracket pairs disabled (Markdown doesn't use them):
|
|
1581
|
+
|
|
1582
|
+
```ts
|
|
1583
|
+
import { createEditor } from 'syncline-editor';
|
|
1584
|
+
import type { TokenSegment } from 'syncline-editor';
|
|
1585
|
+
|
|
1586
|
+
function markdownTokens(line: string): TokenSegment[] {
|
|
1587
|
+
const segs: TokenSegment[] = [];
|
|
1588
|
+
|
|
1589
|
+
if (/^#{1,6}(\s|$)/.test(line))
|
|
1590
|
+
return [{ cls: 'kw', start: 0, end: line.length }];
|
|
1591
|
+
if (/^(`{3,}|~{3,})/.test(line))
|
|
1592
|
+
return [{ cls: 'cmt', start: 0, end: line.length }];
|
|
1593
|
+
if (/^>/.test(line))
|
|
1594
|
+
return [{ cls: 'cmt', start: 0, end: line.length }];
|
|
1595
|
+
|
|
1596
|
+
const rules: [RegExp, TokenSegment['cls']][] = [
|
|
1597
|
+
[/`[^`]+`/g, 'str'],
|
|
1598
|
+
[/\*\*[^*]+\*\*/g, 'kw' ],
|
|
1599
|
+
[/\*[^*]+\*/g, 'typ'],
|
|
1600
|
+
[/~~[^~]+~~/g, 'cmt'],
|
|
1601
|
+
[/!?\[[^\]]*\]\([^)]*\)/g, 'fn' ],
|
|
1602
|
+
[/^([-*+]|\d+\.)\s/, 'dec'],
|
|
1603
|
+
];
|
|
1604
|
+
|
|
1605
|
+
for (const [re, cls] of rules) {
|
|
1606
|
+
let m: RegExpExecArray | null;
|
|
1607
|
+
const g = new RegExp(re.source, 'g');
|
|
1608
|
+
while ((m = g.exec(line)) !== null)
|
|
1609
|
+
segs.push({ cls, start: m.index, end: m.index + m[0].length });
|
|
1610
|
+
}
|
|
1611
|
+
return segs;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
const editor = createEditor(document.getElementById('md-editor')!, {
|
|
1615
|
+
language: 'markdown',
|
|
1616
|
+
theme: 'github-light',
|
|
1617
|
+
provideTokens: markdownTokens,
|
|
1618
|
+
lineCommentToken: '', // no line-comment toggle
|
|
1619
|
+
autoClosePairs: { '(': ')', '[': ']', '`': '`', '*': '*', '_': '_' },
|
|
1620
|
+
bracketMatching: false, // not useful in prose
|
|
1621
|
+
emmet: false,
|
|
1622
|
+
wordWrap: true,
|
|
1623
|
+
wrapColumn: 80,
|
|
1624
|
+
showMinimap: false,
|
|
1625
|
+
tabSize: 2,
|
|
1626
|
+
onChange: (value) => preview.setContent(value),
|
|
1627
|
+
});
|
|
1628
|
+
```
|
|
1629
|
+
|
|
1630
|
+
```css
|
|
1631
|
+
#md-editor { width: 100%; height: 100vh; }
|
|
1632
|
+
```
|
|
1633
|
+
|
|
1634
|
+
**Updating the highlighter without recreating the editor:**
|
|
1635
|
+
|
|
1636
|
+
```ts
|
|
1637
|
+
// Extend the highlighter at runtime — e.g. add frontmatter support
|
|
1638
|
+
editor.updateConfig({
|
|
1639
|
+
provideTokens: (line) => {
|
|
1640
|
+
if (/^---$/.test(line)) return [{ cls: 'dec', start: 0, end: 3 }];
|
|
1641
|
+
return markdownTokens(line);
|
|
1642
|
+
},
|
|
1643
|
+
});
|
|
1644
|
+
```
|
|
1645
|
+
|
|
1646
|
+
---
|
|
1647
|
+
|
|
1455
1648
|
### Monaco-style Editor Embed
|
|
1456
1649
|
|
|
1457
1650
|
A feature-complete editor inside a fixed-height container, closely resembling a VS Code embed:
|
|
@@ -1676,6 +1869,10 @@ import type {
|
|
|
1676
1869
|
// Token colours
|
|
1677
1870
|
TokenColors,
|
|
1678
1871
|
|
|
1872
|
+
// Custom tokeniser
|
|
1873
|
+
TokenSegment,
|
|
1874
|
+
TokenClass,
|
|
1875
|
+
|
|
1679
1876
|
// Themes
|
|
1680
1877
|
ThemeDefinition,
|
|
1681
1878
|
ThemeTokens,
|
|
@@ -1747,6 +1944,22 @@ interface CompletionContext {
|
|
|
1747
1944
|
}
|
|
1748
1945
|
```
|
|
1749
1946
|
|
|
1947
|
+
### `TokenSegment`
|
|
1948
|
+
|
|
1949
|
+
```ts
|
|
1950
|
+
interface TokenSegment {
|
|
1951
|
+
cls: TokenClass; // which token colour class to apply
|
|
1952
|
+
start: number; // start character index in the line (inclusive)
|
|
1953
|
+
end: number; // end character index in the line (exclusive)
|
|
1954
|
+
}
|
|
1955
|
+
```
|
|
1956
|
+
|
|
1957
|
+
### `TokenClass`
|
|
1958
|
+
|
|
1959
|
+
```ts
|
|
1960
|
+
type TokenClass = 'kw' | 'str' | 'cmt' | 'fn' | 'num' | 'cls' | 'op' | 'typ' | 'dec';
|
|
1961
|
+
```
|
|
1962
|
+
|
|
1750
1963
|
### `TokenColors`
|
|
1751
1964
|
|
|
1752
1965
|
```ts
|