@fusion.dev/helix-editor 0.219.0 → 0.221.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.
@@ -1,370 +0,0 @@
1
- import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands';
2
- import { bracketMatching, codeFolding, foldGutter, foldKeymap, indentOnInput, indentUnit, StreamLanguage, syntaxHighlighting, } from '@codemirror/language';
3
- import { setDiagnostics } from '@codemirror/lint';
4
- import { closeSearchPanel, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, SearchQuery, search, searchPanelOpen, setSearchQuery, } from '@codemirror/search';
5
- import { Annotation, Compartment, EditorState, RangeSet } from '@codemirror/state';
6
- import { drawSelection, EditorView, GutterMarker, gutterLineClass, highlightActiveLine, highlightActiveLineGutter, keymap, lineNumbers, placeholder, } from '@codemirror/view';
7
- import { syntaxHighlighter, syntaxRules } from '@fusion.dev/helix-sandbox/highlight';
8
- const LANGUAGES = {
9
- css: async () => (await import('@codemirror/lang-css')).css(),
10
- html: async () => (await import('@codemirror/lang-html')).html(),
11
- js: async () => (await import('@codemirror/lang-javascript')).javascript(),
12
- json: async () => (await import('@codemirror/lang-json')).json(),
13
- jsx: async () => (await import('@codemirror/lang-javascript')).javascript({ jsx: true }),
14
- md: async () => (await import('@codemirror/lang-markdown')).markdown(),
15
- py: async () => (await import('@codemirror/lang-python')).python(),
16
- rs: async () => (await import('@codemirror/lang-rust')).rust(),
17
- sh: async () => StreamLanguage.define((await import('@codemirror/legacy-modes/mode/shell')).shell),
18
- ts: async () => (await import('@codemirror/lang-javascript')).javascript({ typescript: true }),
19
- tsx: async () => (await import('@codemirror/lang-javascript')).javascript({ jsx: true, typescript: true }),
20
- };
21
- const ALIASES = {
22
- bash: 'sh',
23
- cjs: 'js',
24
- htm: 'html',
25
- javascript: 'js',
26
- markdown: 'md',
27
- mjs: 'js',
28
- python: 'py',
29
- rust: 'rs',
30
- shell: 'sh',
31
- typescript: 'ts',
32
- zsh: 'sh',
33
- };
34
- export const languageName = (name) => {
35
- const key = name.trim().toLowerCase();
36
- const known = ALIASES[key] ?? key;
37
- return known in LANGUAGES ? known : undefined;
38
- };
39
- export const languageNames = () => [...Object.keys(LANGUAGES), ...Object.keys(ALIASES)].sort();
40
- const given = Annotation.define();
41
- const helixTheme = EditorView.theme({
42
- '.cm-activeLine, .cm-activeLineGutter': {
43
- backgroundColor: 'color-mix(in oklab, var(--helix-color-foreground) 5%, transparent)',
44
- },
45
- '.cm-content': {
46
- caretColor: 'var(--helix-color-primary)',
47
- paddingBlock: 'var(--helix-space-xs)',
48
- },
49
- '.cm-cursor': { borderLeftColor: 'var(--helix-color-primary)' },
50
- '.cm-foldGutter .cm-gutterElement': { paddingInline: 'var(--helix-space-2xs)' },
51
- '.cm-foldPlaceholder': {
52
- backgroundColor: 'var(--helix-color-surface-sunken)',
53
- border: 'none',
54
- color: 'var(--helix-color-foreground-muted)',
55
- },
56
- '.cm-gutterElement.cm-helix-problem-danger': {
57
- color: 'var(--helix-color-danger)',
58
- fontWeight: 'var(--helix-weight-semibold)',
59
- },
60
- '.cm-gutterElement.cm-helix-problem-info': { color: 'var(--helix-color-info)' },
61
- '.cm-gutterElement.cm-helix-problem-warning': {
62
- color: 'var(--helix-color-warning)',
63
- fontWeight: 'var(--helix-weight-semibold)',
64
- },
65
- '.cm-gutters': {
66
- backgroundColor: 'transparent',
67
- border: 'none',
68
- color: 'var(--helix-color-foreground-muted)',
69
- },
70
- '.cm-lineNumbers .cm-gutterElement': {
71
- minInlineSize: '3ch',
72
- paddingInline: 'var(--helix-space-sm) var(--helix-space-2xs)',
73
- },
74
- '.cm-lintRange-error': {
75
- backgroundImage: 'none',
76
- textDecoration: 'underline wavy var(--helix-color-danger)',
77
- },
78
- '.cm-lintRange-info': {
79
- backgroundImage: 'none',
80
- textDecoration: 'underline dotted var(--helix-color-foreground-muted)',
81
- },
82
- '.cm-lintRange-warning': {
83
- backgroundImage: 'none',
84
- textDecoration: 'underline wavy var(--helix-color-warning)',
85
- },
86
- '.cm-panels:has(> .cm-helix-find:only-child)': { display: 'none' },
87
- '.cm-placeholder': { color: 'var(--helix-color-foreground-muted)' },
88
- '.cm-scroller': {
89
- fontFamily: 'inherit',
90
- fontVariantLigatures: 'none',
91
- lineHeight: 'var(--helix-leading-normal)',
92
- scrollbarColor: 'var(--helix-scrollbar-thumb-color) transparent',
93
- scrollbarWidth: 'thin',
94
- },
95
- '.cm-searchMatch': {
96
- backgroundColor: 'color-mix(in oklab, var(--helix-color-warning) 25%, transparent)',
97
- },
98
- '.cm-searchMatch-selected': {
99
- backgroundColor: 'color-mix(in oklab, var(--helix-color-warning) 50%, transparent)',
100
- outline: 'var(--helix-border-thin) solid var(--helix-color-warning)',
101
- },
102
- '.cm-selectionBackground, &.cm-focused .cm-selectionBackground': {
103
- backgroundColor: 'var(--helix-color-primary-subtle)',
104
- },
105
- '.cm-tooltip': {
106
- backgroundColor: 'var(--helix-color-surface-overlay)',
107
- border: 'var(--helix-border-thin) solid var(--helix-color-border)',
108
- borderRadius: 'var(--helix-radii-sm)',
109
- },
110
- '&': { blockSize: '100%', color: 'var(--helix-color-foreground)', fontSize: 'inherit' },
111
- '&:not(.cm-focused) .cm-activeLine, &:not(.cm-focused) .cm-activeLineGutter': {
112
- backgroundColor: 'transparent',
113
- },
114
- '&.cm-focused': { outline: 'none' },
115
- });
116
- const SEVERITY = { danger: 'error', info: 'info', warning: 'warning' };
117
- const lintOf = (state, length) => state.diagnostics.map((one) => ({
118
- from: Math.min(Math.max(0, one.from), length),
119
- message: one.message,
120
- severity: SEVERITY[one.tone ?? 'danger'],
121
- to: Math.min(Math.max(one.from, one.to), length),
122
- }));
123
- class ProblemLine extends GutterMarker {
124
- constructor(tone) {
125
- super();
126
- this.tone = tone;
127
- this.elementClass = `cm-helix-problem-${tone}`;
128
- }
129
- eq(other) {
130
- return other.tone === this.tone;
131
- }
132
- }
133
- const RANK = { danger: 2, info: 0, warning: 1 };
134
- const problemLines = (state, diagnostics) => {
135
- const worst = new Map();
136
- for (const one of diagnostics) {
137
- const line = state.doc.lineAt(Math.min(Math.max(0, one.from), state.doc.length)).from;
138
- const tone = one.tone ?? 'danger';
139
- const held = worst.get(line);
140
- if (!held || RANK[tone] > RANK[held]) {
141
- worst.set(line, tone);
142
- }
143
- }
144
- const marks = [...worst]
145
- .sort(([a], [b]) => a - b)
146
- .map(([from, tone]) => new ProblemLine(tone).range(from));
147
- return gutterLineClass.of(RangeSet.of(marks));
148
- };
149
- const queryOf = (query) => new SearchQuery({
150
- caseSensitive: query.caseSensitive,
151
- literal: !query.regex,
152
- regexp: query.regex,
153
- replace: query.replace,
154
- search: query.search,
155
- wholeWord: query.wholeWord,
156
- });
157
- const COUNTED = 10_000;
158
- const matchesOf = (state, query) => {
159
- const found = [];
160
- const cursor = query.getCursor(state);
161
- for (let next = cursor.next(); !next.done && found.length < COUNTED; next = cursor.next()) {
162
- if (next.value.to > next.value.from) {
163
- found.push([next.value.from, next.value.to]);
164
- }
165
- }
166
- return found;
167
- };
168
- const standing = (state, query) => {
169
- const matches = matchesOf(state, query);
170
- const { from, to } = state.selection.main;
171
- return {
172
- count: matches.length,
173
- index: matches.findIndex(([start, end]) => start === from && end === to),
174
- };
175
- };
176
- const adoptSyntax = (root) => {
177
- if (typeof CSSStyleSheet !== 'function' || !('adoptedStyleSheets' in root)) {
178
- return;
179
- }
180
- const marked = root;
181
- if (marked.helixSyntax) {
182
- return;
183
- }
184
- const sheet = new CSSStyleSheet();
185
- sheet.replaceSync(syntaxRules);
186
- root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
187
- marked.helixSyntax = true;
188
- };
189
- export const codeMirrorEngine = (options = {}) => ({
190
- mount(parent, state, listeners) {
191
- const root = parent.getRootNode();
192
- adoptSyntax(root);
193
- const language = new Compartment();
194
- const numbers = new Compartment();
195
- const editable = new Compartment();
196
- const wrapping = new Compartment();
197
- const words = new Compartment();
198
- const hint = new Compartment();
199
- const problems = new Compartment();
200
- let current = state;
201
- let asked = '';
202
- const load = (name, view) => {
203
- asked = name;
204
- const known = languageName(name);
205
- if (!known) {
206
- view.dispatch({ effects: language.reconfigure([]) });
207
- return;
208
- }
209
- void LANGUAGES[known]?.().then((grammar) => {
210
- if (asked === name) {
211
- view.dispatch({ effects: language.reconfigure(grammar) });
212
- }
213
- });
214
- };
215
- const view = new EditorView({
216
- parent,
217
- root,
218
- state: EditorState.create({
219
- doc: state.value,
220
- extensions: [
221
- numbers.of(state.lineNumbers ? [lineNumbers(), highlightActiveLineGutter(), foldGutter()] : []),
222
- history(),
223
- drawSelection(),
224
- indentOnInput(),
225
- bracketMatching(),
226
- codeFolding(),
227
- highlightActiveLine(),
228
- highlightSelectionMatches(),
229
- search({
230
- createPanel: () => {
231
- const dom = document.createElement('div');
232
- dom.className = 'cm-helix-find';
233
- return { dom, top: true };
234
- },
235
- }),
236
- problems.of(problemLines(EditorState.create({ doc: state.value }), state.diagnostics)),
237
- indentUnit.of(' '),
238
- EditorState.tabSize.of(2),
239
- keymap.of([...defaultKeymap, ...historyKeymap, ...foldKeymap, indentWithTab]),
240
- syntaxHighlighting(syntaxHighlighter, { fallback: true }),
241
- helixTheme,
242
- language.of([]),
243
- editable.of(EditorState.readOnly.of(state.readOnly)),
244
- wrapping.of(state.wrap ? EditorView.lineWrapping : []),
245
- words.of(EditorView.contentAttributes.of({
246
- 'aria-label': state.label,
247
- 'aria-multiline': 'true',
248
- })),
249
- hint.of(state.placeholder ? placeholder(state.placeholder) : []),
250
- EditorState.transactionFilter.of((transaction) => {
251
- if (!transaction.docChanged || transaction.annotation(given)) {
252
- return transaction;
253
- }
254
- return listeners.change(transaction.newDoc.toString()) ? transaction : [];
255
- }),
256
- EditorView.updateListener.of((update) => {
257
- if (update.selectionSet || update.docChanged) {
258
- const { from, to } = update.state.selection.main;
259
- listeners.caret?.(from, to);
260
- }
261
- }),
262
- ...(options.extensions ?? []),
263
- ],
264
- }),
265
- });
266
- load(state.language, view);
267
- view.dispatch(setDiagnostics(view.state, lintOf(state, view.state.doc.length)));
268
- const ask = (query) => {
269
- const asked = queryOf(query);
270
- if (!searchPanelOpen(view.state)) {
271
- openSearchPanel(view);
272
- }
273
- view.dispatch({ effects: setSearchQuery.of(asked) });
274
- return asked;
275
- };
276
- return {
277
- destroy: () => view.destroy(),
278
- find: (query, direction) => {
279
- const asked = ask(query);
280
- if (!asked.valid) {
281
- return { count: 0, index: -1 };
282
- }
283
- const matches = matchesOf(view.state, asked);
284
- if (matches.length === 0) {
285
- return { count: 0, index: -1 };
286
- }
287
- const { from, to } = view.state.selection.main;
288
- let index = direction === 'previous'
289
- ? matches.findLastIndex(([start]) => start < from)
290
- : matches.findIndex(([start]) => (direction === 'first' ? start >= from : start >= to));
291
- if (index === -1) {
292
- index = direction === 'previous' ? matches.length - 1 : 0;
293
- }
294
- const [anchor, head] = matches[index] ?? [0, 0];
295
- view.dispatch({ scrollIntoView: true, selection: { anchor, head } });
296
- return { count: matches.length, index };
297
- },
298
- findEnd: () => {
299
- closeSearchPanel(view);
300
- },
301
- focus: () => view.focus(),
302
- replace: (query, all) => {
303
- const asked = ask(query);
304
- if (!asked.valid) {
305
- return { count: 0, index: -1 };
306
- }
307
- if (all) {
308
- replaceAll(view);
309
- }
310
- else {
311
- replaceNext(view);
312
- }
313
- return standing(view.state, asked);
314
- },
315
- select: (from, to) => {
316
- const length = view.state.doc.length;
317
- view.dispatch({
318
- scrollIntoView: true,
319
- selection: { anchor: Math.min(from, length), head: Math.min(to, length) },
320
- });
321
- view.focus();
322
- },
323
- update: (next) => {
324
- const effects = [];
325
- if (next.lineNumbers !== current.lineNumbers) {
326
- effects.push(numbers.reconfigure(next.lineNumbers ? [lineNumbers(), highlightActiveLineGutter(), foldGutter()] : []));
327
- }
328
- if (next.readOnly !== current.readOnly) {
329
- effects.push(editable.reconfigure(EditorState.readOnly.of(next.readOnly)));
330
- }
331
- if (next.wrap !== current.wrap) {
332
- effects.push(wrapping.reconfigure(next.wrap ? EditorView.lineWrapping : []));
333
- }
334
- if (next.label !== current.label) {
335
- effects.push(words.reconfigure(EditorView.contentAttributes.of({
336
- 'aria-label': next.label,
337
- 'aria-multiline': 'true',
338
- })));
339
- }
340
- if (next.placeholder !== current.placeholder) {
341
- effects.push(hint.reconfigure(next.placeholder ? placeholder(next.placeholder) : []));
342
- }
343
- const doc = view.state.doc.toString();
344
- view.dispatch({
345
- annotations: given.of(true),
346
- ...(next.value === doc
347
- ? {}
348
- : { changes: { from: 0, insert: next.value, to: doc.length } }),
349
- effects,
350
- });
351
- if (next.diagnostics !== current.diagnostics || next.value !== doc) {
352
- view.dispatch(setDiagnostics(view.state, lintOf(next, view.state.doc.length)));
353
- view.dispatch({
354
- effects: problems.reconfigure(problemLines(view.state, next.diagnostics)),
355
- });
356
- }
357
- if (next.language !== current.language) {
358
- load(next.language, view);
359
- }
360
- current = next;
361
- },
362
- };
363
- },
364
- });
365
- export const connectEditor = (editor, options = {}) => {
366
- editor.engine = codeMirrorEngine(options);
367
- return () => {
368
- editor.engine = undefined;
369
- };
370
- };
@@ -1 +0,0 @@
1
- export * from './codemirror-engine.js';
@@ -1 +0,0 @@
1
- export * from './codemirror-engine.js';