@citisen/litearea 0.1.0 → 0.2.1
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 +39 -18
- package/README.zh.md +25 -11
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +1 -1
- package/dist/react.js.map +1 -1
- package/dist/types/core/types.d.ts +5 -5
- package/dist/types/dom/editor.d.ts +8 -3
- package/dist/types/dom/editor.d.ts.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/docs/architecture.md +10 -6
- package/docs/completion.md +13 -5
- package/docs/grammar.md +16 -15
- package/package.json +1 -6
- package/scripts/browser-check.mjs +133 -54
- package/scripts/verify-package.mjs +51 -27
- package/src/core/types.ts +5 -5
- package/src/dom/editor.ts +14 -4
- package/src/index.ts +6 -5
- package/dist/grammars.cjs +0 -1228
- package/dist/grammars.cjs.map +0 -1
- package/dist/grammars.js +0 -1213
- package/dist/grammars.js.map +0 -1
- package/dist/types/grammars/dshFont.d.ts +0 -127
- package/dist/types/grammars/dshFont.d.ts.map +0 -1
- package/dist/types/grammars/dshSentry.d.ts +0 -84
- package/dist/types/grammars/dshSentry.d.ts.map +0 -1
- package/dist/types/grammars/index.d.ts +0 -3
- package/dist/types/grammars/index.d.ts.map +0 -1
- package/src/grammars/dshFont.ts +0 -1004
- package/src/grammars/dshSentry.ts +0 -742
- package/src/grammars/index.ts +0 -57
|
@@ -26,15 +26,17 @@ const packageJson = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
|
|
|
26
26
|
* Every entry point `package.json` promises, and where its declarations landed.
|
|
27
27
|
*
|
|
28
28
|
* The declarations come from `tsc` rather than from the JavaScript bundler, so they
|
|
29
|
-
* mirror the source tree: the root entry is one file, while `react`
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
29
|
+
* mirror the source tree: the root entry is one file, while `react` is a directory
|
|
30
|
+
* with an `index.d.ts` in it. That extra level is the price of not depending on a
|
|
31
|
+
* declaration bundler that predates TypeScript 7, and it is encoded here so a change
|
|
32
|
+
* to the layout cannot pass unnoticed.
|
|
33
|
+
*
|
|
34
|
+
* There is deliberately no `grammars` entry: the library ships no syntax, so it
|
|
35
|
+
* publishes no grammar for a host to import and none for this verifier to borrow.
|
|
33
36
|
*/
|
|
34
37
|
const ENTRIES = [
|
|
35
38
|
{ name: 'index', declarations: 'types/index.d.ts' },
|
|
36
39
|
{ name: 'react', declarations: 'types/react/index.d.ts' },
|
|
37
|
-
{ name: 'grammars', declarations: 'types/grammars/index.d.ts' },
|
|
38
40
|
{ name: 'styles', declarations: 'types/styles.d.ts' },
|
|
39
41
|
]
|
|
40
42
|
|
|
@@ -74,17 +76,6 @@ const CORE_EXPORTS = [
|
|
|
74
76
|
'canEditThroughPipeline',
|
|
75
77
|
]
|
|
76
78
|
|
|
77
|
-
/** The names the grammars entry must export. */
|
|
78
|
-
const GRAMMAR_EXPORTS = [
|
|
79
|
-
'dshFontQueryGrammar',
|
|
80
|
-
'dshSentryStyleGrammar',
|
|
81
|
-
'fontWeightWord',
|
|
82
|
-
'fontFaceWeights',
|
|
83
|
-
'quoteFontFamily',
|
|
84
|
-
'FONT_WEIGHT_WORDS',
|
|
85
|
-
'FONT_GENERIC_FAMILIES',
|
|
86
|
-
]
|
|
87
|
-
|
|
88
79
|
/** The names the React entry must export. */
|
|
89
80
|
const REACT_EXPORTS = ['LiteAreaEditor']
|
|
90
81
|
|
|
@@ -171,30 +162,63 @@ const core = await import(pathToFileURL(join(dist, 'index.js')).href)
|
|
|
171
162
|
const missing = CORE_EXPORTS.filter((name) => core[name] === undefined)
|
|
172
163
|
if (missing.length > 0) fail(`index.js does not export: ${missing.join(', ')}`)
|
|
173
164
|
|
|
174
|
-
const grammars = await import(pathToFileURL(join(dist, 'grammars.js')).href)
|
|
175
|
-
const missingGrammars = GRAMMAR_EXPORTS.filter((name) => grammars[name] === undefined)
|
|
176
|
-
if (missingGrammars.length > 0) fail(`grammars.js does not export: ${missingGrammars.join(', ')}`)
|
|
177
|
-
|
|
178
165
|
const react = await import(pathToFileURL(join(dist, 'react.js')).href)
|
|
179
166
|
const missingReact = REACT_EXPORTS.filter((name) => react[name] === undefined)
|
|
180
167
|
if (missingReact.length > 0) fail(`react.js does not export: ${missingReact.join(', ')}`)
|
|
181
168
|
|
|
182
169
|
// ── the engine works from the built artifact, not just from source ─────────
|
|
170
|
+
//
|
|
171
|
+
// The grammar below is a FIXTURE and lives only in this file. The package ships
|
|
172
|
+
// no syntax, so there is nothing to import: a verifier that borrowed a language
|
|
173
|
+
// from somewhere else would be checking that language rather than the artifact
|
|
174
|
+
// it is here to check. Two rules and one vocabulary are enough to prove the
|
|
175
|
+
// built engine still tokenizes, diagnoses, completes, and segments.
|
|
176
|
+
|
|
177
|
+
/** A closed set of shapes, small enough to read at a glance. */
|
|
178
|
+
const SHAPES = core.defineVocabulary({
|
|
179
|
+
id: 'shape',
|
|
180
|
+
words: ['circle', 'square'],
|
|
181
|
+
unknownMessage: '"{word}" is not a shape — expected {allowed}.',
|
|
182
|
+
docs: { circle: { detail: 'a disc', body: 'Verifier fixture documentation.' } },
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
/** The whole language: one keyword, one vocabulary, one completion source. */
|
|
186
|
+
const fixture = core.defineGrammar({
|
|
187
|
+
id: 'verify-fixture',
|
|
188
|
+
rules: [
|
|
189
|
+
{ kind: 'match', scope: 'keyword', pattern: /draw/ },
|
|
190
|
+
{ kind: 'words', words: SHAPES, unknown: {} },
|
|
191
|
+
],
|
|
192
|
+
compose: [
|
|
193
|
+
{
|
|
194
|
+
id: 'shape',
|
|
195
|
+
range: (context) => context.word,
|
|
196
|
+
items: () =>
|
|
197
|
+
['circle', 'square'].map((shape) => ({
|
|
198
|
+
label: shape,
|
|
199
|
+
append: ' ',
|
|
200
|
+
kind: 'shape',
|
|
201
|
+
detail: SHAPES.entryFor(shape)?.detail,
|
|
202
|
+
})),
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
})
|
|
183
206
|
|
|
184
|
-
const
|
|
185
|
-
const inspection = core.inspect('running circle blue turn 3\nbogus circle', grammar)
|
|
207
|
+
const inspection = core.inspect('draw circle\nbogus square', fixture)
|
|
186
208
|
assert.ok(inspection.tokens.length > 0, 'the built engine produced no tokens')
|
|
187
209
|
assert.ok(
|
|
188
|
-
inspection.diagnostics.some((diagnostic) => diagnostic.code === 'vocabulary:
|
|
189
|
-
'the built engine did not report the unknown
|
|
210
|
+
inspection.diagnostics.some((diagnostic) => diagnostic.code === 'vocabulary:shape'),
|
|
211
|
+
'the built engine did not report the unknown shape',
|
|
190
212
|
)
|
|
191
|
-
const completion = core.complete(
|
|
192
|
-
text: '
|
|
193
|
-
caret: '
|
|
213
|
+
const completion = core.complete(core.inspect('draw ', fixture), fixture, {
|
|
214
|
+
text: 'draw ',
|
|
215
|
+
caret: 'draw '.length,
|
|
194
216
|
trigger: 'explicit',
|
|
195
217
|
})
|
|
196
218
|
assert.ok(completion !== undefined, 'the built engine offered no completion')
|
|
197
219
|
assert.ok(completion.rows.length > 0, 'the built engine offered an empty completion')
|
|
220
|
+
assert.equal(completion.sourceId, 'shape', 'the built engine opened the wrong completion source')
|
|
221
|
+
assert.equal(completion.rows[0]?.item.append, ' ', 'the built engine dropped a row field')
|
|
198
222
|
|
|
199
223
|
const segments = core.buildSegments(inspection.text, inspection)
|
|
200
224
|
assert.equal(
|
package/src/core/types.ts
CHANGED
|
@@ -67,9 +67,9 @@ export interface Token extends Range {
|
|
|
67
67
|
* tokens and they are deliberately not tokens here.
|
|
68
68
|
*
|
|
69
69
|
* The distinction earns its keep. A token is what the *characters* are; a
|
|
70
|
-
* decoration is what they *mean*, and the two change on different schedules.
|
|
71
|
-
*
|
|
72
|
-
*
|
|
70
|
+
* decoration is what they *mean*, and the two change on different schedules. A
|
|
71
|
+
* grammar can paint `Geist Mono` as a family from the characters alone, but which
|
|
72
|
+
* family is *in effect* depends on a catalogue the HOST supplied — the same
|
|
73
73
|
* characters mean something else on another machine. Painting that as a token
|
|
74
74
|
* would mean re-lexing the document whenever the catalogue changed; painting it
|
|
75
75
|
* as a decoration means recomputing one range list, which is what it is.
|
|
@@ -493,8 +493,8 @@ export interface CompletionContext<State = unknown> {
|
|
|
493
493
|
* `runn|` is no longer "at the start of the line" in the strict sense, but it is
|
|
494
494
|
* unmistakably completing the first word. A source that asked `firstOnLine` would
|
|
495
495
|
* switch itself off after the very first keystroke — which is exactly the bug this
|
|
496
|
-
* field was added to fix, and exactly the kind of thing a
|
|
497
|
-
* for finding.
|
|
496
|
+
* field was added to fix, and exactly the kind of thing a grammar written against
|
|
497
|
+
* a real document is for finding.
|
|
498
498
|
*/
|
|
499
499
|
firstWord: boolean
|
|
500
500
|
/** The first non-whitespace token on the caret's line, when there is one. */
|
package/src/dom/editor.ts
CHANGED
|
@@ -94,9 +94,14 @@ export interface LiteAreaCompletion {
|
|
|
94
94
|
/**
|
|
95
95
|
* Characters that open the list in addition to word characters.
|
|
96
96
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
97
|
+
* Empty by default, because a separator is the wrong moment to interrupt: it is
|
|
98
|
+
* where the next token starts, which is the argument FOR opening there, and it
|
|
99
|
+
* loses to the habit every reader already has — the editor this is modelled on
|
|
100
|
+
* offers nothing on a space and waits for a letter or for `Ctrl+Space`. Nothing
|
|
101
|
+
* is stranded by that. The list stays open once it is open, so a value typed
|
|
102
|
+
* after a name is still filtered in place, and `Ctrl+Space` opens it on demand.
|
|
103
|
+
* Set it to `' '` or `','` for a language where a separator really is where the
|
|
104
|
+
* next token becomes guessable.
|
|
100
105
|
*/
|
|
101
106
|
triggerCharacters?: string
|
|
102
107
|
/** The most rows to offer. Default 100. */
|
|
@@ -289,11 +294,16 @@ export class LiteArea<State = unknown> {
|
|
|
289
294
|
{ autoGrow: true, minRows: 1 },
|
|
290
295
|
options.sizing as Partial<ResolvedSizing> | undefined,
|
|
291
296
|
)
|
|
297
|
+
// A word character opens the list; a separator does not, unless the grammar's
|
|
298
|
+
// host asks for one by name. The list does not need a separator to be helpful
|
|
299
|
+
// — it is already open while the next token is typed, and it re-filters in
|
|
300
|
+
// place — and opening on a space spends the one keystroke a reader is least
|
|
301
|
+
// willing to have interrupted.
|
|
292
302
|
this.completion =
|
|
293
303
|
options.completion === false
|
|
294
304
|
? undefined
|
|
295
305
|
: withDefaults<ResolvedCompletion>(
|
|
296
|
-
{ auto: true, triggerCharacters: '
|
|
306
|
+
{ auto: true, triggerCharacters: '', limit: 100, showDocumentation: true },
|
|
297
307
|
options.completion,
|
|
298
308
|
)
|
|
299
309
|
this.hover =
|
package/src/index.ts
CHANGED
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
//
|
|
7
7
|
// The package has three entry points and this is the largest:
|
|
8
8
|
//
|
|
9
|
-
// @citisen/litearea
|
|
10
|
-
// @citisen/litearea/react
|
|
11
|
-
// @citisen/litearea/
|
|
9
|
+
// @citisen/litearea the engine and the DOM layer (this file)
|
|
10
|
+
// @citisen/litearea/react a React binding over the same editor
|
|
11
|
+
// @citisen/litearea/styles.css the stylesheet, for hosts that link CSS
|
|
12
12
|
//
|
|
13
|
-
// Nothing here knows any syntax
|
|
14
|
-
//
|
|
13
|
+
// Nothing here knows any syntax, and the package ships none: a caller supplies
|
|
14
|
+
// the rules. `src/core/` is pure and needs no DOM; `src/dom/` needs a document
|
|
15
|
+
// and no framework.
|
|
15
16
|
|
|
16
17
|
export type {
|
|
17
18
|
CheckRule,
|