@synclineapi/editor 2.0.0
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 +1858 -0
- package/dist/syncline-editor.es.js +4466 -0
- package/dist/syncline-editor.es.js.map +1 -0
- package/dist/syncline-editor.umd.js +487 -0
- package/dist/syncline-editor.umd.js.map +1 -0
- package/dist/types/index.d.ts +1183 -0
- package/package.json +79 -0
package/README.md
ADDED
|
@@ -0,0 +1,1858 @@
|
|
|
1
|
+
# Syncline Editor
|
|
2
|
+
|
|
3
|
+
> A zero-dependency, pixel-perfect, fully customisable browser-based code editor built as a TypeScript library.
|
|
4
|
+
|
|
5
|
+
Ships as both an **ES module** and **UMD bundle**, runs entirely inside a **Shadow DOM**, and handles **100,000+ line files** via virtual rendering.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Features](#features)
|
|
12
|
+
- [Installation](#installation)
|
|
13
|
+
- [Quick Start](#quick-start)
|
|
14
|
+
- [ES Module](#es-module)
|
|
15
|
+
- [UMD / CDN](#umd--cdn)
|
|
16
|
+
- [Configuration — Full Reference](#configuration--full-reference)
|
|
17
|
+
- [Document](#document)
|
|
18
|
+
- [Display](#display)
|
|
19
|
+
- [Typography](#typography)
|
|
20
|
+
- [Cursor](#cursor)
|
|
21
|
+
- [Layout](#layout)
|
|
22
|
+
- [Editing](#editing)
|
|
23
|
+
- [Features](#features-config)
|
|
24
|
+
- [Syntax & Autocomplete](#syntax--autocomplete-customisation)
|
|
25
|
+
- [Token Colors](#token-colors-config)
|
|
26
|
+
- [Theme](#theme-config)
|
|
27
|
+
- [Callbacks](#callbacks)
|
|
28
|
+
- [Updating at Runtime](#updating-config-at-runtime)
|
|
29
|
+
- [Runtime API](#runtime-api)
|
|
30
|
+
- [Content](#content)
|
|
31
|
+
- [Cursor & Selection](#cursor--selection)
|
|
32
|
+
- [Commands](#commands)
|
|
33
|
+
- [Themes](#themes-api)
|
|
34
|
+
- [Lifecycle](#lifecycle)
|
|
35
|
+
- [Syntax Highlighting](#syntax-highlighting)
|
|
36
|
+
- [Supported Languages](#supported-languages)
|
|
37
|
+
- [Token Classes](#token-classes-and-css-variables)
|
|
38
|
+
- [Extra Keywords & Types](#adding-extra-keywords-and-types)
|
|
39
|
+
- [Token Colors](#token-colors)
|
|
40
|
+
- [Quick Overrides](#quick-overrides)
|
|
41
|
+
- [Field Reference](#tokencolors-fields)
|
|
42
|
+
- [How Overrides Work](#how-token-colour-overrides-work)
|
|
43
|
+
- [Themes](#themes)
|
|
44
|
+
- [Built-in Themes](#built-in-themes)
|
|
45
|
+
- [Switching Themes](#switching-themes)
|
|
46
|
+
- [Creating a Custom Theme](#creating-a-custom-theme)
|
|
47
|
+
- [Extending a Built-in Theme](#extending-a-built-in-theme)
|
|
48
|
+
- [ThemeTokens Reference](#themetokens-reference)
|
|
49
|
+
- [Autocomplete](#autocomplete)
|
|
50
|
+
- [Unified Completions Array](#the-unified-completions-array)
|
|
51
|
+
- [CompletionItem Reference](#completionitem-reference)
|
|
52
|
+
- [Description Panel](#description-panel)
|
|
53
|
+
- [Replace Built-ins](#replace-built-in-completions--replacebuiltins)
|
|
54
|
+
- [Popup Size](#popup-size)
|
|
55
|
+
- [Priority Order](#priority-order)
|
|
56
|
+
- [Snippets](#snippets)
|
|
57
|
+
- [Built-in Snippets](#built-in-snippets-by-language)
|
|
58
|
+
- [Custom Snippets](#custom-snippets)
|
|
59
|
+
- [Snippet Body Syntax](#snippet-body-syntax)
|
|
60
|
+
- [Emmet](#emmet)
|
|
61
|
+
- [Dynamic Completion Provider](#dynamic-completion-provider)
|
|
62
|
+
- [provideCompletions](#providecompletions)
|
|
63
|
+
- [CompletionContext](#completioncontext)
|
|
64
|
+
- [Events & Callbacks](#events--callbacks)
|
|
65
|
+
- [Advanced Features](#advanced-features)
|
|
66
|
+
- [Multi-cursor](#multi-cursor)
|
|
67
|
+
- [Find & Replace](#find--replace)
|
|
68
|
+
- [Code Folding](#code-folding)
|
|
69
|
+
- [Bracket Matching](#bracket-matching)
|
|
70
|
+
- [Word Highlight](#word-highlight)
|
|
71
|
+
- [Whitespace Rendering](#whitespace-rendering)
|
|
72
|
+
- [Cursor Styles](#cursor-styles)
|
|
73
|
+
- [Minimap](#minimap)
|
|
74
|
+
- [Word Wrap](#word-wrap)
|
|
75
|
+
- [Indent Guides](#indent-guides)
|
|
76
|
+
- [Active Line Highlight](#active-line-highlight)
|
|
77
|
+
- [Read-Only Mode](#read-only-mode)
|
|
78
|
+
- [Behavioral Options](#behavioral-options)
|
|
79
|
+
- [Auto-Close Pairs](#auto-close-pairs)
|
|
80
|
+
- [Line Comment Token](#line-comment-token)
|
|
81
|
+
- [Word Separators](#word-separators)
|
|
82
|
+
- [Undo Batch Window](#undo-batch-window)
|
|
83
|
+
- [Feature Flags](#feature-flags)
|
|
84
|
+
- [Hover Documentation](#hover-documentation)
|
|
85
|
+
- [Hover from completions](#hover-from-completions--automatic)
|
|
86
|
+
- [provideHover callback](#providehover--fully-custom-docs)
|
|
87
|
+
- [HoverDoc reference](#hoverdoc-reference)
|
|
88
|
+
- [HoverContext reference](#hovercontext-reference)
|
|
89
|
+
- [Keyboard Shortcuts](#keyboard-shortcuts)
|
|
90
|
+
- [Recipes](#recipes)
|
|
91
|
+
- [Monaco-style Editor Embed](#monaco-style-editor-embed)
|
|
92
|
+
- [DSL / SQL Editor](#dsl--sql-editor)
|
|
93
|
+
- [Read-Only Code Viewer](#read-only-code-viewer)
|
|
94
|
+
- [Custom Theme from Scratch](#custom-theme-from-scratch-recipe)
|
|
95
|
+
- [Framework-aware Autocomplete](#framework-aware-autocomplete)
|
|
96
|
+
- [Auto-Save with Debounce](#auto-save-with-debounce)
|
|
97
|
+
- [TypeScript Types](#typescript-types)
|
|
98
|
+
- [Project Structure](#project-structure)
|
|
99
|
+
- [Development](#development)
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Features
|
|
104
|
+
|
|
105
|
+
| Feature | Detail |
|
|
106
|
+
|---|---|
|
|
107
|
+
| **Zero dependencies** | No external runtime libraries — one self-contained bundle |
|
|
108
|
+
| **Virtual rendering** | Only visible rows exist in the DOM; handles 100,000+ line files smoothly |
|
|
109
|
+
| **Shadow DOM** | Fully encapsulated styles — no CSS leakage in either direction |
|
|
110
|
+
| **Dual build** | ES module + UMD bundle, full TypeScript declarations |
|
|
111
|
+
| **Syntax highlighting** | TypeScript, JavaScript, CSS, JSON, Markdown — nine token classes |
|
|
112
|
+
| **Token color overrides** | Override individual token colours on top of any theme without replacing it |
|
|
113
|
+
| **6 built-in themes** | VR Dark, VS Code Dark+, Monokai, Dracula, GitHub Light, Solarized Light |
|
|
114
|
+
| **Custom themes** | Full `ThemeDefinition` API — every CSS variable exposed |
|
|
115
|
+
| **Unified autocomplete** | One `completions` array for keywords, snippets, custom symbols, and DSL items |
|
|
116
|
+
| **VS Code-style docs panel** | Description panel beside the popup — shown for items with `description` |
|
|
117
|
+
| **Dynamic provider** | `provideCompletions` callback for context-aware, fully runtime-controlled completions |
|
|
118
|
+
| **Built-in snippets** | 17 snippets across TypeScript/JS, CSS, and HTML — Tab-expandable |
|
|
119
|
+
| **Emmet expansion** | `div.wrapper>ul>li*3` → Tab — with inline preview tooltip |
|
|
120
|
+
| **Multi-cursor** | `Alt+Click` to add cursors; `Ctrl+D` to select next occurrence |
|
|
121
|
+
| **Hover documentation** | Tooltip on pointer-rest over any identifier — built-in JS/TS/CSS docs + custom `provideHover` callback |
|
|
122
|
+
| **Move line** | `Alt+Up` / `Alt+Down` to move the current line or selected block up/down |
|
|
123
|
+
| **Find & Replace** | Literal and regex search, case-sensitive mode, replace one / all |
|
|
124
|
+
| **Code folding** | Collapse `{}` blocks via gutter toggle |
|
|
125
|
+
| **Bracket matching** | Highlights matching `()[]{}` pairs at the cursor |
|
|
126
|
+
| **Word highlight** | All occurrences of the word under the cursor highlighted subtly |
|
|
127
|
+
| **Active line highlight** | Distinct background on the current line and its gutter cell |
|
|
128
|
+
| **Minimap** | Canvas-rendered overview with draggable viewport slider |
|
|
129
|
+
| **Status bar** | Language · line/col · selection · undo depth · word-wrap toggle · theme picker |
|
|
130
|
+
| **Indent guides** | Faint vertical lines at each indentation level |
|
|
131
|
+
| **Whitespace rendering** | Visible `·` / `→` for spaces and tabs (`none` / `boundary` / `all`) |
|
|
132
|
+
| **Cursor styles** | `line`, `block`, or `underline`; configurable blink rate |
|
|
133
|
+
| **Read-only mode** | All edits blocked; navigation, selection, and copy still work |
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Installation
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm install syncline-editor
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Quick Start
|
|
146
|
+
|
|
147
|
+
### ES Module
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { createEditor } from 'syncline-editor';
|
|
151
|
+
|
|
152
|
+
const editor = createEditor(document.getElementById('app')!, {
|
|
153
|
+
value: 'const greeting = "Hello, world!";',
|
|
154
|
+
language: 'typescript',
|
|
155
|
+
theme: 'dracula',
|
|
156
|
+
onChange: (value) => console.log('changed:', value.length, 'chars'),
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Give the container an explicit height — the editor fills 100% of its host element:
|
|
161
|
+
|
|
162
|
+
```html
|
|
163
|
+
<div id="app" style="width: 100%; height: 600px;"></div>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### UMD / CDN
|
|
167
|
+
|
|
168
|
+
```html
|
|
169
|
+
<script src="syncline-editor.umd.js"></script>
|
|
170
|
+
<script>
|
|
171
|
+
const editor = SynclineEditor.createEditor(document.getElementById('app'), {
|
|
172
|
+
value: '// start coding',
|
|
173
|
+
language: 'typescript',
|
|
174
|
+
theme: 'vscode-dark',
|
|
175
|
+
});
|
|
176
|
+
</script>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Configuration — Full Reference
|
|
182
|
+
|
|
183
|
+
Pass any subset of `EditorConfig` to `createEditor()`. Every field is optional. All options can also be updated at runtime via `editor.updateConfig(patch)`.
|
|
184
|
+
|
|
185
|
+
### Document
|
|
186
|
+
|
|
187
|
+
| Option | Type | Default | Description |
|
|
188
|
+
|---|---|---|---|
|
|
189
|
+
| `value` | `string \| string[]` | `''` | Initial document content. String or pre-split `string[]`. |
|
|
190
|
+
| `language` | `Language` | `'typescript'` | Syntax highlighting and autocomplete language. |
|
|
191
|
+
|
|
192
|
+
### Display
|
|
193
|
+
|
|
194
|
+
| Option | Type | Default | Description |
|
|
195
|
+
|---|---|---|---|
|
|
196
|
+
| `showGutter` | `boolean` | `true` | Show/hide the line-number gutter. |
|
|
197
|
+
| `showMinimap` | `boolean` | `true` | Show/hide the minimap panel. |
|
|
198
|
+
| `showStatusBar` | `boolean` | `true` | Show/hide the bottom status bar. |
|
|
199
|
+
| `showIndentGuides` | `boolean` | `true` | Faint vertical lines at each indentation level. |
|
|
200
|
+
| `highlightActiveLine` | `boolean` | `true` | Background tint on the active (cursor) line and gutter cell. |
|
|
201
|
+
| `wordHighlight` | `boolean` | `true` | Highlight all other occurrences of the word under the cursor. |
|
|
202
|
+
| `renderWhitespace` | `'none' \| 'boundary' \| 'all'` | `'none'` | Spaces render as `·`, tabs as `→`. `'boundary'` = leading/trailing only. |
|
|
203
|
+
|
|
204
|
+
### Typography
|
|
205
|
+
|
|
206
|
+
| Option | Type | Default | Description |
|
|
207
|
+
|---|---|---|---|
|
|
208
|
+
| `fontFamily` | `string` | `"'JetBrains Mono', monospace"` | CSS `font-family` for all code text. |
|
|
209
|
+
| `fontSize` | `number` | `13` | Font size in pixels. |
|
|
210
|
+
| `lineHeight` | `number` | `22` | Row height in pixels — all scroll and minimap calculations derive from this. |
|
|
211
|
+
|
|
212
|
+
### Cursor
|
|
213
|
+
|
|
214
|
+
| Option | Type | Default | Description |
|
|
215
|
+
|---|---|---|---|
|
|
216
|
+
| `cursorStyle` | `'line' \| 'block' \| 'underline'` | `'line'` | Visual cursor style. |
|
|
217
|
+
| `cursorBlinkRate` | `number` | `1050` | Blink period in ms. Set to `999999` to disable blinking. |
|
|
218
|
+
|
|
219
|
+
### Layout
|
|
220
|
+
|
|
221
|
+
| Option | Type | Default | Description |
|
|
222
|
+
|---|---|---|---|
|
|
223
|
+
| `gutterWidth` | `number` | `60` | Gutter width in pixels. |
|
|
224
|
+
| `minimapWidth` | `number` | `120` | Minimap panel width in pixels. |
|
|
225
|
+
| `wordWrap` | `boolean` | `false` | Soft-wrap long lines. Toggle at runtime with `Alt+Z`. |
|
|
226
|
+
| `wrapColumn` | `number` | `80` | Column at which soft-wrap breaks when `wordWrap` is `true`. |
|
|
227
|
+
|
|
228
|
+
### Editing
|
|
229
|
+
|
|
230
|
+
| Option | Type | Default | Description |
|
|
231
|
+
|---|---|---|---|
|
|
232
|
+
| `tabSize` | `number` | `2` | Spaces per Tab press. |
|
|
233
|
+
| `insertSpaces` | `boolean` | `true` | Insert spaces on Tab; `false` inserts a literal `\t`. |
|
|
234
|
+
| `maxUndoHistory` | `number` | `300` | Maximum undo snapshots retained. |
|
|
235
|
+
| `undoBatchMs` | `number` | `700` | Keystrokes within this window are grouped into one undo step. `0` = per-keystroke. |
|
|
236
|
+
| `readOnly` | `boolean` | `false` | Block all edits. Navigation, selection, and copy still work. |
|
|
237
|
+
| `autoClosePairs` | `Record<string,string>` | see below | Characters that auto-close. Pass `{}` to disable entirely. |
|
|
238
|
+
| `lineCommentToken` | `string` | `''` | Prefix for `Ctrl+/` toggle comment. Auto-detects from language when empty. |
|
|
239
|
+
| `wordSeparators` | `string` | `''` | Extra characters treated as word boundaries for double-click and `Ctrl+←/→`. |
|
|
240
|
+
|
|
241
|
+
Default `autoClosePairs`:
|
|
242
|
+
```ts
|
|
243
|
+
{ '(': ')', '[': ']', '{': '}', '"': '"', "'": "'", '`': '`' }
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Features Config
|
|
247
|
+
|
|
248
|
+
| Option | Type | Default | Description |
|
|
249
|
+
|---|---|---|---|
|
|
250
|
+
| `bracketMatching` | `boolean` | `true` | Highlight matching `()[]{}` pair at the cursor. |
|
|
251
|
+
| `codeFolding` | `boolean` | `true` | Gutter fold button for collapsible blocks. |
|
|
252
|
+
| `emmet` | `boolean` | `true` | Emmet abbreviation expansion via Tab. |
|
|
253
|
+
| `snippetExpansion` | `boolean` | `true` | Tab-expand built-in and custom snippets. |
|
|
254
|
+
| `autocomplete` | `boolean` | `true` | Show the autocomplete popup while typing. |
|
|
255
|
+
| `autocompletePrefixLength` | `number` | `2` | Minimum characters typed before the popup appears. |
|
|
256
|
+
| `multiCursor` | `boolean` | `true` | `Alt+Click` and `Ctrl+D` multi-cursor. |
|
|
257
|
+
| `find` | `boolean` | `true` | Enable the find bar (`Ctrl+F`). |
|
|
258
|
+
| `findReplace` | `boolean` | `true` | Enable find-and-replace (`Ctrl+H`). |
|
|
259
|
+
| `wordSelection` | `boolean` | `true` | Double-click selects the word under the cursor. |
|
|
260
|
+
| `hover` | `boolean` | `true` | Show a documentation tooltip when the pointer rests on a known identifier for ~500 ms. Covers built-in JS/TS symbols and any symbol in `completions` with a `description`, plus the `provideHover` callback. |
|
|
261
|
+
|
|
262
|
+
### Syntax & Autocomplete Customisation
|
|
263
|
+
|
|
264
|
+
| Option | Type | Default | Description |
|
|
265
|
+
|---|---|---|---|
|
|
266
|
+
| `extraKeywords` | `string[]` | `[]` | Words highlighted as keywords **and** added to autocomplete. |
|
|
267
|
+
| `extraTypes` | `string[]` | `[]` | Words highlighted as types **and** added to autocomplete. |
|
|
268
|
+
| `completions` | `CompletionItem[]` | `[]` | Unified completions array — symbols, snippets, DSL items, all in one place. |
|
|
269
|
+
| `replaceBuiltins` | `boolean` | `false` | When `true`, `completions` replaces the built-in language keywords/types entirely. |
|
|
270
|
+
| `provideCompletions` | `(ctx: CompletionContext) => CompletionItem[] \| null` | `undefined` | Dynamic callback — called on every popup open; return `null` to fall through to defaults. |
|
|
271
|
+
| `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. |
|
|
272
|
+
| `maxCompletions` | `number` | `14` | Maximum items shown in the autocomplete popup. |
|
|
273
|
+
|
|
274
|
+
### Token Colors Config
|
|
275
|
+
|
|
276
|
+
| Option | Type | Default | Description |
|
|
277
|
+
|---|---|---|---|
|
|
278
|
+
| `tokenColors` | `TokenColors` | `{}` | Per-token colour overrides layered on top of the active theme. See [Token Colors](#token-colors). |
|
|
279
|
+
|
|
280
|
+
### Theme Config
|
|
281
|
+
|
|
282
|
+
| Option | Type | Default | Description |
|
|
283
|
+
|---|---|---|---|
|
|
284
|
+
| `theme` | `string \| ThemeDefinition` | `''` (VR Dark) | Built-in theme ID or a full `ThemeDefinition` object. |
|
|
285
|
+
|
|
286
|
+
### Callbacks
|
|
287
|
+
|
|
288
|
+
| Option | Signature | Description |
|
|
289
|
+
|---|---|---|
|
|
290
|
+
| `onChange` | `(value: string) => void` | Fired after every content change (keystroke, paste, undo, `setValue`). |
|
|
291
|
+
| `onCursorChange` | `(pos: CursorPosition) => void` | Fired when the cursor moves. |
|
|
292
|
+
| `onSelectionChange` | `(sel: Selection \| null) => void` | Fired when the selection changes or clears. |
|
|
293
|
+
| `onFocus` | `() => void` | Fired when the editor gains keyboard focus. |
|
|
294
|
+
| `onBlur` | `() => void` | Fired when the editor loses keyboard focus. |
|
|
295
|
+
| `provideHover` | `(ctx: HoverContext) => HoverDoc \| null \| undefined` | Provides custom hover documentation. Called after built-in lookup and `completions` search. |
|
|
296
|
+
|
|
297
|
+
### Updating Config at Runtime
|
|
298
|
+
|
|
299
|
+
Any option can be changed after creation — no reload needed:
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
// Toggle features instantly
|
|
303
|
+
editor.updateConfig({ wordWrap: true, showMinimap: false });
|
|
304
|
+
|
|
305
|
+
// Switch language (rebuilds highlighting + completions)
|
|
306
|
+
editor.updateConfig({ language: 'css' });
|
|
307
|
+
|
|
308
|
+
// Change font
|
|
309
|
+
editor.updateConfig({
|
|
310
|
+
fontFamily: "'Fira Code', monospace",
|
|
311
|
+
fontSize: 14,
|
|
312
|
+
lineHeight: 24,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Enter read-only mode
|
|
316
|
+
editor.updateConfig({ readOnly: true });
|
|
317
|
+
|
|
318
|
+
// Override token colours on top of current theme
|
|
319
|
+
editor.updateConfig({
|
|
320
|
+
tokenColors: { keyword: '#ff79c6', string: '#f1fa8c' },
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Restore all token colours to theme defaults
|
|
324
|
+
editor.updateConfig({ tokenColors: {} });
|
|
325
|
+
|
|
326
|
+
// Swap completions at runtime
|
|
327
|
+
editor.updateConfig({ completions: newCompletions });
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Runtime API
|
|
333
|
+
|
|
334
|
+
All methods on the `EditorAPI` object returned by `createEditor()`.
|
|
335
|
+
|
|
336
|
+
### Content
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
editor.getValue(): string // full document as a newline-joined string
|
|
340
|
+
editor.setValue(value: string): void // replace document (records undo snapshot)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Cursor & Selection
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
editor.getCursor(): CursorPosition // { row, col } — zero-based
|
|
347
|
+
editor.setCursor(pos: CursorPosition): void // moves cursor, scrolls into view
|
|
348
|
+
|
|
349
|
+
editor.getSelection(): Selection | null // { ar, ac, fr, fc } or null
|
|
350
|
+
editor.setSelection(sel: Selection | null): void // null to deselect
|
|
351
|
+
|
|
352
|
+
editor.insertText(text: string): void // insert at cursor position; no-op when readOnly
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### History
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
editor.undo(): void
|
|
359
|
+
editor.redo(): void
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Commands
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
editor.executeCommand(name: string): void
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
| Command | Action | Blocked by `readOnly` |
|
|
369
|
+
|---|---|---|
|
|
370
|
+
| `'undo'` | Undo | ✓ |
|
|
371
|
+
| `'redo'` | Redo | ✓ |
|
|
372
|
+
| `'selectAll'` | Select all | — |
|
|
373
|
+
| `'copy'` | Copy selection to clipboard | — |
|
|
374
|
+
| `'cut'` | Cut selection | ✓ |
|
|
375
|
+
| `'paste'` | Paste from clipboard | ✓ |
|
|
376
|
+
| `'toggleComment'` | Toggle line comment | ✓ |
|
|
377
|
+
| `'duplicateLine'` | Duplicate current line | ✓ |
|
|
378
|
+
| `'deleteLine'` | Delete current line | ✓ |
|
|
379
|
+
| `'toggleWordWrap'` | Toggle word wrap | — |
|
|
380
|
+
| `'find'` | Open find bar | — |
|
|
381
|
+
| `'findReplace'` | Open find + replace bar | — |
|
|
382
|
+
| `'indentLine'` | Indent selection / current line | ✓ |
|
|
383
|
+
| `'outdentLine'` | Outdent selection / current line | ✓ |
|
|
384
|
+
|
|
385
|
+
### Themes API
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
editor.setTheme(theme: string | ThemeDefinition): void // switch by ID or object
|
|
389
|
+
editor.getThemes(): string[] // all registered theme IDs
|
|
390
|
+
editor.registerTheme(theme: ThemeDefinition): void // register for later use
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Config
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
editor.updateConfig(patch: Partial<EditorConfig>): void
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Lifecycle
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
editor.focus(): void // programmatically focus the editor
|
|
403
|
+
editor.destroy(): void // unmount all DOM nodes; do not use the instance afterwards
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Syntax Highlighting
|
|
409
|
+
|
|
410
|
+
The editor uses a hand-written, zero-dependency tokeniser that produces nine token classes mapped to CSS custom properties.
|
|
411
|
+
|
|
412
|
+
### Supported Languages
|
|
413
|
+
|
|
414
|
+
| `language` | Keywords | Types | Built-in completions |
|
|
415
|
+
|---|---|---|---|
|
|
416
|
+
| `'typescript'` | 55 keywords (`interface`, `type`, `readonly`, `enum`, `satisfies`, …) | 25 types (`Promise`, `Array`, `HTMLElement`, …) | ~50 JS/TS functions |
|
|
417
|
+
| `'javascript'` | 35 keywords (TypeScript-specific syntax excluded) | 17 types | Same ~50 JS functions |
|
|
418
|
+
| `'css'` | 50 value keywords (`flex`, `block`, `grid`, `@media`, …) | — | 100+ CSS properties + CSS functions |
|
|
419
|
+
| `'json'` | `null`, `true`, `false` | — | — |
|
|
420
|
+
| `'markdown'` | — | — | — |
|
|
421
|
+
| `'text'` | — | — | — |
|
|
422
|
+
|
|
423
|
+
### Token Classes and CSS Variables
|
|
424
|
+
|
|
425
|
+
| Token class | CSS variable | What it colours | Examples |
|
|
426
|
+
|---|---|---|---|
|
|
427
|
+
| `kw` | `--tok-kw` | Keywords | `const`, `return`, `if`, `flex`, `@media` |
|
|
428
|
+
| `str` | `--tok-str` | Strings | `"hello"`, `'world'`, `` `template` `` |
|
|
429
|
+
| `cmt` | `--tok-cmt` | Comments | `// line`, `/* block */` |
|
|
430
|
+
| `fn` | `--tok-fn` | Functions | `console.log(`, `fetch(`, `calc(` |
|
|
431
|
+
| `num` | `--tok-num` | Numbers | `42`, `3.14`, `0xff`, `1n` |
|
|
432
|
+
| `cls` | `--tok-cls` | Classes | `MyClass`, `EventEmitter`, `Promise` |
|
|
433
|
+
| `op` | `--tok-op` | Operators | `+`, `=>`, `===`, `&&`, `?.`, `\|` |
|
|
434
|
+
| `typ` | `--tok-typ` | Types | `string`, `boolean`, `HTMLElement` |
|
|
435
|
+
| `dec` | `--tok-dec` | Decorators | `@Component`, `@Injectable` |
|
|
436
|
+
|
|
437
|
+
### Adding Extra Keywords and Types
|
|
438
|
+
|
|
439
|
+
`extraKeywords` and `extraTypes` affect **both** syntax highlighting and autocomplete:
|
|
440
|
+
|
|
441
|
+
```ts
|
|
442
|
+
createEditor(container, {
|
|
443
|
+
language: 'typescript',
|
|
444
|
+
extraKeywords: ['pipeline', 'stage', 'emit'],
|
|
445
|
+
extraTypes: ['Observable', 'Subject', 'BehaviorSubject'],
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Update at runtime (rebuilds the tokeniser cache immediately):
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
editor.updateConfig({
|
|
453
|
+
extraKeywords: ['pipeline', 'stage', 'emit', 'dispatch'],
|
|
454
|
+
});
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## Token Colors
|
|
460
|
+
|
|
461
|
+
Override individual syntax token colours on top of any active theme without replacing the whole theme. Pass only the fields you want to change — omitted fields keep the theme default.
|
|
462
|
+
|
|
463
|
+
### Quick Overrides
|
|
464
|
+
|
|
465
|
+
```ts
|
|
466
|
+
import type { TokenColors } from 'syncline-editor';
|
|
467
|
+
|
|
468
|
+
// Override specific tokens — all others remain from the active theme
|
|
469
|
+
editor.updateConfig({
|
|
470
|
+
tokenColors: {
|
|
471
|
+
keyword: '#ff79c6', // pink keywords
|
|
472
|
+
string: '#f1fa8c', // yellow strings
|
|
473
|
+
comment: '#6272a4', // muted blue comments
|
|
474
|
+
function: '#50fa7b', // green functions
|
|
475
|
+
number: '#bd93f9', // purple numbers
|
|
476
|
+
class: '#8be9fd', // cyan class names
|
|
477
|
+
operator: '#f8f8f2', // near-white operators
|
|
478
|
+
type: '#8be9fd', // cyan types
|
|
479
|
+
decorator: '#ffb86c', // orange decorators
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// Restore everything to the current theme's defaults
|
|
484
|
+
editor.updateConfig({ tokenColors: {} });
|
|
485
|
+
|
|
486
|
+
// Remove just one override and keep the rest
|
|
487
|
+
editor.updateConfig({
|
|
488
|
+
tokenColors: { ...currentOverrides, keyword: '' },
|
|
489
|
+
});
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### TokenColors Fields
|
|
493
|
+
|
|
494
|
+
| Field | CSS variable | What it highlights |
|
|
495
|
+
|---|---|---|
|
|
496
|
+
| `keyword` | `--tok-kw` | `if`, `const`, `class`, `interface`, `flex`, `@media` |
|
|
497
|
+
| `string` | `--tok-str` | String and template literals |
|
|
498
|
+
| `comment` | `--tok-cmt` | Line and block comments |
|
|
499
|
+
| `function` | `--tok-fn` | Function names, CSS functions like `calc()` |
|
|
500
|
+
| `number` | `--tok-num` | Numeric literals |
|
|
501
|
+
| `class` | `--tok-cls` | Class names, constructor calls |
|
|
502
|
+
| `operator` | `--tok-op` | Operators and punctuation |
|
|
503
|
+
| `type` | `--tok-typ` | Type names, built-in types |
|
|
504
|
+
| `decorator` | `--tok-dec` | Decorator annotations (`@Component`, `@Injectable`) |
|
|
505
|
+
|
|
506
|
+
### How Token Colour Overrides Work
|
|
507
|
+
|
|
508
|
+
Token colours are CSS custom properties (`--tok-kw`, `--tok-str`, …) written as **inline styles** on the editor's host element. Both the theme engine and `tokenColors` write to the same properties:
|
|
509
|
+
|
|
510
|
+
1. `setTheme()` — writes all `--tok-*` values from the theme definition
|
|
511
|
+
2. `updateConfig({ tokenColors })` — overwrites specific properties on top
|
|
512
|
+
|
|
513
|
+
This means **token color overrides survive theme switches** — they are automatically re-applied each time the theme changes.
|
|
514
|
+
|
|
515
|
+
> **Tip:** Prefer `tokenColors` for quick colour customisation. Use a full `ThemeDefinition` only when you need to control backgrounds, borders, highlights, and the minimap too.
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Themes
|
|
520
|
+
|
|
521
|
+
### Built-in Themes
|
|
522
|
+
|
|
523
|
+
| ID | Style |
|
|
524
|
+
|---|---|
|
|
525
|
+
| `''` (empty string) | VR Dark *(default)* |
|
|
526
|
+
| `'vscode-dark'` | VS Code Dark+ |
|
|
527
|
+
| `'monokai'` | Monokai |
|
|
528
|
+
| `'dracula'` | Dracula |
|
|
529
|
+
| `'github-light'` | GitHub Light |
|
|
530
|
+
| `'solarized-light'` | Solarized Light |
|
|
531
|
+
|
|
532
|
+
### Switching Themes
|
|
533
|
+
|
|
534
|
+
```ts
|
|
535
|
+
// By ID
|
|
536
|
+
editor.setTheme('dracula');
|
|
537
|
+
editor.setTheme('github-light');
|
|
538
|
+
|
|
539
|
+
// By object (register not required when passing directly)
|
|
540
|
+
editor.setTheme(myCustomTheme);
|
|
541
|
+
|
|
542
|
+
// Via updateConfig
|
|
543
|
+
editor.updateConfig({ theme: 'monokai' });
|
|
544
|
+
|
|
545
|
+
// Get all registered IDs
|
|
546
|
+
const ids = editor.getThemes();
|
|
547
|
+
// ['', 'vscode-dark', 'monokai', 'dracula', 'github-light', 'solarized-light', ...]
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Importing Built-in Theme Objects
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
import {
|
|
554
|
+
BUILT_IN_THEMES, // ThemeDefinition[] — all six
|
|
555
|
+
THEME_VR_DARK,
|
|
556
|
+
THEME_VSCODE_DARK,
|
|
557
|
+
THEME_MONOKAI,
|
|
558
|
+
THEME_DRACULA,
|
|
559
|
+
THEME_GITHUB_LIGHT,
|
|
560
|
+
THEME_SOLARIZED_LIGHT,
|
|
561
|
+
} from 'syncline-editor';
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Creating a Custom Theme
|
|
565
|
+
|
|
566
|
+
A `ThemeDefinition` requires an `id`, `name`, `description`, a `light` flag, and a complete `tokens` object. Provide all keys to ensure full coverage across every UI surface.
|
|
567
|
+
|
|
568
|
+
```ts
|
|
569
|
+
import type { ThemeDefinition } from 'syncline-editor';
|
|
570
|
+
|
|
571
|
+
const myTheme: ThemeDefinition = {
|
|
572
|
+
id: 'my-purple',
|
|
573
|
+
name: 'My Purple',
|
|
574
|
+
description: 'Custom purple dark theme',
|
|
575
|
+
light: false,
|
|
576
|
+
tokens: {
|
|
577
|
+
// ── Backgrounds ────────────────────────────────────────────
|
|
578
|
+
bg0: '#0a0a14', bg1: '#0f0f1a', bg2: '#141420',
|
|
579
|
+
bg3: '#1a1a28', bg4: '#20203a',
|
|
580
|
+
|
|
581
|
+
// ── Borders ────────────────────────────────────────────────
|
|
582
|
+
border: 'rgba(255,255,255,.08)',
|
|
583
|
+
border2: 'rgba(255,255,255,.14)',
|
|
584
|
+
border3: 'rgba(255,255,255,.22)',
|
|
585
|
+
|
|
586
|
+
// ── Text ───────────────────────────────────────────────────
|
|
587
|
+
text: '#e0deff', text2: '#8080c0', text3: '#404060',
|
|
588
|
+
|
|
589
|
+
// ── Accent ─────────────────────────────────────────────────
|
|
590
|
+
accent: '#ff79c6', accent2: '#6644aa',
|
|
591
|
+
|
|
592
|
+
// ── Semantic colours ───────────────────────────────────────
|
|
593
|
+
green: '#50fa7b', orange: '#ffb86c',
|
|
594
|
+
purple: '#bd93f9', red: '#ff5555', yellow: '#f1fa8c',
|
|
595
|
+
|
|
596
|
+
// ── Cursor ─────────────────────────────────────────────────
|
|
597
|
+
cur: '#ff79c6', curGlow: 'rgba(255,121,198,.55)',
|
|
598
|
+
|
|
599
|
+
// ── Editor surface ─────────────────────────────────────────
|
|
600
|
+
curLineBg: 'rgba(255,121,198,.04)',
|
|
601
|
+
curLineGutter: '#161628',
|
|
602
|
+
gutterBg: '#0a0a14',
|
|
603
|
+
gutterHover: '#161628',
|
|
604
|
+
gutterBorder: 'rgba(255,255,255,.06)',
|
|
605
|
+
gutterNum: '#404060',
|
|
606
|
+
gutterNumAct: '#8080c0',
|
|
607
|
+
|
|
608
|
+
// ── Highlights ─────────────────────────────────────────────
|
|
609
|
+
selBg: 'rgba(189,147,249,.25)',
|
|
610
|
+
wordHlBg: 'rgba(255,121,198,.07)',
|
|
611
|
+
wordHlBorder: 'rgba(255,121,198,.25)',
|
|
612
|
+
bmBorder: 'rgba(255,121,198,.65)',
|
|
613
|
+
foldBg: 'rgba(255,121,198,.07)',
|
|
614
|
+
foldBorder: 'rgba(255,121,198,.30)',
|
|
615
|
+
|
|
616
|
+
// ── Find bar ───────────────────────────────────────────────
|
|
617
|
+
findBg: 'rgba(241,250,140,.14)',
|
|
618
|
+
findBorder: 'rgba(241,250,140,.52)',
|
|
619
|
+
findCurBg: '#f1fa8c',
|
|
620
|
+
findCurBorder: '#d4dc50',
|
|
621
|
+
findCurText: '#282a36',
|
|
622
|
+
|
|
623
|
+
// ── Sidebar ────────────────────────────────────────────────
|
|
624
|
+
fileActiveBg: 'rgba(255,121,198,.12)',
|
|
625
|
+
fileActiveText: '#ff79c6',
|
|
626
|
+
|
|
627
|
+
// ── Minimap ────────────────────────────────────────────────
|
|
628
|
+
mmBg: '#0a0a14',
|
|
629
|
+
mmSlider: 'rgba(255,255,255,.07)',
|
|
630
|
+
mmDim: 'rgba(0,0,0,.32)',
|
|
631
|
+
mmEdge: 'rgba(255,255,255,.20)',
|
|
632
|
+
|
|
633
|
+
// ── Misc ───────────────────────────────────────────────────
|
|
634
|
+
indentGuide: 'rgba(255,255,255,.07)',
|
|
635
|
+
|
|
636
|
+
// ── Syntax token colours ───────────────────────────────────
|
|
637
|
+
tokKw: '#ff79c6', tokStr: '#f1fa8c',
|
|
638
|
+
tokCmt: '#6272a4', tokFn: '#50fa7b',
|
|
639
|
+
tokNum: '#bd93f9', tokCls: '#8be9fd',
|
|
640
|
+
tokOp: '#f8f8f2', tokTyp: '#8be9fd',
|
|
641
|
+
tokDec: '#ffb86c',
|
|
642
|
+
},
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
// Register once, then switch by ID from anywhere
|
|
646
|
+
editor.registerTheme(myTheme);
|
|
647
|
+
editor.setTheme('my-purple');
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### Extending a Built-in Theme
|
|
651
|
+
|
|
652
|
+
Spread an existing theme's tokens and override only the fields you need:
|
|
653
|
+
|
|
654
|
+
```ts
|
|
655
|
+
import { THEME_DRACULA } from 'syncline-editor';
|
|
656
|
+
|
|
657
|
+
editor.registerTheme({
|
|
658
|
+
id: 'dracula-tweaked',
|
|
659
|
+
name: 'Dracula (tweaked)',
|
|
660
|
+
description: 'Dracula with warmer keyword and string colours',
|
|
661
|
+
light: false,
|
|
662
|
+
tokens: {
|
|
663
|
+
...THEME_DRACULA.tokens, // inherit everything
|
|
664
|
+
tokKw: '#ffb86c', // orange keywords instead of pink
|
|
665
|
+
tokStr: '#f1fa8c', // yellow strings instead of green
|
|
666
|
+
},
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
editor.setTheme('dracula-tweaked');
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
### ThemeTokens Reference
|
|
673
|
+
|
|
674
|
+
Every key maps to a `--<name>` CSS custom property on the editor's Shadow DOM host.
|
|
675
|
+
|
|
676
|
+
| Group | Keys |
|
|
677
|
+
|---|---|
|
|
678
|
+
| **Backgrounds** | `bg0` `bg1` `bg2` `bg3` `bg4` |
|
|
679
|
+
| **Borders** | `border` `border2` `border3` |
|
|
680
|
+
| **Text** | `text` `text2` `text3` |
|
|
681
|
+
| **Accent** | `accent` `accent2` |
|
|
682
|
+
| **Semantic** | `green` `orange` `purple` `red` `yellow` |
|
|
683
|
+
| **Cursor** | `cur` `curGlow` |
|
|
684
|
+
| **Editor surface** | `curLineBg` `curLineGutter` `gutterBg` `gutterHover` `gutterBorder` `gutterNum` `gutterNumAct` |
|
|
685
|
+
| **Highlights** | `selBg` `wordHlBg` `wordHlBorder` `bmBorder` `foldBg` `foldBorder` |
|
|
686
|
+
| **Find bar** | `findBg` `findBorder` `findCurBg` `findCurBorder` `findCurText` |
|
|
687
|
+
| **Sidebar** | `fileActiveBg` `fileActiveText` |
|
|
688
|
+
| **Minimap** | `mmBg` `mmSlider` `mmDim` `mmEdge` |
|
|
689
|
+
| **Misc** | `indentGuide` |
|
|
690
|
+
| **Syntax token colours** | `tokKw` `tokStr` `tokCmt` `tokFn` `tokNum` `tokCls` `tokOp` `tokTyp` `tokDec` |
|
|
691
|
+
|
|
692
|
+
---
|
|
693
|
+
|
|
694
|
+
## Autocomplete
|
|
695
|
+
|
|
696
|
+
The popup opens when the user has typed at least `autocompletePrefixLength` characters (default `2`) and shows up to `maxCompletions` items (default `14`).
|
|
697
|
+
|
|
698
|
+
> Emmet abbreviations also surface in the same popup with an **E** badge whenever the current prefix matches a valid abbreviation.
|
|
699
|
+
|
|
700
|
+
### The Unified `completions` Array
|
|
701
|
+
|
|
702
|
+
All custom completions — regular symbols, snippets, language-filtered items — go into a single `completions` array and are differentiated by `kind`:
|
|
703
|
+
|
|
704
|
+
```ts
|
|
705
|
+
import type { CompletionItem } from 'syncline-editor';
|
|
706
|
+
|
|
707
|
+
createEditor(container, {
|
|
708
|
+
language: 'typescript',
|
|
709
|
+
completions: [
|
|
710
|
+
// Regular function symbol — shows description on the right panel when selected
|
|
711
|
+
{
|
|
712
|
+
label: 'fetchUser',
|
|
713
|
+
kind: 'fn',
|
|
714
|
+
detail: '(id: string) => Promise<User>',
|
|
715
|
+
description: 'Fetches a user by ID from the REST API.\n\nReturns `null` if the user does not exist.',
|
|
716
|
+
},
|
|
717
|
+
|
|
718
|
+
// Type symbol
|
|
719
|
+
{
|
|
720
|
+
label: 'UserStatus',
|
|
721
|
+
kind: 'typ',
|
|
722
|
+
detail: 'enum',
|
|
723
|
+
description: 'Represents the current state of a user account.\n\n`active` | `suspended` | `pending`',
|
|
724
|
+
},
|
|
725
|
+
|
|
726
|
+
// Snippet — kind: "snip" + body template
|
|
727
|
+
{
|
|
728
|
+
label: 'mycomp',
|
|
729
|
+
kind: 'snip',
|
|
730
|
+
detail: 'React component scaffold',
|
|
731
|
+
description: 'Scaffolds a named React functional component with JSX return.',
|
|
732
|
+
body: 'export function $1Component() {\n return (\n <div>\n $2\n </div>\n );\n}',
|
|
733
|
+
language: ['typescript'], // only show in TypeScript files
|
|
734
|
+
},
|
|
735
|
+
],
|
|
736
|
+
});
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
Update completions at runtime (takes effect on the next popup open):
|
|
740
|
+
|
|
741
|
+
```ts
|
|
742
|
+
editor.updateConfig({ completions: updatedItems });
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
### CompletionItem Reference
|
|
746
|
+
|
|
747
|
+
```ts
|
|
748
|
+
interface CompletionItem {
|
|
749
|
+
label: string; // text inserted on accept; used for prefix matching
|
|
750
|
+
kind: CompletionKind; // badge type — see table below
|
|
751
|
+
detail?: string; // short hint shown on the right of the popup row
|
|
752
|
+
description?: string; // full docs shown in the side panel when selected
|
|
753
|
+
body?: string; // snippet template; Tab/Enter expands it when set
|
|
754
|
+
language?: string | string[]; // restrict to specific language(s); omit = all
|
|
755
|
+
}
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
| `kind` | Badge | Intended use |
|
|
759
|
+
|---|---|---|
|
|
760
|
+
| `'kw'` | **K** | Language keyword |
|
|
761
|
+
| `'fn'` | **f** | Function, method, CSS function |
|
|
762
|
+
| `'typ'` | **T** | Type, interface, enum |
|
|
763
|
+
| `'cls'` | **C** | Class name |
|
|
764
|
+
| `'var'` | **·** | Variable, CSS property, in-file word |
|
|
765
|
+
| `'snip'` | **S** | Snippet — set `body` and accepting it expands the template |
|
|
766
|
+
| `'emmet'` | **E** | Emmet abbreviation (auto-generated, not user-defined) |
|
|
767
|
+
|
|
768
|
+
### Description Panel
|
|
769
|
+
|
|
770
|
+
When a selected item has a `description`, a VS Code-style documentation panel opens **to the right** of the suggestion list. For `'snip'` items without an explicit `description`, the body template is shown as a preview automatically.
|
|
771
|
+
|
|
772
|
+
```ts
|
|
773
|
+
{
|
|
774
|
+
label: 'useState',
|
|
775
|
+
kind: 'fn',
|
|
776
|
+
detail: '<S>(initialState: S) => [S, Dispatch<SetStateAction<S>>]',
|
|
777
|
+
description: 'Returns a stateful value and a setter function.\n\n' +
|
|
778
|
+
'During the initial render the state equals `initialState`.\n\n' +
|
|
779
|
+
'The setter function updates the state and triggers a re-render.',
|
|
780
|
+
}
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
Multi-line descriptions use `\n` for line breaks. Markdown is not rendered — plain text only.
|
|
784
|
+
|
|
785
|
+
### Replace Built-in Completions — `replaceBuiltins`
|
|
786
|
+
|
|
787
|
+
Set `replaceBuiltins: true` to suppress all built-in language keywords and show **only** your `completions`. Perfect for DSL editors where language noise would be confusing:
|
|
788
|
+
|
|
789
|
+
```ts
|
|
790
|
+
// SQL editor — hides all JS/TS keywords, shows only SQL
|
|
791
|
+
createEditor(container, {
|
|
792
|
+
language: 'text',
|
|
793
|
+
replaceBuiltins: true,
|
|
794
|
+
completions: [
|
|
795
|
+
{ label: 'SELECT', kind: 'kw', detail: 'SQL', description: 'Retrieve rows from a table.' },
|
|
796
|
+
{ label: 'FROM', kind: 'kw', detail: 'SQL' },
|
|
797
|
+
{ label: 'WHERE', kind: 'kw', detail: 'SQL', description: 'Filter rows by a condition.' },
|
|
798
|
+
{ label: 'JOIN', kind: 'kw', detail: 'SQL' },
|
|
799
|
+
{ label: 'GROUP BY', kind: 'kw', detail: 'SQL' },
|
|
800
|
+
{ label: 'ORDER BY', kind: 'kw', detail: 'SQL' },
|
|
801
|
+
{ label: 'LIMIT', kind: 'kw', detail: 'SQL', description: 'Limit the number of rows returned.' },
|
|
802
|
+
],
|
|
803
|
+
});
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
### Popup Size
|
|
807
|
+
|
|
808
|
+
```ts
|
|
809
|
+
createEditor(container, {
|
|
810
|
+
maxCompletions: 20, // max items shown (default 14)
|
|
811
|
+
autocompletePrefixLength: 1, // trigger after 1 char (default 2)
|
|
812
|
+
});
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
### Priority Order
|
|
816
|
+
|
|
817
|
+
When multiple sources are configured, the final suggestion list is built in this order:
|
|
818
|
+
|
|
819
|
+
1. `provideCompletions` callback — if it returns a non-null array, it **wins entirely** and all other sources are skipped
|
|
820
|
+
2. `completions` with `replaceBuiltins: true` — your items replace language defaults
|
|
821
|
+
3. `completions` (default) — merged on top of language defaults
|
|
822
|
+
4. Language built-ins — keywords, types, and functions for the active language
|
|
823
|
+
5. In-file words — identifiers extracted from the current document, always appended last
|
|
824
|
+
|
|
825
|
+
---
|
|
826
|
+
|
|
827
|
+
## Hover Documentation
|
|
828
|
+
|
|
829
|
+
When `hover: true` (the default), resting the pointer over a recognised identifier for ~500 ms shows a floating tooltip with a **title**, **type signature**, and **description** — exactly like VS Code's hover.
|
|
830
|
+
|
|
831
|
+
### How the lookup works (priority order)
|
|
832
|
+
|
|
833
|
+
1. **Built-in docs** — ~75 entries covering common JS/TS APIs (`console.log`, `Math.floor`, `Promise.all`, `fetch`, `async`, `const`, utility types like `Record`, `Partial`, …)
|
|
834
|
+
2. **`completions` array** — any item in your `completions` config that has a `description` (and/or `detail`) is automatically available as a hover doc. No extra config needed.
|
|
835
|
+
3. **`provideHover` callback** — a runtime function you supply for anything not covered above.
|
|
836
|
+
|
|
837
|
+
### Hover from `completions` — automatic
|
|
838
|
+
|
|
839
|
+
If you already define completions with descriptions, hover just works:
|
|
840
|
+
|
|
841
|
+
```ts
|
|
842
|
+
const editor = createEditor(container, {
|
|
843
|
+
completions: [
|
|
844
|
+
{
|
|
845
|
+
label: 'myQuery',
|
|
846
|
+
kind: 'fn',
|
|
847
|
+
detail: '(id: string) => Promise<User>', // → shown as type signature
|
|
848
|
+
description: 'Fetches a user by their ID.\n\nReturns null if the user does not exist.', // → shown as body
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
label: 'MyEntity',
|
|
852
|
+
kind: 'cls',
|
|
853
|
+
detail: 'class MyEntity',
|
|
854
|
+
description: 'The core domain model. Includes all persistence and validation logic.',
|
|
855
|
+
},
|
|
856
|
+
],
|
|
857
|
+
});
|
|
858
|
+
// Hovering over "myQuery" or "MyEntity" in the editor now shows a tooltip automatically.
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
### `provideHover` — fully custom docs
|
|
862
|
+
|
|
863
|
+
Use `provideHover` for symbols that live in an external symbol table, API schema, or documentation database:
|
|
864
|
+
|
|
865
|
+
```ts
|
|
866
|
+
const editor = createEditor(container, {
|
|
867
|
+
provideHover: (ctx) => {
|
|
868
|
+
// ctx = { word, row, col, line, language, doc }
|
|
869
|
+
|
|
870
|
+
// Example: look up from a GraphQL schema
|
|
871
|
+
const field = mySchema.getField(ctx.word);
|
|
872
|
+
if (!field) return null; // return null to show nothing
|
|
873
|
+
|
|
874
|
+
return {
|
|
875
|
+
title: field.name,
|
|
876
|
+
type: field.type, // e.g. "String!"
|
|
877
|
+
body: field.description, // e.g. "The unique identifier of this node."
|
|
878
|
+
};
|
|
879
|
+
},
|
|
880
|
+
});
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
### `HoverDoc` reference
|
|
884
|
+
|
|
885
|
+
```ts
|
|
886
|
+
interface HoverDoc {
|
|
887
|
+
title: string; // Bold name at the top of the tooltip
|
|
888
|
+
type?: string; // Type signature in monospace (optional)
|
|
889
|
+
body: string; // Description text
|
|
890
|
+
}
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
### `HoverContext` reference
|
|
894
|
+
|
|
895
|
+
```ts
|
|
896
|
+
interface HoverContext {
|
|
897
|
+
word: string; // The identifier under the pointer (may include dot prefix, e.g. "console.log")
|
|
898
|
+
row: number; // Zero-based document line
|
|
899
|
+
col: number; // Zero-based character column
|
|
900
|
+
line: string; // Full text of the hovered line
|
|
901
|
+
language: string; // Active language (e.g. "typescript")
|
|
902
|
+
doc: readonly string[]; // Full document as array of lines
|
|
903
|
+
}
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
### Disable hover entirely
|
|
907
|
+
|
|
908
|
+
```ts
|
|
909
|
+
editor.updateConfig({ hover: false });
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
---
|
|
913
|
+
|
|
914
|
+
## Snippets
|
|
915
|
+
|
|
916
|
+
Snippets let you type a short trigger word and press `Tab` to expand a full multi-line block. The cursor lands at the first `$1` tab stop after expansion.
|
|
917
|
+
|
|
918
|
+
Snippets appear in the unified autocomplete popup with an **S** badge alongside keywords, functions, and Emmet abbreviations. When a snippet item is selected in the popup, its body template is previewed in the description panel on the right.
|
|
919
|
+
|
|
920
|
+
### Built-in Snippets by Language
|
|
921
|
+
|
|
922
|
+
**TypeScript / JavaScript**
|
|
923
|
+
|
|
924
|
+
| Trigger | Expands to |
|
|
925
|
+
|---|---|
|
|
926
|
+
| `fn` | `function name(params) { }` |
|
|
927
|
+
| `afn` | `const name = (params) => { };` |
|
|
928
|
+
| `asyncfn` | `async function name(params): Promise<T> { }` |
|
|
929
|
+
| `cl` | `class Name { constructor(params) { } }` |
|
|
930
|
+
| `forof` | `for (const item of iterable) { }` |
|
|
931
|
+
| `forin` | `for (const key in object) { }` |
|
|
932
|
+
| `trycatch` | `try { } catch (error) { }` |
|
|
933
|
+
| `promise` | `new Promise<T>((resolve, reject) => { })` |
|
|
934
|
+
| `imp` | `import { name } from 'module';` |
|
|
935
|
+
| `iface` | `interface Name { field: Type; }` *(TypeScript only)* |
|
|
936
|
+
| `ife` | Immediately-invoked function expression |
|
|
937
|
+
| `sw` | `switch (expr) { case val: … default: … }` |
|
|
938
|
+
|
|
939
|
+
**CSS**
|
|
940
|
+
|
|
941
|
+
| Trigger | Expands to |
|
|
942
|
+
|---|---|
|
|
943
|
+
| `flex` | Flexbox container (`display: flex; align-items; justify-content`) |
|
|
944
|
+
| `grid` | CSS grid (`display: grid; grid-template-columns; gap`) |
|
|
945
|
+
| `media` | `@media (max-width: 768px) { }` |
|
|
946
|
+
| `anim` | `@keyframes name { from { } to { } }` |
|
|
947
|
+
| `var` | `--name: value;` |
|
|
948
|
+
|
|
949
|
+
**HTML / General** *(available in all languages)*
|
|
950
|
+
|
|
951
|
+
| Trigger | Expands to |
|
|
952
|
+
|---|---|
|
|
953
|
+
| `accordion` | `<details><summary>…</summary><div>…</div></details>` |
|
|
954
|
+
| `card` | Card with header, body, and footer divs |
|
|
955
|
+
| `navbar` | `<nav>` with anchor links |
|
|
956
|
+
| `modal` | Modal dialog with header, body, and footer |
|
|
957
|
+
| `table` | `<table>` with `<thead>` and `<tbody>` |
|
|
958
|
+
|
|
959
|
+
### Custom Snippets
|
|
960
|
+
|
|
961
|
+
Add your own snippets to the unified `completions` array with `kind: 'snip'` and a `body` template. They appear in the popup with an **S** badge and also expand on Tab when the label is typed exactly:
|
|
962
|
+
|
|
963
|
+
```ts
|
|
964
|
+
import type { CompletionItem } from 'syncline-editor';
|
|
965
|
+
|
|
966
|
+
createEditor(container, {
|
|
967
|
+
language: 'typescript',
|
|
968
|
+
completions: [
|
|
969
|
+
{
|
|
970
|
+
label: 'rcomp',
|
|
971
|
+
kind: 'snip',
|
|
972
|
+
detail: 'React component',
|
|
973
|
+
description: 'Scaffolds a typed React functional component.',
|
|
974
|
+
body: 'export function $1({ $2 }: $1Props) {\n return (\n <div>\n $3\n </div>\n );\n}',
|
|
975
|
+
language: ['typescript'],
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
label: 'clog',
|
|
979
|
+
kind: 'snip',
|
|
980
|
+
detail: 'console.log with label',
|
|
981
|
+
body: "console.log('$1:', $2);",
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
label: 'todo',
|
|
985
|
+
kind: 'snip',
|
|
986
|
+
detail: 'TODO comment',
|
|
987
|
+
body: '// TODO($1): $2',
|
|
988
|
+
},
|
|
989
|
+
],
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
// Add more at runtime
|
|
993
|
+
editor.updateConfig({ completions: [...existing, newItem] });
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
Disable all snippet expansion (built-in and custom):
|
|
997
|
+
|
|
998
|
+
```ts
|
|
999
|
+
createEditor(container, { snippetExpansion: false });
|
|
1000
|
+
```
|
|
1001
|
+
|
|
1002
|
+
### Snippet Body Syntax
|
|
1003
|
+
|
|
1004
|
+
| Placeholder | Meaning |
|
|
1005
|
+
|---|---|
|
|
1006
|
+
| `$1` | First cursor stop — where the caret lands immediately after expansion |
|
|
1007
|
+
| `$2`, `$3`, … | Additional tab stops (rendered as visual hints; no cycling yet) |
|
|
1008
|
+
| `\n` | Line break — continuation lines inherit the trigger line's indentation |
|
|
1009
|
+
|
|
1010
|
+
> **Priority:** Emmet is tried first on Tab. If both Emmet and a snippet match the same word, Emmet wins.
|
|
1011
|
+
|
|
1012
|
+
---
|
|
1013
|
+
|
|
1014
|
+
## Emmet
|
|
1015
|
+
|
|
1016
|
+
Type an abbreviation and press `Tab` to expand. A preview tooltip appears above the cursor while a valid abbreviation is detected.
|
|
1017
|
+
|
|
1018
|
+
```
|
|
1019
|
+
ul>li*5 → <ul><li></li> × 5</ul>
|
|
1020
|
+
div.container → <div class="container"></div>
|
|
1021
|
+
input[type=text] → <input type="text">
|
|
1022
|
+
a:href → <a href=""></a>
|
|
1023
|
+
section>h2+p → <section><h2></h2><p></p></section>
|
|
1024
|
+
```
|
|
1025
|
+
|
|
1026
|
+
Emmet abbreviations also appear in the autocomplete popup with an **E** badge — select and press `Tab` or `Enter` to expand.
|
|
1027
|
+
|
|
1028
|
+
```ts
|
|
1029
|
+
createEditor(container, { emmet: false }); // disable
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
---
|
|
1033
|
+
|
|
1034
|
+
## Dynamic Completion Provider
|
|
1035
|
+
|
|
1036
|
+
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.
|
|
1037
|
+
|
|
1038
|
+
### provideCompletions
|
|
1039
|
+
|
|
1040
|
+
```ts
|
|
1041
|
+
import type { CompletionContext, CompletionItem } from 'syncline-editor';
|
|
1042
|
+
|
|
1043
|
+
createEditor(container, {
|
|
1044
|
+
provideCompletions: (ctx: CompletionContext): CompletionItem[] | null => {
|
|
1045
|
+
// ctx.prefix — characters typed before the cursor on the current line
|
|
1046
|
+
// ctx.language — active language string ('typescript', 'css', etc.)
|
|
1047
|
+
// ctx.line — zero-based cursor row
|
|
1048
|
+
// ctx.col — zero-based cursor column
|
|
1049
|
+
// ctx.doc — full document as string[] (one element per line)
|
|
1050
|
+
|
|
1051
|
+
// Example: variable completions after "$"
|
|
1052
|
+
if (ctx.prefix.startsWith('$')) {
|
|
1053
|
+
return myVariableTable.map(v => ({
|
|
1054
|
+
label: v.name,
|
|
1055
|
+
kind: 'var' as const,
|
|
1056
|
+
detail: v.type,
|
|
1057
|
+
description: v.docs,
|
|
1058
|
+
}));
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// Example: import suggestions inside import statements
|
|
1062
|
+
const importLine = ctx.doc[ctx.line];
|
|
1063
|
+
if (importLine.startsWith('import ') && ctx.prefix.length >= 1) {
|
|
1064
|
+
return myModuleList.map(m => ({
|
|
1065
|
+
label: m.name,
|
|
1066
|
+
kind: 'cls' as const,
|
|
1067
|
+
detail: m.version,
|
|
1068
|
+
}));
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// Return null to fall through to built-in completions
|
|
1072
|
+
return null;
|
|
1073
|
+
},
|
|
1074
|
+
});
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
Update the provider at runtime:
|
|
1078
|
+
|
|
1079
|
+
```ts
|
|
1080
|
+
editor.updateConfig({
|
|
1081
|
+
provideCompletions: (ctx) => newProvider(ctx),
|
|
1082
|
+
});
|
|
1083
|
+
```
|
|
1084
|
+
|
|
1085
|
+
### CompletionContext
|
|
1086
|
+
|
|
1087
|
+
```ts
|
|
1088
|
+
interface CompletionContext {
|
|
1089
|
+
prefix: string; // characters typed before the cursor (the match prefix)
|
|
1090
|
+
language: string; // active language ID
|
|
1091
|
+
line: number; // cursor row, zero-based
|
|
1092
|
+
col: number; // cursor column, zero-based
|
|
1093
|
+
doc: string[]; // full document split into lines
|
|
1094
|
+
}
|
|
1095
|
+
```
|
|
1096
|
+
|
|
1097
|
+
---
|
|
1098
|
+
|
|
1099
|
+
## Events & Callbacks
|
|
1100
|
+
|
|
1101
|
+
All callbacks are defined in `EditorConfig` and can be updated via `updateConfig` at any time:
|
|
1102
|
+
|
|
1103
|
+
```ts
|
|
1104
|
+
const editor = createEditor(container, {
|
|
1105
|
+
onChange: (value) => {
|
|
1106
|
+
// Fires after every edit — keystroke, paste, undo, setValue(), …
|
|
1107
|
+
// value: full document as a newline-joined string
|
|
1108
|
+
autoSave(value);
|
|
1109
|
+
},
|
|
1110
|
+
|
|
1111
|
+
onCursorChange: (pos) => {
|
|
1112
|
+
// pos.row and pos.col are zero-based
|
|
1113
|
+
statusEl.textContent = `Ln ${pos.row + 1}, Col ${pos.col + 1}`;
|
|
1114
|
+
},
|
|
1115
|
+
|
|
1116
|
+
onSelectionChange: (sel) => {
|
|
1117
|
+
// sel is null when the selection is cleared
|
|
1118
|
+
if (sel) {
|
|
1119
|
+
const rows = Math.abs(sel.fr - sel.ar) + 1;
|
|
1120
|
+
console.log(`${rows} line(s) selected`);
|
|
1121
|
+
}
|
|
1122
|
+
},
|
|
1123
|
+
|
|
1124
|
+
onFocus: () => container.classList.add('editor-focused'),
|
|
1125
|
+
onBlur: () => container.classList.remove('editor-focused'),
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
// Replace a callback at runtime — no re-creation needed
|
|
1129
|
+
editor.updateConfig({
|
|
1130
|
+
onChange: (value) => newAutoSave(value),
|
|
1131
|
+
});
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
---
|
|
1135
|
+
|
|
1136
|
+
## Advanced Features
|
|
1137
|
+
|
|
1138
|
+
### Multi-cursor
|
|
1139
|
+
|
|
1140
|
+
| Action | Shortcut |
|
|
1141
|
+
|---|---|
|
|
1142
|
+
| Add cursor at click position | `Alt / Option + Click` |
|
|
1143
|
+
| Select next occurrence of word | `Ctrl / Cmd + D` |
|
|
1144
|
+
| Clear all extra cursors | `Escape` |
|
|
1145
|
+
|
|
1146
|
+
Every cursor has its own independent selection. All cursors type, delete, and move in sync.
|
|
1147
|
+
|
|
1148
|
+
```ts
|
|
1149
|
+
createEditor(container, { multiCursor: false }); // disable
|
|
1150
|
+
```
|
|
1151
|
+
|
|
1152
|
+
### Find & Replace
|
|
1153
|
+
|
|
1154
|
+
Open programmatically:
|
|
1155
|
+
|
|
1156
|
+
```ts
|
|
1157
|
+
editor.executeCommand('find'); // opens find bar
|
|
1158
|
+
editor.executeCommand('findReplace'); // opens with replace row visible
|
|
1159
|
+
```
|
|
1160
|
+
|
|
1161
|
+
The find bar supports:
|
|
1162
|
+
- **Match case** — `Aa` toggle
|
|
1163
|
+
- **Regex mode** — `.*` toggle
|
|
1164
|
+
- **Navigate** — `↑` / `↓` buttons or `Enter` / `Shift+Enter`
|
|
1165
|
+
- **Replace one** — replaces the current highlighted match
|
|
1166
|
+
- **Replace all** — replaces every match in the document
|
|
1167
|
+
|
|
1168
|
+
Configuration options:
|
|
1169
|
+
|
|
1170
|
+
```ts
|
|
1171
|
+
// Disable both find and replace
|
|
1172
|
+
createEditor(container, { find: false });
|
|
1173
|
+
|
|
1174
|
+
// Find only — no replace row
|
|
1175
|
+
createEditor(container, { find: true, findReplace: false });
|
|
1176
|
+
```
|
|
1177
|
+
|
|
1178
|
+
### Code Folding
|
|
1179
|
+
|
|
1180
|
+
Click the `▾` / `▸` gutter toggle next to any foldable block. Folded sections show a dashed bottom border and a `…` indicator.
|
|
1181
|
+
|
|
1182
|
+
```ts
|
|
1183
|
+
createEditor(container, { codeFolding: false }); // disable
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
### Bracket Matching
|
|
1187
|
+
|
|
1188
|
+
When the cursor is adjacent to `(`, `)`, `[`, `]`, `{`, or `}`, both the opening and closing characters are highlighted with a border.
|
|
1189
|
+
|
|
1190
|
+
```ts
|
|
1191
|
+
createEditor(container, { bracketMatching: false }); // disable
|
|
1192
|
+
```
|
|
1193
|
+
|
|
1194
|
+
Colour controlled by `ThemeTokens.bmBorder`.
|
|
1195
|
+
|
|
1196
|
+
### Word Highlight
|
|
1197
|
+
|
|
1198
|
+
When the cursor rests on a word, all other occurrences in the document are subtly boxed. Automatically disabled while a selection is active.
|
|
1199
|
+
|
|
1200
|
+
```ts
|
|
1201
|
+
createEditor(container, { wordHighlight: false }); // disable
|
|
1202
|
+
```
|
|
1203
|
+
|
|
1204
|
+
Colours: `ThemeTokens.wordHlBg` (fill) and `ThemeTokens.wordHlBorder` (outline).
|
|
1205
|
+
|
|
1206
|
+
### Whitespace Rendering
|
|
1207
|
+
|
|
1208
|
+
```ts
|
|
1209
|
+
createEditor(container, { renderWhitespace: 'none' }); // default — invisible
|
|
1210
|
+
createEditor(container, { renderWhitespace: 'boundary' }); // leading/trailing only
|
|
1211
|
+
createEditor(container, { renderWhitespace: 'all' }); // every space and tab
|
|
1212
|
+
```
|
|
1213
|
+
|
|
1214
|
+
Spaces render as `·` and tabs as `→`.
|
|
1215
|
+
|
|
1216
|
+
### Cursor Styles
|
|
1217
|
+
|
|
1218
|
+
```ts
|
|
1219
|
+
createEditor(container, { cursorStyle: 'line' }); // thin vertical beam (default)
|
|
1220
|
+
createEditor(container, { cursorStyle: 'block' }); // filled block behind character
|
|
1221
|
+
createEditor(container, { cursorStyle: 'underline' }); // horizontal bar below character
|
|
1222
|
+
|
|
1223
|
+
createEditor(container, { cursorBlinkRate: 2000 }); // slower blink
|
|
1224
|
+
createEditor(container, { cursorBlinkRate: 999999 }); // no blink
|
|
1225
|
+
```
|
|
1226
|
+
|
|
1227
|
+
### Minimap
|
|
1228
|
+
|
|
1229
|
+
Canvas-rendered pixel-accurate overview of the full document with a draggable viewport slider. Scroll, drag the slider, or click anywhere on the minimap to jump.
|
|
1230
|
+
|
|
1231
|
+
```ts
|
|
1232
|
+
createEditor(container, {
|
|
1233
|
+
showMinimap: true,
|
|
1234
|
+
minimapWidth: 100, // pixels (default 120)
|
|
1235
|
+
});
|
|
1236
|
+
```
|
|
1237
|
+
|
|
1238
|
+
Colours: `ThemeTokens.mmBg`, `mmSlider`, `mmDim`, `mmEdge`.
|
|
1239
|
+
|
|
1240
|
+
### Word Wrap
|
|
1241
|
+
|
|
1242
|
+
```ts
|
|
1243
|
+
createEditor(container, {
|
|
1244
|
+
wordWrap: true,
|
|
1245
|
+
wrapColumn: 100, // default 80
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
// Toggle at runtime — also available via Alt+Z keyboard shortcut
|
|
1249
|
+
editor.executeCommand('toggleWordWrap');
|
|
1250
|
+
editor.updateConfig({ wordWrap: !currentWrap });
|
|
1251
|
+
```
|
|
1252
|
+
|
|
1253
|
+
### Indent Guides
|
|
1254
|
+
|
|
1255
|
+
Faint vertical lines connecting matching indentation levels:
|
|
1256
|
+
|
|
1257
|
+
```ts
|
|
1258
|
+
createEditor(container, { showIndentGuides: false }); // disable
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
Colour controlled by `ThemeTokens.indentGuide`.
|
|
1262
|
+
|
|
1263
|
+
### Active Line Highlight
|
|
1264
|
+
|
|
1265
|
+
The row containing the cursor gets a distinct background and gutter colour:
|
|
1266
|
+
|
|
1267
|
+
```ts
|
|
1268
|
+
createEditor(container, { highlightActiveLine: false }); // disable
|
|
1269
|
+
```
|
|
1270
|
+
|
|
1271
|
+
Colours: `ThemeTokens.curLineBg` (row background) and `ThemeTokens.curLineGutter` (gutter cell).
|
|
1272
|
+
|
|
1273
|
+
### Read-Only Mode
|
|
1274
|
+
|
|
1275
|
+
```ts
|
|
1276
|
+
const viewer = createEditor(container, {
|
|
1277
|
+
value: sourceCode,
|
|
1278
|
+
readOnly: true,
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
// These are all silently no-ops in read-only mode:
|
|
1282
|
+
viewer.insertText('test');
|
|
1283
|
+
viewer.executeCommand('cut');
|
|
1284
|
+
viewer.executeCommand('deleteLine');
|
|
1285
|
+
|
|
1286
|
+
// These still work normally:
|
|
1287
|
+
viewer.getCursor();
|
|
1288
|
+
viewer.getSelection();
|
|
1289
|
+
viewer.executeCommand('copy');
|
|
1290
|
+
viewer.executeCommand('find');
|
|
1291
|
+
|
|
1292
|
+
// Toggle at runtime:
|
|
1293
|
+
editor.updateConfig({ readOnly: false }); // re-enable editing
|
|
1294
|
+
editor.updateConfig({ readOnly: true }); // lock again
|
|
1295
|
+
```
|
|
1296
|
+
|
|
1297
|
+
---
|
|
1298
|
+
|
|
1299
|
+
## Behavioral Options
|
|
1300
|
+
|
|
1301
|
+
### Auto-Close Pairs
|
|
1302
|
+
|
|
1303
|
+
```ts
|
|
1304
|
+
// Default — close all bracket and quote types
|
|
1305
|
+
createEditor(container, {
|
|
1306
|
+
autoClosePairs: { '(': ')', '[': ']', '{': '}', '"': '"', "'": "'", '`': '`' },
|
|
1307
|
+
});
|
|
1308
|
+
|
|
1309
|
+
// Only parentheses and curly braces
|
|
1310
|
+
createEditor(container, {
|
|
1311
|
+
autoClosePairs: { '(': ')', '{': '}' },
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
// Disable auto-closing entirely
|
|
1315
|
+
createEditor(container, { autoClosePairs: {} });
|
|
1316
|
+
```
|
|
1317
|
+
|
|
1318
|
+
Typing the opening character inserts the closing character and places the cursor between them. Typing the closing character again skips over it.
|
|
1319
|
+
|
|
1320
|
+
### Line Comment Token
|
|
1321
|
+
|
|
1322
|
+
Controls the `Ctrl+/` toggle-comment prefix:
|
|
1323
|
+
|
|
1324
|
+
```ts
|
|
1325
|
+
// Auto-detect from language (default — // for TS/JS/CSS, nothing for JSON/Markdown)
|
|
1326
|
+
createEditor(container, { lineCommentToken: '' });
|
|
1327
|
+
|
|
1328
|
+
// Python / Ruby / YAML / shell
|
|
1329
|
+
createEditor(container, { lineCommentToken: '#' });
|
|
1330
|
+
|
|
1331
|
+
// SQL / Lua
|
|
1332
|
+
createEditor(container, { lineCommentToken: '--' });
|
|
1333
|
+
|
|
1334
|
+
// LaTeX
|
|
1335
|
+
createEditor(container, { lineCommentToken: '%' });
|
|
1336
|
+
```
|
|
1337
|
+
|
|
1338
|
+
### Word Separators
|
|
1339
|
+
|
|
1340
|
+
Characters treated as word boundaries for double-click selection, `Ctrl+Left/Right`, and `Ctrl+D`:
|
|
1341
|
+
|
|
1342
|
+
```ts
|
|
1343
|
+
// Default — use built-in \w word boundary
|
|
1344
|
+
createEditor(container, { wordSeparators: '' });
|
|
1345
|
+
|
|
1346
|
+
// Include hyphens (good for CSS, kebab-case identifiers)
|
|
1347
|
+
createEditor(container, {
|
|
1348
|
+
wordSeparators: '`~!@#%^&*()-=+[{}]\\|;:\'",.<>/?',
|
|
1349
|
+
});
|
|
1350
|
+
```
|
|
1351
|
+
|
|
1352
|
+
### Undo Batch Window
|
|
1353
|
+
|
|
1354
|
+
Controls how keystrokes are grouped into undo steps:
|
|
1355
|
+
|
|
1356
|
+
```ts
|
|
1357
|
+
// Default — group keystrokes within 700 ms into one undo step
|
|
1358
|
+
createEditor(container, { undoBatchMs: 700 });
|
|
1359
|
+
|
|
1360
|
+
// Per-keystroke undo — every character is its own step
|
|
1361
|
+
createEditor(container, { undoBatchMs: 0 });
|
|
1362
|
+
|
|
1363
|
+
// One undo per ~2 seconds of continuous typing
|
|
1364
|
+
createEditor(container, { undoBatchMs: 2000 });
|
|
1365
|
+
```
|
|
1366
|
+
|
|
1367
|
+
### Feature Flags
|
|
1368
|
+
|
|
1369
|
+
Disable individual features at creation or toggle them at runtime:
|
|
1370
|
+
|
|
1371
|
+
```ts
|
|
1372
|
+
createEditor(container, {
|
|
1373
|
+
bracketMatching: false, // no bracket-pair highlight
|
|
1374
|
+
codeFolding: false, // no fold buttons in gutter
|
|
1375
|
+
emmet: false, // no Emmet expansion
|
|
1376
|
+
snippetExpansion: false, // no snippet Tab-expansion
|
|
1377
|
+
autocomplete: false, // no completion popup
|
|
1378
|
+
multiCursor: false, // no Alt+Click / Ctrl+D
|
|
1379
|
+
find: false, // no find bar
|
|
1380
|
+
findReplace: false, // no replace row
|
|
1381
|
+
wordSelection: false, // double-click places cursor only
|
|
1382
|
+
wordHighlight: false, // no occurrence boxes
|
|
1383
|
+
highlightActiveLine: false, // no active-line background
|
|
1384
|
+
showIndentGuides: false, // no indent guide lines
|
|
1385
|
+
showGutter: false, // hide line numbers
|
|
1386
|
+
showMinimap: false, // hide minimap panel
|
|
1387
|
+
showStatusBar: false, // hide status bar
|
|
1388
|
+
});
|
|
1389
|
+
```
|
|
1390
|
+
|
|
1391
|
+
---
|
|
1392
|
+
|
|
1393
|
+
## Keyboard Shortcuts
|
|
1394
|
+
|
|
1395
|
+
| Shortcut | Action | Controlled by |
|
|
1396
|
+
|---|---|---|
|
|
1397
|
+
| `Ctrl / Cmd + Z` | Undo | `undoBatchMs`, `maxUndoHistory` |
|
|
1398
|
+
| `Ctrl / Cmd + Y` or `Ctrl + Shift + Z` | Redo | — |
|
|
1399
|
+
| `Ctrl / Cmd + A` | Select all | — |
|
|
1400
|
+
| `Ctrl / Cmd + C` | Copy | — |
|
|
1401
|
+
| `Ctrl / Cmd + X` | Cut | `readOnly` |
|
|
1402
|
+
| `Ctrl / Cmd + V` | Paste | `readOnly` |
|
|
1403
|
+
| `Ctrl / Cmd + F` | Open find bar | `find` |
|
|
1404
|
+
| `Ctrl / Cmd + H` | Open find + replace | `findReplace` |
|
|
1405
|
+
| `Ctrl / Cmd + D` | Select next occurrence | `multiCursor` |
|
|
1406
|
+
| `Ctrl / Cmd + Shift + D` | Duplicate line | `readOnly` |
|
|
1407
|
+
| `Ctrl / Cmd + K` | Delete line | `readOnly` |
|
|
1408
|
+
| `Ctrl / Cmd + /` | Toggle line comment | `lineCommentToken`, `readOnly` |
|
|
1409
|
+
| `Alt / Option + Z` | Toggle word wrap | — |
|
|
1410
|
+
| `Alt / Option + ↑` | Move current line (or selected block) up | `readOnly` |
|
|
1411
|
+
| `Alt / Option + ↓` | Move current line (or selected block) down | `readOnly` |
|
|
1412
|
+
| `Tab` | Indent / expand Emmet / expand snippet | `emmet`, `snippetExpansion`, `tabSize` |
|
|
1413
|
+
| `Shift + Tab` | Outdent | `tabSize` |
|
|
1414
|
+
| `Alt / Option + Click` | Add extra cursor | `multiCursor` |
|
|
1415
|
+
| `Double-click` | Select word | `wordSelection`, `wordSeparators` |
|
|
1416
|
+
| `Triple-click` | Select entire line | — |
|
|
1417
|
+
| `Escape` | Clear selection · close popup · close find · remove extra cursors | — |
|
|
1418
|
+
| `↑ ↓ ← →` | Move cursor | — |
|
|
1419
|
+
| `Cmd + ← →` (Mac) | Start / end of line | — |
|
|
1420
|
+
| `Cmd + ↑ ↓` (Mac) | Start / end of document | — |
|
|
1421
|
+
| `Ctrl + ← →` (Win) | Word skip left / right | `wordSeparators` |
|
|
1422
|
+
| `Ctrl + Home / End` (Win) | Start / end of document | — |
|
|
1423
|
+
| `Option + ← →` (Mac) | Word skip left / right | `wordSeparators` |
|
|
1424
|
+
| `Shift + arrow keys` | Extend selection | — |
|
|
1425
|
+
| `Shift + Cmd/Ctrl + arrow` | Extend selection to boundary | — |
|
|
1426
|
+
| `Home` | First non-whitespace (press again = column 0) | — |
|
|
1427
|
+
| `End` | End of line | — |
|
|
1428
|
+
| `PageUp / PageDown` | Scroll one viewport | — |
|
|
1429
|
+
| `Backspace` | Delete character left | `readOnly` |
|
|
1430
|
+
| `Delete` | Delete character right | `readOnly` |
|
|
1431
|
+
|
|
1432
|
+
All mutating shortcuts are silently blocked when `readOnly: true`. Navigation, selection, and `Ctrl+C` always work.
|
|
1433
|
+
|
|
1434
|
+
---
|
|
1435
|
+
|
|
1436
|
+
## Recipes
|
|
1437
|
+
|
|
1438
|
+
Real-world patterns you can copy and adapt.
|
|
1439
|
+
|
|
1440
|
+
### Monaco-style Editor Embed
|
|
1441
|
+
|
|
1442
|
+
A feature-complete editor inside a fixed-height container, closely resembling a VS Code embed:
|
|
1443
|
+
|
|
1444
|
+
```ts
|
|
1445
|
+
import { createEditor } from 'syncline-editor';
|
|
1446
|
+
|
|
1447
|
+
const editor = createEditor(document.getElementById('editor')!, {
|
|
1448
|
+
value: initialCode,
|
|
1449
|
+
language: 'typescript',
|
|
1450
|
+
theme: 'vscode-dark',
|
|
1451
|
+
fontSize: 13,
|
|
1452
|
+
lineHeight: 20,
|
|
1453
|
+
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
|
|
1454
|
+
tabSize: 2,
|
|
1455
|
+
insertSpaces: true,
|
|
1456
|
+
wordWrap: false,
|
|
1457
|
+
showMinimap: true,
|
|
1458
|
+
showGutter: true,
|
|
1459
|
+
showStatusBar: true,
|
|
1460
|
+
showIndentGuides: true,
|
|
1461
|
+
bracketMatching: true,
|
|
1462
|
+
codeFolding: true,
|
|
1463
|
+
emmet: true,
|
|
1464
|
+
autocomplete: true,
|
|
1465
|
+
multiCursor: true,
|
|
1466
|
+
find: true,
|
|
1467
|
+
findReplace: true,
|
|
1468
|
+
wordHighlight: true,
|
|
1469
|
+
highlightActiveLine: true,
|
|
1470
|
+
renderWhitespace: 'boundary',
|
|
1471
|
+
onChange: (v) => localStorage.setItem('draft', v),
|
|
1472
|
+
onFocus: () => console.log('editor focused'),
|
|
1473
|
+
});
|
|
1474
|
+
```
|
|
1475
|
+
|
|
1476
|
+
```css
|
|
1477
|
+
#editor { width: 100%; height: 100vh; }
|
|
1478
|
+
```
|
|
1479
|
+
|
|
1480
|
+
---
|
|
1481
|
+
|
|
1482
|
+
### DSL / SQL Editor
|
|
1483
|
+
|
|
1484
|
+
Replace all built-in completions with domain-specific keywords — zero JS/TS noise:
|
|
1485
|
+
|
|
1486
|
+
```ts
|
|
1487
|
+
import { createEditor } from 'syncline-editor';
|
|
1488
|
+
import type { CompletionItem } from 'syncline-editor';
|
|
1489
|
+
|
|
1490
|
+
const sqlCompletions: CompletionItem[] = [
|
|
1491
|
+
{ label: 'SELECT', kind: 'kw', detail: 'SQL', description: 'Retrieve rows from a table or view.' },
|
|
1492
|
+
{ label: 'FROM', kind: 'kw', detail: 'SQL', description: 'Specify the source table.' },
|
|
1493
|
+
{ label: 'WHERE', kind: 'kw', detail: 'SQL', description: 'Filter rows using a predicate.' },
|
|
1494
|
+
{ label: 'JOIN', kind: 'kw', detail: 'SQL' },
|
|
1495
|
+
{ label: 'LEFT JOIN', kind: 'kw', detail: 'SQL' },
|
|
1496
|
+
{ label: 'INNER JOIN', kind: 'kw', detail: 'SQL' },
|
|
1497
|
+
{ label: 'GROUP BY', kind: 'kw', detail: 'SQL' },
|
|
1498
|
+
{ label: 'ORDER BY', kind: 'kw', detail: 'SQL' },
|
|
1499
|
+
{ label: 'HAVING', kind: 'kw', detail: 'SQL' },
|
|
1500
|
+
{ label: 'LIMIT', kind: 'kw', detail: 'SQL' },
|
|
1501
|
+
{ label: 'OFFSET', kind: 'kw', detail: 'SQL' },
|
|
1502
|
+
{ label: 'INSERT INTO', kind: 'kw', detail: 'SQL' },
|
|
1503
|
+
{ label: 'UPDATE', kind: 'kw', detail: 'SQL' },
|
|
1504
|
+
{ label: 'DELETE FROM', kind: 'kw', detail: 'SQL' },
|
|
1505
|
+
{ label: 'COUNT', kind: 'fn', detail: 'aggregate', description: 'COUNT(*) — number of rows.' },
|
|
1506
|
+
{ label: 'SUM', kind: 'fn', detail: 'aggregate' },
|
|
1507
|
+
{ label: 'AVG', kind: 'fn', detail: 'aggregate' },
|
|
1508
|
+
{ label: 'MAX', kind: 'fn', detail: 'aggregate' },
|
|
1509
|
+
{ label: 'MIN', kind: 'fn', detail: 'aggregate' },
|
|
1510
|
+
];
|
|
1511
|
+
|
|
1512
|
+
createEditor(container, {
|
|
1513
|
+
language: 'text',
|
|
1514
|
+
replaceBuiltins: true,
|
|
1515
|
+
completions: sqlCompletions,
|
|
1516
|
+
lineCommentToken: '--',
|
|
1517
|
+
theme: 'vscode-dark',
|
|
1518
|
+
});
|
|
1519
|
+
```
|
|
1520
|
+
|
|
1521
|
+
---
|
|
1522
|
+
|
|
1523
|
+
### Read-Only Code Viewer
|
|
1524
|
+
|
|
1525
|
+
A zero-interaction code display with syntax highlighting, no editing, no cursors:
|
|
1526
|
+
|
|
1527
|
+
```ts
|
|
1528
|
+
createEditor(container, {
|
|
1529
|
+
value: sourceCode,
|
|
1530
|
+
language: 'typescript',
|
|
1531
|
+
theme: 'github-light',
|
|
1532
|
+
readOnly: true,
|
|
1533
|
+
autocomplete: false,
|
|
1534
|
+
emmet: false,
|
|
1535
|
+
snippetExpansion: false,
|
|
1536
|
+
find: false,
|
|
1537
|
+
findReplace: false,
|
|
1538
|
+
multiCursor: false,
|
|
1539
|
+
codeFolding: false,
|
|
1540
|
+
showStatusBar: false,
|
|
1541
|
+
showMinimap: false,
|
|
1542
|
+
highlightActiveLine: false,
|
|
1543
|
+
wordHighlight: false,
|
|
1544
|
+
bracketMatching: false,
|
|
1545
|
+
cursorBlinkRate: 999999,
|
|
1546
|
+
});
|
|
1547
|
+
```
|
|
1548
|
+
|
|
1549
|
+
---
|
|
1550
|
+
|
|
1551
|
+
### Custom Theme from Scratch (Recipe)
|
|
1552
|
+
|
|
1553
|
+
Minimal theme extending Dracula with a brand accent colour:
|
|
1554
|
+
|
|
1555
|
+
```ts
|
|
1556
|
+
import { THEME_DRACULA } from 'syncline-editor';
|
|
1557
|
+
|
|
1558
|
+
const brandTheme = {
|
|
1559
|
+
id: 'brand-dark',
|
|
1560
|
+
name: 'Brand Dark',
|
|
1561
|
+
description: 'Company brand colour scheme',
|
|
1562
|
+
light: false,
|
|
1563
|
+
tokens: {
|
|
1564
|
+
...THEME_DRACULA.tokens,
|
|
1565
|
+
accent: '#00C2FF', // brand blue accent
|
|
1566
|
+
accent2: '#005F7A',
|
|
1567
|
+
cur: '#00C2FF',
|
|
1568
|
+
curGlow: 'rgba(0,194,255,.45)',
|
|
1569
|
+
selBg: 'rgba(0,194,255,.20)',
|
|
1570
|
+
wordHlBorder: 'rgba(0,194,255,.35)',
|
|
1571
|
+
tokKw: '#00C2FF', // brand-coloured keywords
|
|
1572
|
+
},
|
|
1573
|
+
};
|
|
1574
|
+
|
|
1575
|
+
editor.registerTheme(brandTheme);
|
|
1576
|
+
editor.setTheme('brand-dark');
|
|
1577
|
+
```
|
|
1578
|
+
|
|
1579
|
+
---
|
|
1580
|
+
|
|
1581
|
+
### Framework-aware Autocomplete
|
|
1582
|
+
|
|
1583
|
+
Use `provideCompletions` to serve different items based on context — React hooks inside `.tsx`, lifecycle methods inside class components, etc.:
|
|
1584
|
+
|
|
1585
|
+
```ts
|
|
1586
|
+
import type { CompletionContext, CompletionItem } from 'syncline-editor';
|
|
1587
|
+
|
|
1588
|
+
const reactHooks: CompletionItem[] = [
|
|
1589
|
+
{ label: 'useState', kind: 'fn', detail: 'React hook', description: 'Adds local state to a functional component.' },
|
|
1590
|
+
{ label: 'useEffect', kind: 'fn', detail: 'React hook', description: 'Run side-effects after render.' },
|
|
1591
|
+
{ label: 'useCallback', kind: 'fn', detail: 'React hook' },
|
|
1592
|
+
{ label: 'useMemo', kind: 'fn', detail: 'React hook' },
|
|
1593
|
+
{ label: 'useRef', kind: 'fn', detail: 'React hook' },
|
|
1594
|
+
{ label: 'useContext', kind: 'fn', detail: 'React hook' },
|
|
1595
|
+
];
|
|
1596
|
+
|
|
1597
|
+
createEditor(container, {
|
|
1598
|
+
language: 'typescript',
|
|
1599
|
+
provideCompletions: (ctx: CompletionContext): CompletionItem[] | null => {
|
|
1600
|
+
// Serve React hooks when prefix starts with "use"
|
|
1601
|
+
if (ctx.prefix.startsWith('use')) {
|
|
1602
|
+
return reactHooks.filter(h => h.label.startsWith(ctx.prefix));
|
|
1603
|
+
}
|
|
1604
|
+
return null; // fall through to built-in completions
|
|
1605
|
+
},
|
|
1606
|
+
});
|
|
1607
|
+
```
|
|
1608
|
+
|
|
1609
|
+
---
|
|
1610
|
+
|
|
1611
|
+
### Auto-Save with Debounce
|
|
1612
|
+
|
|
1613
|
+
Debounce `onChange` so the backend isn't hammered on every keystroke:
|
|
1614
|
+
|
|
1615
|
+
```ts
|
|
1616
|
+
function debounce<T extends (...args: any[]) => void>(fn: T, ms: number): T {
|
|
1617
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
1618
|
+
return ((...args) => {
|
|
1619
|
+
clearTimeout(timer);
|
|
1620
|
+
timer = setTimeout(() => fn(...args), ms);
|
|
1621
|
+
}) as T;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
const save = debounce(async (value: string) => {
|
|
1625
|
+
await fetch('/api/save', {
|
|
1626
|
+
method: 'POST',
|
|
1627
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1628
|
+
body: JSON.stringify({ content: value }),
|
|
1629
|
+
});
|
|
1630
|
+
}, 1000);
|
|
1631
|
+
|
|
1632
|
+
createEditor(container, {
|
|
1633
|
+
onChange: save,
|
|
1634
|
+
});
|
|
1635
|
+
```
|
|
1636
|
+
|
|
1637
|
+
---
|
|
1638
|
+
|
|
1639
|
+
## TypeScript Types
|
|
1640
|
+
|
|
1641
|
+
All types are exported from the package root:
|
|
1642
|
+
|
|
1643
|
+
```ts
|
|
1644
|
+
import type {
|
|
1645
|
+
// Core
|
|
1646
|
+
EditorConfig,
|
|
1647
|
+
EditorAPI,
|
|
1648
|
+
Language,
|
|
1649
|
+
|
|
1650
|
+
// Positions
|
|
1651
|
+
CursorPosition,
|
|
1652
|
+
Selection,
|
|
1653
|
+
FindMatch,
|
|
1654
|
+
ExtraCursor,
|
|
1655
|
+
|
|
1656
|
+
// Autocomplete
|
|
1657
|
+
CompletionItem,
|
|
1658
|
+
CompletionKind,
|
|
1659
|
+
CompletionContext,
|
|
1660
|
+
|
|
1661
|
+
// Token colours
|
|
1662
|
+
TokenColors,
|
|
1663
|
+
|
|
1664
|
+
// Themes
|
|
1665
|
+
ThemeDefinition,
|
|
1666
|
+
ThemeTokens,
|
|
1667
|
+
} from 'syncline-editor';
|
|
1668
|
+
```
|
|
1669
|
+
|
|
1670
|
+
### `Language`
|
|
1671
|
+
|
|
1672
|
+
```ts
|
|
1673
|
+
type Language =
|
|
1674
|
+
| 'typescript'
|
|
1675
|
+
| 'javascript'
|
|
1676
|
+
| 'css'
|
|
1677
|
+
| 'json'
|
|
1678
|
+
| 'markdown'
|
|
1679
|
+
| 'text';
|
|
1680
|
+
```
|
|
1681
|
+
|
|
1682
|
+
### `CursorPosition`
|
|
1683
|
+
|
|
1684
|
+
```ts
|
|
1685
|
+
interface CursorPosition {
|
|
1686
|
+
row: number; // zero-based line index
|
|
1687
|
+
col: number; // zero-based character offset
|
|
1688
|
+
}
|
|
1689
|
+
```
|
|
1690
|
+
|
|
1691
|
+
### `Selection`
|
|
1692
|
+
|
|
1693
|
+
```ts
|
|
1694
|
+
interface Selection {
|
|
1695
|
+
ar: number; // anchor row — where selection started
|
|
1696
|
+
ac: number; // anchor col
|
|
1697
|
+
fr: number; // focus row — where the caret currently sits
|
|
1698
|
+
fc: number; // focus col
|
|
1699
|
+
}
|
|
1700
|
+
```
|
|
1701
|
+
|
|
1702
|
+
Either end can come before the other — backward selections are valid.
|
|
1703
|
+
|
|
1704
|
+
### `CompletionItem`
|
|
1705
|
+
|
|
1706
|
+
```ts
|
|
1707
|
+
interface CompletionItem {
|
|
1708
|
+
label: string; // text inserted on accept; used for prefix matching
|
|
1709
|
+
kind: CompletionKind; // 'kw' | 'fn' | 'typ' | 'cls' | 'var' | 'snip' | 'emmet'
|
|
1710
|
+
detail?: string; // short hint shown on the right of the popup row
|
|
1711
|
+
description?: string; // full docs shown in the side panel when this item is selected
|
|
1712
|
+
body?: string; // snippet template — Tab/Enter expands when set
|
|
1713
|
+
language?: string | string[]; // restrict to specific language(s); omit = all languages
|
|
1714
|
+
}
|
|
1715
|
+
```
|
|
1716
|
+
|
|
1717
|
+
### `CompletionKind`
|
|
1718
|
+
|
|
1719
|
+
```ts
|
|
1720
|
+
type CompletionKind = 'kw' | 'fn' | 'typ' | 'cls' | 'var' | 'snip' | 'emmet';
|
|
1721
|
+
```
|
|
1722
|
+
|
|
1723
|
+
### `CompletionContext`
|
|
1724
|
+
|
|
1725
|
+
```ts
|
|
1726
|
+
interface CompletionContext {
|
|
1727
|
+
prefix: string; // characters before the cursor (the match prefix)
|
|
1728
|
+
language: string; // active language ID
|
|
1729
|
+
line: number; // cursor row (zero-based)
|
|
1730
|
+
col: number; // cursor column (zero-based)
|
|
1731
|
+
doc: string[]; // full document split into lines
|
|
1732
|
+
}
|
|
1733
|
+
```
|
|
1734
|
+
|
|
1735
|
+
### `TokenColors`
|
|
1736
|
+
|
|
1737
|
+
```ts
|
|
1738
|
+
interface TokenColors {
|
|
1739
|
+
keyword?: string; // --tok-kw — if, const, class, interface, @media
|
|
1740
|
+
string?: string; // --tok-str — string and template literals
|
|
1741
|
+
comment?: string; // --tok-cmt — line and block comments
|
|
1742
|
+
function?: string; // --tok-fn — function names, CSS functions
|
|
1743
|
+
number?: string; // --tok-num — numeric literals
|
|
1744
|
+
class?: string; // --tok-cls — class names, constructor calls
|
|
1745
|
+
operator?: string; // --tok-op — +, =>, ===, &&, ?.
|
|
1746
|
+
type?: string; // --tok-typ — type names, built-in types
|
|
1747
|
+
decorator?: string; // --tok-dec — @Component, @Injectable
|
|
1748
|
+
}
|
|
1749
|
+
```
|
|
1750
|
+
|
|
1751
|
+
Pass an empty string `''` for any field to restore that token to the current theme's default.
|
|
1752
|
+
|
|
1753
|
+
### `ThemeDefinition`
|
|
1754
|
+
|
|
1755
|
+
```ts
|
|
1756
|
+
interface ThemeDefinition {
|
|
1757
|
+
id: string; // unique identifier — used with setTheme() and getThemes()
|
|
1758
|
+
name: string; // human-readable display name
|
|
1759
|
+
description: string; // short description
|
|
1760
|
+
light: boolean; // true for light themes (affects status-bar icon tinting)
|
|
1761
|
+
tokens: ThemeTokens; // complete set of CSS variable values
|
|
1762
|
+
}
|
|
1763
|
+
```
|
|
1764
|
+
|
|
1765
|
+
---
|
|
1766
|
+
|
|
1767
|
+
## Project Structure
|
|
1768
|
+
|
|
1769
|
+
```
|
|
1770
|
+
syncline-editor/
|
|
1771
|
+
├── src/
|
|
1772
|
+
│ ├── core/
|
|
1773
|
+
│ │ ├── constants.ts # Per-language keyword/type sets + layout constants
|
|
1774
|
+
│ │ ├── document.ts # Document model, undo/redo, selection helpers
|
|
1775
|
+
│ │ ├── tokeniser.ts # Language-aware syntax tokeniser (zero deps)
|
|
1776
|
+
│ │ └── wrap-map.ts # Soft-wrap virtual line map
|
|
1777
|
+
│ ├── features/
|
|
1778
|
+
│ │ ├── autocomplete.ts # Completion engine — ranking, filtering, snippet items
|
|
1779
|
+
│ │ ├── bracket-matcher.ts # Bracket-pair finder
|
|
1780
|
+
│ │ ├── code-folding.ts # Block folding logic
|
|
1781
|
+
│ │ ├── emmet.ts # Emmet abbreviation expander
|
|
1782
|
+
│ │ ├── find-replace.ts # Find / replace engine
|
|
1783
|
+
│ │ ├── multi-cursor.ts # Multi-cursor state manager
|
|
1784
|
+
│ │ ├── snippets.ts # Built-in snippet library (TS/JS, CSS, HTML)
|
|
1785
|
+
│ │ └── word-highlight.ts # Same-word occurrence finder
|
|
1786
|
+
│ ├── renderer/
|
|
1787
|
+
│ │ ├── minimap-renderer.ts # Canvas minimap + scroll calculations
|
|
1788
|
+
│ │ └── row-renderer.ts # Virtual row → HTML string
|
|
1789
|
+
│ ├── themes/
|
|
1790
|
+
│ │ ├── built-in-themes.ts # 6 built-in ThemeDefinition objects
|
|
1791
|
+
│ │ └── theme-manager.ts # Theme registry + CSS variable injection
|
|
1792
|
+
│ ├── types/
|
|
1793
|
+
│ │ └── index.ts # All public TypeScript interfaces and types
|
|
1794
|
+
│ ├── ui/
|
|
1795
|
+
│ │ └── styles.ts # All CSS injected into Shadow DOM
|
|
1796
|
+
│ ├── utils/
|
|
1797
|
+
│ │ ├── dom.ts # DOM helpers
|
|
1798
|
+
│ │ ├── string.ts # String utilities (word boundaries, escaping, …)
|
|
1799
|
+
│ │ └── validation.ts # Config validation helpers
|
|
1800
|
+
│ ├── SynclineEditor.ts # Main editor class — implements EditorAPI
|
|
1801
|
+
│ └── index.ts # Public API barrel export
|
|
1802
|
+
├── playground/
|
|
1803
|
+
│ └── index.html # Interactive playground (every option exposed)
|
|
1804
|
+
├── tests/ # Vitest test suite
|
|
1805
|
+
├── package.json
|
|
1806
|
+
├── tsconfig.json
|
|
1807
|
+
├── vite.config.ts # Playground dev server + build
|
|
1808
|
+
├── vite.lib.config.ts # Library build (ES module + UMD + .d.ts)
|
|
1809
|
+
└── README.md
|
|
1810
|
+
```
|
|
1811
|
+
|
|
1812
|
+
---
|
|
1813
|
+
|
|
1814
|
+
## Development
|
|
1815
|
+
|
|
1816
|
+
```bash
|
|
1817
|
+
# Install dependencies
|
|
1818
|
+
npm install
|
|
1819
|
+
|
|
1820
|
+
# Start the interactive playground (Vite dev server, hot reload)
|
|
1821
|
+
npm run dev
|
|
1822
|
+
|
|
1823
|
+
# Type-check only (no emit)
|
|
1824
|
+
npm run typecheck
|
|
1825
|
+
|
|
1826
|
+
# Build the playground into dist/
|
|
1827
|
+
npm run build
|
|
1828
|
+
|
|
1829
|
+
# Build the distributable library (ES + UMD + .d.ts)
|
|
1830
|
+
npm run build:lib
|
|
1831
|
+
|
|
1832
|
+
# Preview the built playground
|
|
1833
|
+
npm run preview
|
|
1834
|
+
|
|
1835
|
+
# Run the test suite
|
|
1836
|
+
npm run test
|
|
1837
|
+
```
|
|
1838
|
+
|
|
1839
|
+
### Playground
|
|
1840
|
+
|
|
1841
|
+
The playground at `playground/index.html` is a fully self-contained interactive demo with every option wired to live controls:
|
|
1842
|
+
|
|
1843
|
+
- **Theme switcher** — all 6 built-in themes as clickable chips
|
|
1844
|
+
- **Language selector** — TypeScript · JavaScript · CSS · JSON · Markdown · plain text
|
|
1845
|
+
- **Token Colors** — 9 colour pickers with live preview, enable/disable toggle, and reset
|
|
1846
|
+
- **Features** — checkbox for every boolean option
|
|
1847
|
+
- **Typography** — font family, size, line height, cursor style, whitespace rendering
|
|
1848
|
+
- **Layout** — gutter width, minimap width, wrap column
|
|
1849
|
+
- **Autocomplete** — `provideCompletions` code editor, extra keywords/types, unified `completions` JSON editor with live validation, `replaceBuiltins` toggle
|
|
1850
|
+
- **Behavior** — auto-close pairs, line comment token, word separators, undo batch window
|
|
1851
|
+
- **Actions** — buttons for every `executeCommand` and `getValue` / `setValue`
|
|
1852
|
+
- **Event log** — live feed of all `onChange` / `onCursorChange` / `onSelectionChange` / `onFocus` / `onBlur` events
|
|
1853
|
+
|
|
1854
|
+
---
|
|
1855
|
+
|
|
1856
|
+
## License
|
|
1857
|
+
|
|
1858
|
+
MIT
|