@pyreon/code 0.5.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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/lib/analysis/index.js.html +5406 -0
  3. package/lib/dist-B5vB-rif.js +3904 -0
  4. package/lib/dist-B5vB-rif.js.map +1 -0
  5. package/lib/dist-BAfzu5eu.js +1428 -0
  6. package/lib/dist-BAfzu5eu.js.map +1 -0
  7. package/lib/dist-BLlV_D16.js +1166 -0
  8. package/lib/dist-BLlV_D16.js.map +1 -0
  9. package/lib/dist-BNmKLTu8.js +373 -0
  10. package/lib/dist-BNmKLTu8.js.map +1 -0
  11. package/lib/dist-BZtTlC1J.js +692 -0
  12. package/lib/dist-BZtTlC1J.js.map +1 -0
  13. package/lib/dist-CTDqGIAf.js +856 -0
  14. package/lib/dist-CTDqGIAf.js.map +1 -0
  15. package/lib/dist-CTPisNZp.js +83 -0
  16. package/lib/dist-CTPisNZp.js.map +1 -0
  17. package/lib/dist-Ce2tvOxv.js +379 -0
  18. package/lib/dist-Ce2tvOxv.js.map +1 -0
  19. package/lib/dist-CttF0OTv.js +465 -0
  20. package/lib/dist-CttF0OTv.js.map +1 -0
  21. package/lib/dist-DS2tluW9.js +818 -0
  22. package/lib/dist-DS2tluW9.js.map +1 -0
  23. package/lib/dist-DUNx9ldu.js +460 -0
  24. package/lib/dist-DUNx9ldu.js.map +1 -0
  25. package/lib/dist-Dej_yf3k.js +473 -0
  26. package/lib/dist-Dej_yf3k.js.map +1 -0
  27. package/lib/dist-DshStUxU.js +283 -0
  28. package/lib/dist-DshStUxU.js.map +1 -0
  29. package/lib/dist-qTrOe7xY.js +461 -0
  30. package/lib/dist-qTrOe7xY.js.map +1 -0
  31. package/lib/dist-v09vikKr.js +2421 -0
  32. package/lib/dist-v09vikKr.js.map +1 -0
  33. package/lib/index.js +915 -0
  34. package/lib/index.js.map +1 -0
  35. package/lib/types/dist.d.ts +798 -0
  36. package/lib/types/dist.d.ts.map +1 -0
  37. package/lib/types/dist10.d.ts +67 -0
  38. package/lib/types/dist10.d.ts.map +1 -0
  39. package/lib/types/dist11.d.ts +126 -0
  40. package/lib/types/dist11.d.ts.map +1 -0
  41. package/lib/types/dist12.d.ts +21 -0
  42. package/lib/types/dist12.d.ts.map +1 -0
  43. package/lib/types/dist13.d.ts +404 -0
  44. package/lib/types/dist13.d.ts.map +1 -0
  45. package/lib/types/dist14.d.ts +292 -0
  46. package/lib/types/dist14.d.ts.map +1 -0
  47. package/lib/types/dist15.d.ts +132 -0
  48. package/lib/types/dist15.d.ts.map +1 -0
  49. package/lib/types/dist2.d.ts +15 -0
  50. package/lib/types/dist2.d.ts.map +1 -0
  51. package/lib/types/dist3.d.ts +106 -0
  52. package/lib/types/dist3.d.ts.map +1 -0
  53. package/lib/types/dist4.d.ts +67 -0
  54. package/lib/types/dist4.d.ts.map +1 -0
  55. package/lib/types/dist5.d.ts +95 -0
  56. package/lib/types/dist5.d.ts.map +1 -0
  57. package/lib/types/dist6.d.ts +330 -0
  58. package/lib/types/dist6.d.ts.map +1 -0
  59. package/lib/types/dist7.d.ts +15 -0
  60. package/lib/types/dist7.d.ts.map +1 -0
  61. package/lib/types/dist8.d.ts +15 -0
  62. package/lib/types/dist8.d.ts.map +1 -0
  63. package/lib/types/dist9.d.ts +635 -0
  64. package/lib/types/dist9.d.ts.map +1 -0
  65. package/lib/types/index.d.ts +852 -0
  66. package/lib/types/index.d.ts.map +1 -0
  67. package/lib/types/index2.d.ts +347 -0
  68. package/lib/types/index2.d.ts.map +1 -0
  69. package/package.json +79 -0
  70. package/src/components/code-editor.tsx +42 -0
  71. package/src/components/diff-editor.tsx +97 -0
  72. package/src/components/tabbed-editor.tsx +86 -0
  73. package/src/editor.ts +652 -0
  74. package/src/index.ts +52 -0
  75. package/src/languages.ts +77 -0
  76. package/src/minimap.ts +160 -0
  77. package/src/tabbed-editor.ts +231 -0
  78. package/src/tests/code.test.ts +505 -0
  79. package/src/themes.ts +87 -0
  80. package/src/types.ts +253 -0
package/lib/index.js ADDED
@@ -0,0 +1,915 @@
1
+ import { bracketMatching, defaultHighlightStyle, foldGutter, foldKeymap, indentOnInput, indentUnit, syntaxHighlighting } from "@codemirror/language";
2
+ import { MergeView } from "@codemirror/merge";
3
+ import { Compartment, EditorState } from "@codemirror/state";
4
+ import { Decoration, EditorView, GutterMarker, ViewPlugin, crosshairCursor, drawSelection, dropCursor, gutter, highlightActiveLine, highlightActiveLineGutter, keymap, lineNumbers, placeholder, rectangularSelection } from "@codemirror/view";
5
+ import { autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap } from "@codemirror/autocomplete";
6
+ import { defaultKeymap, history, historyKeymap, indentWithTab, redo, undo } from "@codemirror/commands";
7
+ import { lintKeymap, setDiagnostics } from "@codemirror/lint";
8
+ import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
9
+ import { computed, effect, signal } from "@pyreon/reactivity";
10
+
11
+ //#region \0rolldown/runtime.js
12
+ var __defProp = Object.defineProperty;
13
+ var __exportAll = (all, no_symbols) => {
14
+ let target = {};
15
+ for (var name in all) {
16
+ __defProp(target, name, {
17
+ get: all[name],
18
+ enumerable: true
19
+ });
20
+ }
21
+ if (!no_symbols) {
22
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
23
+ }
24
+ return target;
25
+ };
26
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) {
27
+ if (typeof require !== "undefined") return require.apply(this, arguments);
28
+ throw Error("Calling `require` for \"" + x + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
29
+ });
30
+
31
+ //#endregion
32
+ //#region ../../node_modules/.bun/@pyreon+core@0.6.0/node_modules/@pyreon/core/lib/jsx-runtime.js
33
+ /**
34
+ * Hyperscript function — the compiled output of JSX.
35
+ * `<div class="x">hello</div>` → `h("div", { class: "x" }, "hello")`
36
+ *
37
+ * Generic on P so TypeScript validates props match the component's signature
38
+ * at the call site, then stores the result in the loosely-typed VNode.
39
+ */
40
+ /** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */
41
+ const EMPTY_PROPS = {};
42
+ function h(type, props, ...children) {
43
+ return {
44
+ type,
45
+ props: props ?? EMPTY_PROPS,
46
+ children: normalizeChildren(children),
47
+ key: props?.key ?? null
48
+ };
49
+ }
50
+ function normalizeChildren(children) {
51
+ for (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);
52
+ return children;
53
+ }
54
+ function flattenChildren(children) {
55
+ const result = [];
56
+ for (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));
57
+ else result.push(child);
58
+ return result;
59
+ }
60
+ /**
61
+ * JSX automatic runtime.
62
+ *
63
+ * When tsconfig has `"jsxImportSource": "@pyreon/core"`, the TS/bundler compiler
64
+ * rewrites JSX to imports from this file automatically:
65
+ * <div class="x" /> → jsx("div", { class: "x" })
66
+ */
67
+ function jsx(type, props, key) {
68
+ const { children, ...rest } = props;
69
+ const propsWithKey = key != null ? {
70
+ ...rest,
71
+ key
72
+ } : rest;
73
+ if (typeof type === "function") return h(type, children !== void 0 ? {
74
+ ...propsWithKey,
75
+ children
76
+ } : propsWithKey);
77
+ return h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);
78
+ }
79
+ const jsxs = jsx;
80
+
81
+ //#endregion
82
+ //#region src/components/code-editor.tsx
83
+ /**
84
+ * Code editor component — mounts a CodeMirror 6 instance.
85
+ *
86
+ * @example
87
+ * ```tsx
88
+ * const editor = createEditor({
89
+ * value: 'const x = 1',
90
+ * language: 'typescript',
91
+ * theme: 'dark',
92
+ * })
93
+ *
94
+ * <CodeEditor instance={editor} style="height: 400px" />
95
+ * ```
96
+ */
97
+ function CodeEditor(props) {
98
+ const { instance } = props;
99
+ const containerRef = (el) => {
100
+ if (!el) return;
101
+ const mountable = instance;
102
+ if (mountable._mount) mountable._mount(el);
103
+ };
104
+ const baseStyle = `width: 100%; height: 100%; overflow: hidden; ${props.style ?? ""}`;
105
+ return /* @__PURE__ */ jsx("div", {
106
+ ref: containerRef,
107
+ class: `pyreon-code-editor ${props.class ?? ""}`,
108
+ style: baseStyle
109
+ });
110
+ }
111
+
112
+ //#endregion
113
+ //#region src/languages.ts
114
+ /**
115
+ * Language extension loaders — lazy-loaded on demand.
116
+ * Only the requested language is imported, keeping the initial bundle small.
117
+ */
118
+ const languageLoaders = {
119
+ javascript: () => import("./dist-CTDqGIAf.js").then((n) => n.t).then((m) => m.javascript()),
120
+ typescript: () => import("./dist-CTDqGIAf.js").then((n) => n.t).then((m) => m.javascript({ typescript: true })),
121
+ jsx: () => import("./dist-CTDqGIAf.js").then((n) => n.t).then((m) => m.javascript({ jsx: true })),
122
+ tsx: () => import("./dist-CTDqGIAf.js").then((n) => n.t).then((m) => m.javascript({
123
+ typescript: true,
124
+ jsx: true
125
+ })),
126
+ html: () => import("./dist-BAfzu5eu.js").then((m) => m.html()),
127
+ css: () => import("./dist-BLlV_D16.js").then((n) => n.r).then((m) => m.css()),
128
+ json: () => import("./dist-CTPisNZp.js").then((m) => m.json()),
129
+ markdown: () => import("./dist-v09vikKr.js").then((m) => m.markdown()),
130
+ python: () => import("./dist-DS2tluW9.js").then((m) => m.python()),
131
+ rust: () => import("./dist-Ce2tvOxv.js").then((m) => m.rust()),
132
+ sql: () => import("./dist-BZtTlC1J.js").then((m) => m.sql()),
133
+ xml: () => import("./dist-DUNx9ldu.js").then((m) => m.xml()),
134
+ yaml: () => import("./dist-BNmKLTu8.js").then((m) => m.yaml()),
135
+ cpp: () => import("./dist-Dej_yf3k.js").then((m) => m.cpp()),
136
+ java: () => import("./dist-DshStUxU.js").then((m) => m.java()),
137
+ go: () => import("./dist-qTrOe7xY.js").then((m) => m.go()),
138
+ php: () => import("./dist-CttF0OTv.js").then((m) => m.php()),
139
+ ruby: () => Promise.resolve([]),
140
+ shell: () => Promise.resolve([]),
141
+ plain: () => Promise.resolve([])
142
+ };
143
+ const loaded = /* @__PURE__ */ new Map();
144
+ /**
145
+ * Load a language extension. Returns cached if already loaded.
146
+ * Language grammars are lazy-imported — zero cost until used.
147
+ *
148
+ * @example
149
+ * ```ts
150
+ * const ext = await loadLanguage('typescript')
151
+ * ```
152
+ */
153
+ async function loadLanguage(language) {
154
+ const cached = loaded.get(language);
155
+ if (cached) return cached;
156
+ const loader = languageLoaders[language];
157
+ if (!loader) return [];
158
+ try {
159
+ const ext = await loader();
160
+ loaded.set(language, ext);
161
+ return ext;
162
+ } catch {
163
+ return [];
164
+ }
165
+ }
166
+ /**
167
+ * Get available languages.
168
+ */
169
+ function getAvailableLanguages() {
170
+ return Object.keys(languageLoaders);
171
+ }
172
+
173
+ //#endregion
174
+ //#region src/themes.ts
175
+ /**
176
+ * Light theme — clean, minimal.
177
+ */
178
+ const lightTheme = EditorView.theme({
179
+ "&": {
180
+ backgroundColor: "#ffffff",
181
+ color: "#1e293b"
182
+ },
183
+ ".cm-content": { caretColor: "#1e293b" },
184
+ ".cm-cursor": { borderLeftColor: "#1e293b" },
185
+ "&.cm-focused .cm-selectionBackground, .cm-selectionBackground": { backgroundColor: "#dbeafe" },
186
+ ".cm-gutters": {
187
+ backgroundColor: "#f8fafc",
188
+ color: "#94a3b8",
189
+ borderRight: "1px solid #e2e8f0"
190
+ },
191
+ ".cm-activeLineGutter": {
192
+ backgroundColor: "#f1f5f9",
193
+ color: "#475569"
194
+ },
195
+ ".cm-activeLine": { backgroundColor: "#f8fafc" },
196
+ ".cm-foldGutter": { color: "#94a3b8" }
197
+ });
198
+ /**
199
+ * Dark theme — VS Code inspired.
200
+ */
201
+ const darkTheme = EditorView.theme({
202
+ "&": {
203
+ backgroundColor: "#1e1e2e",
204
+ color: "#cdd6f4"
205
+ },
206
+ ".cm-content": { caretColor: "#f5e0dc" },
207
+ ".cm-cursor": { borderLeftColor: "#f5e0dc" },
208
+ "&.cm-focused .cm-selectionBackground, .cm-selectionBackground": { backgroundColor: "#45475a" },
209
+ ".cm-gutters": {
210
+ backgroundColor: "#181825",
211
+ color: "#585b70",
212
+ borderRight: "1px solid #313244"
213
+ },
214
+ ".cm-activeLineGutter": {
215
+ backgroundColor: "#1e1e2e",
216
+ color: "#a6adc8"
217
+ },
218
+ ".cm-activeLine": { backgroundColor: "#1e1e2e80" },
219
+ ".cm-foldGutter": { color: "#585b70" },
220
+ ".cm-matchingBracket": {
221
+ backgroundColor: "#45475a",
222
+ color: "#f5e0dc"
223
+ }
224
+ }, { dark: true });
225
+ /**
226
+ * Resolve a theme value to a CodeMirror extension.
227
+ */
228
+ function resolveTheme(theme) {
229
+ if (theme === "light") return lightTheme;
230
+ if (theme === "dark") return darkTheme;
231
+ return theme;
232
+ }
233
+
234
+ //#endregion
235
+ //#region src/components/diff-editor.tsx
236
+ /**
237
+ * Side-by-side or inline diff editor using @codemirror/merge.
238
+ *
239
+ * @example
240
+ * ```tsx
241
+ * <DiffEditor
242
+ * original="const x = 1"
243
+ * modified="const x = 2"
244
+ * language="typescript"
245
+ * theme="dark"
246
+ * style="height: 400px"
247
+ * />
248
+ * ```
249
+ */
250
+ function DiffEditor(props) {
251
+ const { original, modified, language = "plain", theme = "light", readOnly = true, inline = false } = props;
252
+ const containerRef = async (el) => {
253
+ if (!el) return;
254
+ const langExt = await loadLanguage(language);
255
+ const themeExt = resolveTheme(theme);
256
+ const extensions = [
257
+ syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
258
+ langExt,
259
+ themeExt,
260
+ EditorView.editable.of(!readOnly),
261
+ EditorState.readOnly.of(readOnly)
262
+ ];
263
+ const originalText = typeof original === "string" ? original : original();
264
+ const modifiedText = typeof modified === "string" ? modified : modified();
265
+ el.innerHTML = "";
266
+ if (inline) new MergeView({
267
+ a: {
268
+ doc: originalText,
269
+ extensions
270
+ },
271
+ b: {
272
+ doc: modifiedText,
273
+ extensions
274
+ },
275
+ parent: el,
276
+ collapseUnchanged: {
277
+ margin: 3,
278
+ minSize: 4
279
+ }
280
+ });
281
+ else new MergeView({
282
+ a: {
283
+ doc: originalText,
284
+ extensions
285
+ },
286
+ b: {
287
+ doc: modifiedText,
288
+ extensions
289
+ },
290
+ parent: el,
291
+ collapseUnchanged: {
292
+ margin: 3,
293
+ minSize: 4
294
+ }
295
+ });
296
+ };
297
+ const baseStyle = `width: 100%; height: 100%; overflow: hidden; ${props.style ?? ""}`;
298
+ return /* @__PURE__ */ jsx("div", {
299
+ ref: containerRef,
300
+ class: `pyreon-diff-editor ${props.class ?? ""}`,
301
+ style: baseStyle
302
+ });
303
+ }
304
+
305
+ //#endregion
306
+ //#region src/components/tabbed-editor.tsx
307
+ /**
308
+ * Tabbed code editor component — renders tab bar + editor.
309
+ * Headless styling — the tab bar is a plain div with button tabs.
310
+ * Consumers can style via CSS classes.
311
+ *
312
+ * @example
313
+ * ```tsx
314
+ * const editor = createTabbedEditor({
315
+ * tabs: [
316
+ * { name: 'index.ts', language: 'typescript', value: 'const x = 1' },
317
+ * { name: 'style.css', language: 'css', value: '.app { }' },
318
+ * ],
319
+ * theme: 'dark',
320
+ * })
321
+ *
322
+ * <TabbedEditor instance={editor} style="height: 500px" />
323
+ * ```
324
+ */
325
+ function TabbedEditor(props) {
326
+ const { instance } = props;
327
+ const containerStyle = `display: flex; flex-direction: column; width: 100%; height: 100%; ${props.style ?? ""}`;
328
+ const tabBarStyle = "display: flex; overflow-x: auto; background: #f1f5f9; border-bottom: 1px solid #e2e8f0; min-height: 34px; flex-shrink: 0;";
329
+ return /* @__PURE__ */ jsxs("div", {
330
+ class: `pyreon-tabbed-editor ${props.class ?? ""}`,
331
+ style: containerStyle,
332
+ children: [() => {
333
+ const tabs = instance.tabs();
334
+ const activeId = instance.activeTabId();
335
+ return /* @__PURE__ */ jsx("div", {
336
+ class: "pyreon-tabbed-editor-tabs",
337
+ style: tabBarStyle,
338
+ children: tabs.map((tab) => {
339
+ const id = tab.id ?? tab.name;
340
+ const isActive = id === activeId;
341
+ const tabStyle = `display: flex; align-items: center; gap: 6px; padding: 6px 12px; border: none; background: ${isActive ? "white" : "transparent"}; border-bottom: ${isActive ? "2px solid #3b82f6" : "2px solid transparent"}; cursor: pointer; font-size: 13px; color: ${isActive ? "#1e293b" : "#64748b"}; white-space: nowrap; position: relative; font-family: inherit;`;
342
+ return /* @__PURE__ */ jsxs("button", {
343
+ type: "button",
344
+ class: `pyreon-tab ${isActive ? "active" : ""} ${tab.modified ? "modified" : ""}`,
345
+ style: tabStyle,
346
+ onClick: () => instance.switchTab(id),
347
+ children: [
348
+ /* @__PURE__ */ jsx("span", { children: tab.name }),
349
+ tab.modified && /* @__PURE__ */ jsx("span", {
350
+ style: "width: 6px; height: 6px; border-radius: 50%; background: #f59e0b; flex-shrink: 0;",
351
+ title: "Modified"
352
+ }),
353
+ tab.closable !== false && /* @__PURE__ */ jsx("span", {
354
+ style: "font-size: 14px; line-height: 1; opacity: 0.5; cursor: pointer; padding: 0 2px; margin-left: 2px;",
355
+ title: "Close",
356
+ onClick: (e) => {
357
+ e.stopPropagation();
358
+ instance.closeTab(id);
359
+ },
360
+ children: "×"
361
+ })
362
+ ]
363
+ }, id);
364
+ })
365
+ });
366
+ }, /* @__PURE__ */ jsx("div", {
367
+ style: "flex: 1; min-height: 0;",
368
+ children: /* @__PURE__ */ jsx(CodeEditor, { instance: instance.editor })
369
+ })]
370
+ });
371
+ }
372
+
373
+ //#endregion
374
+ //#region src/minimap.ts
375
+ /**
376
+ * Canvas-based minimap extension for CodeMirror 6.
377
+ * Renders a scaled-down overview of the document on the right side.
378
+ */
379
+ const MINIMAP_WIDTH = 80;
380
+ const CHAR_WIDTH = 1.2;
381
+ const LINE_HEIGHT = 2.5;
382
+ const MINIMAP_BG = "#1e1e2e";
383
+ const MINIMAP_BG_LIGHT = "#f8fafc";
384
+ const TEXT_COLOR = "#585b70";
385
+ const TEXT_COLOR_LIGHT = "#94a3b8";
386
+ const VIEWPORT_COLOR = "rgba(59, 130, 246, 0.15)";
387
+ const VIEWPORT_BORDER = "rgba(59, 130, 246, 0.4)";
388
+ function createMinimapCanvas() {
389
+ const canvas = document.createElement("canvas");
390
+ canvas.style.cssText = `position: absolute; right: 0; top: 0; width: ${MINIMAP_WIDTH}px; height: 100%; cursor: pointer; z-index: 5;`;
391
+ canvas.width = MINIMAP_WIDTH * 2;
392
+ return canvas;
393
+ }
394
+ function renderMinimap(canvas, view) {
395
+ const ctx = canvas.getContext("2d");
396
+ if (!ctx) return;
397
+ const doc = view.state.doc;
398
+ const totalLines = doc.lines;
399
+ const height = canvas.clientHeight;
400
+ canvas.height = height * 2;
401
+ const isDark = view.dom.classList.contains("cm-dark");
402
+ const bg = isDark ? MINIMAP_BG : MINIMAP_BG_LIGHT;
403
+ const textColor = isDark ? TEXT_COLOR : TEXT_COLOR_LIGHT;
404
+ const scale = 2;
405
+ ctx.setTransform(scale, 0, 0, scale, 0, 0);
406
+ ctx.fillStyle = bg;
407
+ ctx.fillRect(0, 0, MINIMAP_WIDTH, height);
408
+ const contentHeight = totalLines * LINE_HEIGHT;
409
+ const scrollFraction = contentHeight > height ? view.scrollDOM.scrollTop / (view.scrollDOM.scrollHeight - view.scrollDOM.clientHeight || 1) : 0;
410
+ const offset = contentHeight > height ? scrollFraction * (contentHeight - height) : 0;
411
+ ctx.fillStyle = textColor;
412
+ const startLine = Math.max(1, Math.floor(offset / LINE_HEIGHT));
413
+ const endLine = Math.min(totalLines, startLine + Math.ceil(height / LINE_HEIGHT) + 1);
414
+ for (let i = startLine; i <= endLine; i++) {
415
+ const line = doc.line(i);
416
+ const y = (i - 1) * LINE_HEIGHT - offset;
417
+ if (y < -LINE_HEIGHT || y > height) continue;
418
+ const text = line.text;
419
+ let x = 4;
420
+ for (let j = 0; j < Math.min(text.length, 60); j++) {
421
+ if (text[j] !== " " && text[j] !== " ") ctx.fillRect(x, y, CHAR_WIDTH, 1.5);
422
+ x += CHAR_WIDTH;
423
+ }
424
+ }
425
+ const viewportTop = view.scrollDOM.scrollTop;
426
+ const viewportHeight = view.scrollDOM.clientHeight;
427
+ const docHeight = view.scrollDOM.scrollHeight || 1;
428
+ const vpY = viewportTop / docHeight * Math.min(contentHeight, height);
429
+ const vpH = viewportHeight / docHeight * Math.min(contentHeight, height);
430
+ ctx.fillStyle = VIEWPORT_COLOR;
431
+ ctx.fillRect(0, vpY, MINIMAP_WIDTH, vpH);
432
+ ctx.strokeStyle = VIEWPORT_BORDER;
433
+ ctx.lineWidth = 1;
434
+ ctx.strokeRect(.5, vpY + .5, MINIMAP_WIDTH - 1, vpH - 1);
435
+ }
436
+ /**
437
+ * CodeMirror 6 minimap extension.
438
+ * Renders a canvas-based code overview on the right side of the editor.
439
+ *
440
+ * @example
441
+ * ```ts
442
+ * import { minimapExtension } from '@pyreon/code'
443
+ * // Add to editor extensions
444
+ * ```
445
+ */
446
+ function minimapExtension() {
447
+ return [ViewPlugin.fromClass(class {
448
+ canvas;
449
+ view;
450
+ animFrame = null;
451
+ constructor(view) {
452
+ this.view = view;
453
+ this.canvas = createMinimapCanvas();
454
+ view.dom.style.position = "relative";
455
+ view.dom.appendChild(this.canvas);
456
+ this.canvas.addEventListener("click", (e) => {
457
+ const rect = this.canvas.getBoundingClientRect();
458
+ const scrollTarget = (e.clientY - rect.top) / rect.height * (view.scrollDOM.scrollHeight - view.scrollDOM.clientHeight);
459
+ view.scrollDOM.scrollTo({
460
+ top: scrollTarget,
461
+ behavior: "smooth"
462
+ });
463
+ });
464
+ this.render();
465
+ }
466
+ render() {
467
+ renderMinimap(this.canvas, this.view);
468
+ }
469
+ update(update) {
470
+ if (update.docChanged || update.viewportChanged || update.geometryChanged) {
471
+ if (this.animFrame) cancelAnimationFrame(this.animFrame);
472
+ this.animFrame = requestAnimationFrame(() => this.render());
473
+ }
474
+ }
475
+ destroy() {
476
+ if (this.animFrame) cancelAnimationFrame(this.animFrame);
477
+ this.canvas.remove();
478
+ }
479
+ }), EditorView.theme({ ".cm-scroller": { paddingRight: `${MINIMAP_WIDTH + 8}px` } })];
480
+ }
481
+
482
+ //#endregion
483
+ //#region src/editor.ts
484
+ /**
485
+ * Create a reactive code editor instance.
486
+ *
487
+ * The editor state (value, language, theme, cursor, selection) is backed
488
+ * by signals. The CodeMirror EditorView is created when mounted via
489
+ * the `<CodeEditor>` component.
490
+ *
491
+ * @param config - Editor configuration
492
+ * @returns A reactive EditorInstance
493
+ *
494
+ * @example
495
+ * ```tsx
496
+ * const editor = createEditor({
497
+ * value: 'const x = 1',
498
+ * language: 'typescript',
499
+ * theme: 'dark',
500
+ * })
501
+ *
502
+ * editor.value() // reactive
503
+ * editor.value.set('new') // updates editor
504
+ *
505
+ * <CodeEditor instance={editor} />
506
+ * ```
507
+ */
508
+ function createEditor(config = {}) {
509
+ const { value: initialValue = "", language: initialLanguage = "plain", theme: initialTheme = "light", lineNumbers: showLineNumbers = true, readOnly: initialReadOnly = false, foldGutter: showFoldGutter = true, bracketMatching: enableBracketMatching = true, autocomplete: enableAutocomplete = true, search: _enableSearch = true, highlightIndentGuides: enableIndentGuides = true, vim: enableVim = false, emacs: enableEmacs = false, tabSize: configTabSize = 2, lineWrapping: enableLineWrapping = false, placeholder: placeholderText, minimap: enableMinimap = false, extensions: userExtensions = [], onChange } = config;
510
+ const value = signal(initialValue);
511
+ const language = signal(initialLanguage);
512
+ const theme = signal(initialTheme);
513
+ const readOnly = signal(initialReadOnly);
514
+ const focused = signal(false);
515
+ const view = signal(null);
516
+ const docVersion = signal(0);
517
+ const languageCompartment = new Compartment();
518
+ const themeCompartment = new Compartment();
519
+ const readOnlyCompartment = new Compartment();
520
+ const extraKeymapCompartment = new Compartment();
521
+ const keyModeCompartment = new Compartment();
522
+ const cursor = computed(() => {
523
+ docVersion();
524
+ const v = view.peek();
525
+ if (!v) return {
526
+ line: 1,
527
+ col: 1
528
+ };
529
+ const pos = v.state.selection.main.head;
530
+ const line = v.state.doc.lineAt(pos);
531
+ return {
532
+ line: line.number,
533
+ col: pos - line.from + 1
534
+ };
535
+ });
536
+ const selection = computed(() => {
537
+ docVersion();
538
+ const v = view.peek();
539
+ if (!v) return {
540
+ from: 0,
541
+ to: 0,
542
+ text: ""
543
+ };
544
+ const sel = v.state.selection.main;
545
+ return {
546
+ from: sel.from,
547
+ to: sel.to,
548
+ text: v.state.sliceDoc(sel.from, sel.to)
549
+ };
550
+ });
551
+ const lineCount = computed(() => {
552
+ docVersion();
553
+ const v = view.peek();
554
+ return v ? v.state.doc.lines : initialValue.split("\n").length;
555
+ });
556
+ const lineHighlights = /* @__PURE__ */ new Map();
557
+ const lineHighlightField = ViewPlugin.fromClass(class {
558
+ decorations;
559
+ constructor(editorView) {
560
+ this.decorations = this.buildDecos(editorView);
561
+ }
562
+ buildDecos(editorView) {
563
+ const ranges = [];
564
+ for (const [lineNum, cls] of lineHighlights) if (lineNum >= 1 && lineNum <= editorView.state.doc.lines) {
565
+ const lineInfo = editorView.state.doc.line(lineNum);
566
+ ranges.push({
567
+ from: lineInfo.from,
568
+ deco: Decoration.line({ class: cls })
569
+ });
570
+ }
571
+ return Decoration.set(ranges.sort((a, b) => a.from - b.from).map((d) => d.deco.range(d.from)));
572
+ }
573
+ update(upd) {
574
+ if (upd.docChanged || upd.viewportChanged) this.decorations = this.buildDecos(upd.view);
575
+ }
576
+ }, { decorations: (plugin) => plugin.decorations });
577
+ const gutterMarkers = /* @__PURE__ */ new Map();
578
+ class CustomGutterMarker extends GutterMarker {
579
+ markerText;
580
+ markerTitle;
581
+ markerClass;
582
+ constructor(opts) {
583
+ super();
584
+ this.markerText = opts.text ?? "";
585
+ this.markerTitle = opts.title ?? "";
586
+ this.markerClass = opts.class ?? "";
587
+ }
588
+ toDOM() {
589
+ const el = document.createElement("span");
590
+ el.textContent = this.markerText;
591
+ el.title = this.markerTitle;
592
+ if (this.markerClass) el.className = this.markerClass;
593
+ el.style.cssText = "cursor: pointer; display: inline-block; width: 100%; text-align: center;";
594
+ return el;
595
+ }
596
+ }
597
+ const gutterMarkerExtension = gutter({
598
+ class: "pyreon-code-gutter-markers",
599
+ lineMarker: (gutterView, line) => {
600
+ const lineNo = gutterView.state.doc.lineAt(line.from).number;
601
+ const marker = gutterMarkers.get(lineNo);
602
+ if (!marker) return null;
603
+ return new CustomGutterMarker(marker);
604
+ },
605
+ initialSpacer: () => new CustomGutterMarker({ text: " " })
606
+ });
607
+ function buildExtensions(langExt) {
608
+ const exts = [
609
+ history(),
610
+ drawSelection(),
611
+ dropCursor(),
612
+ rectangularSelection(),
613
+ crosshairCursor(),
614
+ highlightActiveLine(),
615
+ highlightActiveLineGutter(),
616
+ highlightSelectionMatches(),
617
+ indentOnInput(),
618
+ syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
619
+ indentUnit.of(" ".repeat(configTabSize)),
620
+ keymap.of([
621
+ ...closeBracketsKeymap,
622
+ ...defaultKeymap,
623
+ ...searchKeymap,
624
+ ...historyKeymap,
625
+ ...foldKeymap,
626
+ ...completionKeymap,
627
+ ...lintKeymap,
628
+ indentWithTab
629
+ ]),
630
+ languageCompartment.of(langExt),
631
+ themeCompartment.of(resolveTheme(initialTheme)),
632
+ readOnlyCompartment.of(EditorState.readOnly.of(initialReadOnly)),
633
+ extraKeymapCompartment.of([]),
634
+ keyModeCompartment.of([]),
635
+ EditorView.updateListener.of((update) => {
636
+ if (update.docChanged) {
637
+ const newValue = update.state.doc.toString();
638
+ if (newValue !== value.peek()) {
639
+ value.set(newValue);
640
+ onChange?.(newValue);
641
+ }
642
+ docVersion.update((v) => v + 1);
643
+ }
644
+ if (update.selectionSet) docVersion.update((v) => v + 1);
645
+ if (update.focusChanged) focused.set(update.view.hasFocus);
646
+ })
647
+ ];
648
+ if (showLineNumbers) exts.push(lineNumbers());
649
+ if (showFoldGutter) exts.push(foldGutter());
650
+ if (enableBracketMatching) exts.push(bracketMatching(), closeBrackets());
651
+ if (enableAutocomplete) exts.push(autocompletion());
652
+ if (enableLineWrapping) exts.push(EditorView.lineWrapping);
653
+ if (enableIndentGuides) exts.push(EditorView.theme({ ".cm-line": {
654
+ backgroundImage: "linear-gradient(to right, #e5e7eb 1px, transparent 1px)",
655
+ backgroundSize: `${configTabSize}ch 100%`,
656
+ backgroundPosition: "0 0"
657
+ } }));
658
+ if (placeholderText) exts.push(placeholder(placeholderText));
659
+ if (enableMinimap) exts.push(minimapExtension());
660
+ exts.push(lineHighlightField);
661
+ exts.push(gutterMarkerExtension);
662
+ exts.push(...userExtensions);
663
+ return exts;
664
+ }
665
+ let mounted = false;
666
+ async function mount(parent) {
667
+ if (mounted) return;
668
+ const extensions = buildExtensions(await loadLanguage(language.peek()));
669
+ const editorView = new EditorView({
670
+ state: EditorState.create({
671
+ doc: value.peek(),
672
+ extensions
673
+ }),
674
+ parent
675
+ });
676
+ view.set(editorView);
677
+ mounted = true;
678
+ effect(() => {
679
+ const val = value();
680
+ const v = view.peek();
681
+ if (!v) return;
682
+ const current = v.state.doc.toString();
683
+ if (val !== current) v.dispatch({ changes: {
684
+ from: 0,
685
+ to: current.length,
686
+ insert: val
687
+ } });
688
+ });
689
+ effect(() => {
690
+ const lang = language();
691
+ const v = view.peek();
692
+ if (!v) return;
693
+ loadLanguage(lang).then((ext) => {
694
+ v.dispatch({ effects: languageCompartment.reconfigure(ext) });
695
+ });
696
+ });
697
+ effect(() => {
698
+ const t = theme();
699
+ const v = view.peek();
700
+ if (!v) return;
701
+ v.dispatch({ effects: themeCompartment.reconfigure(resolveTheme(t)) });
702
+ });
703
+ effect(() => {
704
+ const ro = readOnly();
705
+ const v = view.peek();
706
+ if (!v) return;
707
+ v.dispatch({ effects: readOnlyCompartment.reconfigure(EditorState.readOnly.of(ro)) });
708
+ });
709
+ }
710
+ function focus() {
711
+ view.peek()?.focus();
712
+ }
713
+ function insert(text) {
714
+ const v = view.peek();
715
+ if (!v) return;
716
+ const pos = v.state.selection.main.head;
717
+ v.dispatch({ changes: {
718
+ from: pos,
719
+ insert: text
720
+ } });
721
+ }
722
+ function replaceSelection(text) {
723
+ const v = view.peek();
724
+ if (!v) return;
725
+ v.dispatch(v.state.replaceSelection(text));
726
+ }
727
+ function select(from, to) {
728
+ const v = view.peek();
729
+ if (!v) return;
730
+ v.dispatch({ selection: {
731
+ anchor: from,
732
+ head: to
733
+ } });
734
+ }
735
+ function selectAll() {
736
+ const v = view.peek();
737
+ if (!v) return;
738
+ v.dispatch({ selection: {
739
+ anchor: 0,
740
+ head: v.state.doc.length
741
+ } });
742
+ }
743
+ function goToLine(line) {
744
+ const v = view.peek();
745
+ if (!v) return;
746
+ const lineInfo = v.state.doc.line(Math.min(Math.max(1, line), v.state.doc.lines));
747
+ v.dispatch({
748
+ selection: { anchor: lineInfo.from },
749
+ scrollIntoView: true
750
+ });
751
+ v.focus();
752
+ }
753
+ function undo$1() {
754
+ const v = view.peek();
755
+ if (v) undo(v);
756
+ }
757
+ function redo$1() {
758
+ const v = view.peek();
759
+ if (v) redo(v);
760
+ }
761
+ function foldAll() {
762
+ const v = view.peek();
763
+ if (!v) return;
764
+ const { foldAll: foldAllCmd } = __require("@codemirror/language");
765
+ foldAllCmd(v);
766
+ }
767
+ function unfoldAll() {
768
+ const v = view.peek();
769
+ if (!v) return;
770
+ const { unfoldAll: unfoldAllCmd } = __require("@codemirror/language");
771
+ unfoldAllCmd(v);
772
+ }
773
+ function setDiagnostics$1(diagnostics) {
774
+ const v = view.peek();
775
+ if (!v) return;
776
+ v.dispatch(setDiagnostics(v.state, diagnostics.map((d) => ({
777
+ from: d.from,
778
+ to: d.to,
779
+ severity: d.severity === "hint" ? "info" : d.severity,
780
+ message: d.message,
781
+ source: d.source
782
+ }))));
783
+ }
784
+ function clearDiagnostics() {
785
+ const v = view.peek();
786
+ if (!v) return;
787
+ v.dispatch(setDiagnostics(v.state, []));
788
+ }
789
+ function highlightLine(line, className) {
790
+ lineHighlights.set(line, className);
791
+ const v = view.peek();
792
+ if (v) v.dispatch({ effects: [] });
793
+ }
794
+ function clearLineHighlights() {
795
+ lineHighlights.clear();
796
+ const v = view.peek();
797
+ if (v) v.dispatch({ effects: [] });
798
+ }
799
+ function setGutterMarker(line, marker) {
800
+ gutterMarkers.set(line, marker);
801
+ const v = view.peek();
802
+ if (v) v.dispatch({ effects: [] });
803
+ }
804
+ function clearGutterMarkers() {
805
+ gutterMarkers.clear();
806
+ const v = view.peek();
807
+ if (v) v.dispatch({ effects: [] });
808
+ }
809
+ const customKeybindings = [];
810
+ function addKeybinding(key, handler) {
811
+ customKeybindings.push({
812
+ key,
813
+ run: () => {
814
+ handler();
815
+ return true;
816
+ }
817
+ });
818
+ const v = view.peek();
819
+ if (!v) return;
820
+ v.dispatch({ effects: extraKeymapCompartment.reconfigure(keymap.of(customKeybindings)) });
821
+ }
822
+ function getLine(line) {
823
+ const v = view.peek();
824
+ if (!v) return "";
825
+ const clamped = Math.min(Math.max(1, line), v.state.doc.lines);
826
+ return v.state.doc.line(clamped).text;
827
+ }
828
+ function getWordAtCursor() {
829
+ const v = view.peek();
830
+ if (!v) return "";
831
+ const pos = v.state.selection.main.head;
832
+ const line = v.state.doc.lineAt(pos);
833
+ const col = pos - line.from;
834
+ const text = line.text;
835
+ let start = col;
836
+ let end = col;
837
+ while (start > 0 && /\w/.test(text[start - 1])) start--;
838
+ while (end < text.length && /\w/.test(text[end])) end++;
839
+ return text.slice(start, end);
840
+ }
841
+ function scrollTo(pos) {
842
+ const v = view.peek();
843
+ if (!v) return;
844
+ v.dispatch({ effects: EditorView.scrollIntoView(pos, { y: "center" }) });
845
+ }
846
+ async function loadKeyMode() {
847
+ const v = view.peek();
848
+ if (!v) return;
849
+ const vimPkg = "@replit/codemirror-vim";
850
+ const emacsPkg = "@replit/codemirror-emacs";
851
+ if (enableVim) try {
852
+ const mod = await import(
853
+ /* @vite-ignore */
854
+ vimPkg
855
+ );
856
+ v.dispatch({ effects: keyModeCompartment.reconfigure(mod.vim()) });
857
+ } catch {}
858
+ if (enableEmacs) try {
859
+ const mod = await import(
860
+ /* @vite-ignore */
861
+ emacsPkg
862
+ );
863
+ v.dispatch({ effects: keyModeCompartment.reconfigure(mod.emacs()) });
864
+ } catch {}
865
+ }
866
+ function dispose() {
867
+ const v = view.peek();
868
+ if (v) {
869
+ v.destroy();
870
+ view.set(null);
871
+ mounted = false;
872
+ }
873
+ }
874
+ return {
875
+ value,
876
+ language,
877
+ theme,
878
+ readOnly,
879
+ cursor,
880
+ selection,
881
+ lineCount,
882
+ focused,
883
+ view,
884
+ focus,
885
+ insert,
886
+ replaceSelection,
887
+ select,
888
+ selectAll,
889
+ goToLine,
890
+ undo: undo$1,
891
+ redo: redo$1,
892
+ foldAll,
893
+ unfoldAll,
894
+ setDiagnostics: setDiagnostics$1,
895
+ clearDiagnostics,
896
+ highlightLine,
897
+ clearLineHighlights,
898
+ setGutterMarker,
899
+ clearGutterMarkers,
900
+ addKeybinding,
901
+ getLine,
902
+ getWordAtCursor,
903
+ scrollTo,
904
+ config,
905
+ dispose,
906
+ _mount: async (parent) => {
907
+ await mount(parent);
908
+ await loadKeyMode();
909
+ }
910
+ };
911
+ }
912
+
913
+ //#endregion
914
+ export { CodeEditor, DiffEditor, TabbedEditor, createEditor, darkTheme, getAvailableLanguages, lightTheme, loadLanguage, minimapExtension, resolveTheme, __exportAll as t };
915
+ //# sourceMappingURL=index.js.map