@pyreon/code 0.10.0 → 0.11.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 (62) hide show
  1. package/lib/analysis/index.js.html +1 -1
  2. package/lib/{dist-qTrOe7xY.js → dist-5FA-omYL.js} +4 -4
  3. package/lib/dist-5FA-omYL.js.map +1 -0
  4. package/lib/{dist-CTPisNZp.js → dist-BisvZuec.js} +4 -4
  5. package/lib/dist-BisvZuec.js.map +1 -0
  6. package/lib/{dist-BAfzu5eu.js → dist-BrE8YJpx.js} +6 -6
  7. package/lib/dist-BrE8YJpx.js.map +1 -0
  8. package/lib/{dist-Dej_yf3k.js → dist-BxzbGcJt.js} +4 -4
  9. package/lib/dist-BxzbGcJt.js.map +1 -0
  10. package/lib/{dist-Ce2tvOxv.js → dist-CXUY-Nzh.js} +4 -4
  11. package/lib/dist-CXUY-Nzh.js.map +1 -0
  12. package/lib/{dist-B5vB-rif.js → dist-CfapY6Xm.js} +4 -4
  13. package/lib/dist-CfapY6Xm.js.map +1 -0
  14. package/lib/{dist-BLlV_D16.js → dist-CkzBqhDP.js} +30 -30
  15. package/lib/dist-CkzBqhDP.js.map +1 -0
  16. package/lib/{dist-BZtTlC1J.js → dist-DGt-lmGy.js} +3 -3
  17. package/lib/dist-DGt-lmGy.js.map +1 -0
  18. package/lib/{dist-DUNx9ldu.js → dist-DqTrMnaP.js} +4 -4
  19. package/lib/dist-DqTrMnaP.js.map +1 -0
  20. package/lib/{dist-CttF0OTv.js → dist-DwKx52QE.js} +5 -5
  21. package/lib/dist-DwKx52QE.js.map +1 -0
  22. package/lib/{dist-BNmKLTu8.js → dist-Oei2Buyd.js} +5 -5
  23. package/lib/dist-Oei2Buyd.js.map +1 -0
  24. package/lib/{dist-DS2tluW9.js → dist-TrbJHyZy.js} +4 -4
  25. package/lib/dist-TrbJHyZy.js.map +1 -0
  26. package/lib/{dist-DshStUxU.js → dist-jF2joTkO.js} +4 -4
  27. package/lib/dist-jF2joTkO.js.map +1 -0
  28. package/lib/{dist-CTDqGIAf.js → dist-pDxtX_z4.js} +4 -4
  29. package/lib/dist-pDxtX_z4.js.map +1 -0
  30. package/lib/{dist-v09vikKr.js → dist-sGMMCnKq.js} +5 -5
  31. package/lib/dist-sGMMCnKq.js.map +1 -0
  32. package/lib/index.js +20 -20
  33. package/lib/index.js.map +1 -1
  34. package/lib/types/index.d.ts +4 -4
  35. package/lib/types/index.d.ts.map +1 -1
  36. package/package.json +14 -7
  37. package/src/components/code-editor.tsx +4 -8
  38. package/src/components/diff-editor.tsx +16 -22
  39. package/src/components/tabbed-editor.tsx +8 -11
  40. package/src/editor.ts +44 -67
  41. package/src/index.ts +8 -8
  42. package/src/languages.ts +20 -28
  43. package/src/minimap.ts +21 -31
  44. package/src/tabbed-editor.ts +24 -38
  45. package/src/tests/code.test.ts +193 -199
  46. package/src/themes.ts +48 -48
  47. package/src/types.ts +27 -27
  48. package/lib/dist-B5vB-rif.js.map +0 -1
  49. package/lib/dist-BAfzu5eu.js.map +0 -1
  50. package/lib/dist-BLlV_D16.js.map +0 -1
  51. package/lib/dist-BNmKLTu8.js.map +0 -1
  52. package/lib/dist-BZtTlC1J.js.map +0 -1
  53. package/lib/dist-CTDqGIAf.js.map +0 -1
  54. package/lib/dist-CTPisNZp.js.map +0 -1
  55. package/lib/dist-Ce2tvOxv.js.map +0 -1
  56. package/lib/dist-CttF0OTv.js.map +0 -1
  57. package/lib/dist-DS2tluW9.js.map +0 -1
  58. package/lib/dist-DUNx9ldu.js.map +0 -1
  59. package/lib/dist-Dej_yf3k.js.map +0 -1
  60. package/lib/dist-DshStUxU.js.map +0 -1
  61. package/lib/dist-qTrOe7xY.js.map +0 -1
  62. package/lib/dist-v09vikKr.js.map +0 -1
package/src/editor.ts CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  closeBrackets,
4
4
  closeBracketsKeymap,
5
5
  completionKeymap,
6
- } from '@codemirror/autocomplete'
6
+ } from "@codemirror/autocomplete"
7
7
  import {
8
8
  redo as cmRedo,
9
9
  undo as cmUndo,
@@ -11,7 +11,7 @@ import {
11
11
  history,
12
12
  historyKeymap,
13
13
  indentWithTab,
14
- } from '@codemirror/commands'
14
+ } from "@codemirror/commands"
15
15
  import {
16
16
  bracketMatching,
17
17
  defaultHighlightStyle,
@@ -20,13 +20,10 @@ import {
20
20
  indentOnInput,
21
21
  indentUnit,
22
22
  syntaxHighlighting,
23
- } from '@codemirror/language'
24
- import {
25
- setDiagnostics as cmSetDiagnostics,
26
- lintKeymap,
27
- } from '@codemirror/lint'
28
- import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
29
- import { Compartment, EditorState, type Extension } from '@codemirror/state'
23
+ } from "@codemirror/language"
24
+ import { setDiagnostics as cmSetDiagnostics, lintKeymap } from "@codemirror/lint"
25
+ import { highlightSelectionMatches, searchKeymap } from "@codemirror/search"
26
+ import { Compartment, EditorState, type Extension } from "@codemirror/state"
30
27
  import {
31
28
  GutterMarker as CMGutterMarker,
32
29
  crosshairCursor,
@@ -44,17 +41,12 @@ import {
44
41
  rectangularSelection,
45
42
  ViewPlugin,
46
43
  type ViewUpdate,
47
- } from '@codemirror/view'
48
- import { computed, effect, signal } from '@pyreon/reactivity'
49
- import { loadLanguage } from './languages'
50
- import { minimapExtension } from './minimap'
51
- import { resolveTheme } from './themes'
52
- import type {
53
- EditorConfig,
54
- EditorInstance,
55
- EditorLanguage,
56
- EditorTheme,
57
- } from './types'
44
+ } from "@codemirror/view"
45
+ import { computed, effect, signal } from "@pyreon/reactivity"
46
+ import { loadLanguage } from "./languages"
47
+ import { minimapExtension } from "./minimap"
48
+ import { resolveTheme } from "./themes"
49
+ import type { EditorConfig, EditorInstance, EditorLanguage, EditorTheme } from "./types"
58
50
 
59
51
  /**
60
52
  * Create a reactive code editor instance.
@@ -82,9 +74,9 @@ import type {
82
74
  */
83
75
  export function createEditor(config: EditorConfig = {}): EditorInstance {
84
76
  const {
85
- value: initialValue = '',
86
- language: initialLanguage = 'plain',
87
- theme: initialTheme = 'light',
77
+ value: initialValue = "",
78
+ language: initialLanguage = "plain",
79
+ theme: initialTheme = "light",
88
80
  lineNumbers: showLineNumbers = true,
89
81
  readOnly: initialReadOnly = false,
90
82
  foldGutter: showFoldGutter = true,
@@ -136,7 +128,7 @@ export function createEditor(config: EditorConfig = {}): EditorInstance {
136
128
  const selection = computed(() => {
137
129
  docVersion()
138
130
  const v = view.peek()
139
- if (!v) return { from: 0, to: 0, text: '' }
131
+ if (!v) return { from: 0, to: 0, text: "" }
140
132
  const sel = v.state.selection.main
141
133
  return {
142
134
  from: sel.from,
@@ -148,7 +140,7 @@ export function createEditor(config: EditorConfig = {}): EditorInstance {
148
140
  const lineCount = computed(() => {
149
141
  docVersion()
150
142
  const v = view.peek()
151
- return v ? v.state.doc.lines : initialValue.split('\n').length
143
+ return v ? v.state.doc.lines : initialValue.split("\n").length
152
144
  })
153
145
 
154
146
  // ── Line highlight support ──────────────────────────────────────────
@@ -175,9 +167,7 @@ export function createEditor(config: EditorConfig = {}): EditorInstance {
175
167
  }
176
168
  }
177
169
  return Decoration.set(
178
- ranges
179
- .sort((a, b) => a.from - b.from)
180
- .map((d) => d.deco.range(d.from)),
170
+ ranges.sort((a, b) => a.from - b.from).map((d) => d.deco.range(d.from)),
181
171
  )
182
172
  }
183
173
 
@@ -192,10 +182,7 @@ export function createEditor(config: EditorConfig = {}): EditorInstance {
192
182
 
193
183
  // ── Gutter marker support ──────────────────────────────────────────
194
184
 
195
- const gutterMarkers = new Map<
196
- number,
197
- { class?: string; text?: string; title?: string }
198
- >()
185
+ const gutterMarkers = new Map<number, { class?: string; text?: string; title?: string }>()
199
186
 
200
187
  class CustomGutterMarker extends CMGutterMarker {
201
188
  markerText: string
@@ -204,31 +191,30 @@ export function createEditor(config: EditorConfig = {}): EditorInstance {
204
191
 
205
192
  constructor(opts: { class?: string; text?: string; title?: string }) {
206
193
  super()
207
- this.markerText = opts.text ?? ''
208
- this.markerTitle = opts.title ?? ''
209
- this.markerClass = opts.class ?? ''
194
+ this.markerText = opts.text ?? ""
195
+ this.markerTitle = opts.title ?? ""
196
+ this.markerClass = opts.class ?? ""
210
197
  }
211
198
 
212
199
  override toDOM() {
213
- const el = document.createElement('span')
200
+ const el = document.createElement("span")
214
201
  el.textContent = this.markerText
215
202
  el.title = this.markerTitle
216
203
  if (this.markerClass) el.className = this.markerClass
217
- el.style.cssText =
218
- 'cursor: pointer; display: inline-block; width: 100%; text-align: center;'
204
+ el.style.cssText = "cursor: pointer; display: inline-block; width: 100%; text-align: center;"
219
205
  return el
220
206
  }
221
207
  }
222
208
 
223
209
  const gutterMarkerExtension = gutter({
224
- class: 'pyreon-code-gutter-markers',
210
+ class: "pyreon-code-gutter-markers",
225
211
  lineMarker: (gutterView, line) => {
226
212
  const lineNo = gutterView.state.doc.lineAt(line.from).number
227
213
  const marker = gutterMarkers.get(lineNo)
228
214
  if (!marker) return null
229
215
  return new CustomGutterMarker(marker)
230
216
  },
231
- initialSpacer: () => new CustomGutterMarker({ text: ' ' }),
217
+ initialSpacer: () => new CustomGutterMarker({ text: " " }),
232
218
  })
233
219
 
234
220
  // ── Build extensions ─────────────────────────────────────────────────
@@ -246,7 +232,7 @@ export function createEditor(config: EditorConfig = {}): EditorInstance {
246
232
  highlightSelectionMatches(),
247
233
  indentOnInput(),
248
234
  syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
249
- indentUnit.of(' '.repeat(configTabSize)),
235
+ indentUnit.of(" ".repeat(configTabSize)),
250
236
 
251
237
  // Keymaps
252
238
  keymap.of([
@@ -297,11 +283,10 @@ export function createEditor(config: EditorConfig = {}): EditorInstance {
297
283
  if (enableIndentGuides) {
298
284
  exts.push(
299
285
  EditorView.theme({
300
- '.cm-line': {
301
- backgroundImage:
302
- 'linear-gradient(to right, #e5e7eb 1px, transparent 1px)',
286
+ ".cm-line": {
287
+ backgroundImage: "linear-gradient(to right, #e5e7eb 1px, transparent 1px)",
303
288
  backgroundSize: `${configTabSize}ch 100%`,
304
- backgroundPosition: '0 0',
289
+ backgroundPosition: "0 0",
305
290
  },
306
291
  }),
307
292
  )
@@ -419,9 +404,7 @@ export function createEditor(config: EditorConfig = {}): EditorInstance {
419
404
  function goToLine(line: number): void {
420
405
  const v = view.peek()
421
406
  if (!v) return
422
- const lineInfo = v.state.doc.line(
423
- Math.min(Math.max(1, line), v.state.doc.lines),
424
- )
407
+ const lineInfo = v.state.doc.line(Math.min(Math.max(1, line), v.state.doc.lines))
425
408
  v.dispatch({
426
409
  selection: { anchor: lineInfo.from },
427
410
  scrollIntoView: true,
@@ -442,20 +425,20 @@ export function createEditor(config: EditorConfig = {}): EditorInstance {
442
425
  function foldAll(): void {
443
426
  const v = view.peek()
444
427
  if (!v) return
445
- const { foldAll: foldAllCmd } = require('@codemirror/language')
428
+ const { foldAll: foldAllCmd } = require("@codemirror/language")
446
429
  foldAllCmd(v)
447
430
  }
448
431
 
449
432
  function unfoldAll(): void {
450
433
  const v = view.peek()
451
434
  if (!v) return
452
- const { unfoldAll: unfoldAllCmd } = require('@codemirror/language')
435
+ const { unfoldAll: unfoldAllCmd } = require("@codemirror/language")
453
436
  unfoldAllCmd(v)
454
437
  }
455
438
 
456
439
  // ── Diagnostics ────────────────────────────────────────────────────
457
440
 
458
- function setDiagnostics(diagnostics: import('./types').Diagnostic[]): void {
441
+ function setDiagnostics(diagnostics: import("./types").Diagnostic[]): void {
459
442
  const v = view.peek()
460
443
  if (!v) return
461
444
  v.dispatch(
@@ -464,7 +447,7 @@ export function createEditor(config: EditorConfig = {}): EditorInstance {
464
447
  diagnostics.map((d) => ({
465
448
  from: d.from,
466
449
  to: d.to,
467
- severity: d.severity === 'hint' ? 'info' : d.severity,
450
+ severity: d.severity === "hint" ? "info" : d.severity,
468
451
  message: d.message,
469
452
  ...(d.source != null ? { source: d.source } : {}),
470
453
  })),
@@ -495,10 +478,7 @@ export function createEditor(config: EditorConfig = {}): EditorInstance {
495
478
 
496
479
  // ── Gutter markers ────────────────────────────────────────────────
497
480
 
498
- function setGutterMarker(
499
- line: number,
500
- marker: import('./types').GutterMarker,
501
- ): void {
481
+ function setGutterMarker(line: number, marker: import("./types").GutterMarker): void {
502
482
  gutterMarkers.set(line, marker)
503
483
  const v = view.peek()
504
484
  if (v) v.dispatch({ effects: [] })
@@ -514,10 +494,7 @@ export function createEditor(config: EditorConfig = {}): EditorInstance {
514
494
 
515
495
  const customKeybindings: Array<{ key: string; run: () => boolean }> = []
516
496
 
517
- function addKeybinding(
518
- key: string,
519
- handler: () => boolean | undefined,
520
- ): void {
497
+ function addKeybinding(key: string, handler: () => boolean | undefined): void {
521
498
  customKeybindings.push({
522
499
  key,
523
500
  run: () => {
@@ -536,14 +513,14 @@ export function createEditor(config: EditorConfig = {}): EditorInstance {
536
513
 
537
514
  function getLine(line: number): string {
538
515
  const v = view.peek()
539
- if (!v) return ''
516
+ if (!v) return ""
540
517
  const clamped = Math.min(Math.max(1, line), v.state.doc.lines)
541
518
  return v.state.doc.line(clamped).text
542
519
  }
543
520
 
544
521
  function getWordAtCursor(): string {
545
522
  const v = view.peek()
546
- if (!v) return ''
523
+ if (!v) return ""
547
524
  const pos = v.state.selection.main.head
548
525
  const line = v.state.doc.lineAt(pos)
549
526
  const col = pos - line.from
@@ -552,8 +529,8 @@ export function createEditor(config: EditorConfig = {}): EditorInstance {
552
529
  // Find word boundaries
553
530
  let start = col
554
531
  let end = col
555
- while (start > 0 && /\w/.test(text[start - 1]!)) start--
556
- while (end < text.length && /\w/.test(text[end]!)) end++
532
+ while (start > 0 && /\w/.test(text.charAt(start - 1))) start--
533
+ while (end < text.length && /\w/.test(text.charAt(end))) end++
557
534
 
558
535
  return text.slice(start, end)
559
536
  }
@@ -562,7 +539,7 @@ export function createEditor(config: EditorConfig = {}): EditorInstance {
562
539
  const v = view.peek()
563
540
  if (!v) return
564
541
  v.dispatch({
565
- effects: EditorView.scrollIntoView(pos, { y: 'center' }),
542
+ effects: EditorView.scrollIntoView(pos, { y: "center" }),
566
543
  })
567
544
  }
568
545
 
@@ -573,8 +550,8 @@ export function createEditor(config: EditorConfig = {}): EditorInstance {
573
550
  if (!v) return
574
551
 
575
552
  // Use string concat to prevent Vite from statically analyzing these optional imports
576
- const vimPkg = '@replit/codemirror-' + 'vim'
577
- const emacsPkg = '@replit/codemirror-' + 'emacs'
553
+ const vimPkg = "@replit/codemirror-" + "vim"
554
+ const emacsPkg = "@replit/codemirror-" + "emacs"
578
555
 
579
556
  if (enableVim) {
580
557
  try {
package/src/index.ts CHANGED
@@ -24,17 +24,17 @@
24
24
  */
25
25
 
26
26
  // Components
27
- export { CodeEditor } from './components/code-editor'
28
- export { DiffEditor } from './components/diff-editor'
29
- export { TabbedEditor } from './components/tabbed-editor'
27
+ export { CodeEditor } from "./components/code-editor"
28
+ export { DiffEditor } from "./components/diff-editor"
29
+ export { TabbedEditor } from "./components/tabbed-editor"
30
30
  // Core
31
- export { createEditor } from './editor'
31
+ export { createEditor } from "./editor"
32
32
  // Languages
33
- export { getAvailableLanguages, loadLanguage } from './languages'
33
+ export { getAvailableLanguages, loadLanguage } from "./languages"
34
34
  // Minimap
35
- export { minimapExtension } from './minimap'
35
+ export { minimapExtension } from "./minimap"
36
36
  // Themes
37
- export { darkTheme, lightTheme, resolveTheme } from './themes'
37
+ export { darkTheme, lightTheme, resolveTheme } from "./themes"
38
38
 
39
39
  // Types
40
40
  export type {
@@ -49,4 +49,4 @@ export type {
49
49
  TabbedEditorConfig,
50
50
  TabbedEditorInstance,
51
51
  TabbedEditorProps,
52
- } from './types'
52
+ } from "./types"
package/src/languages.ts CHANGED
@@ -1,38 +1,32 @@
1
- import type { Extension } from '@codemirror/state'
2
- import type { EditorLanguage } from './types'
1
+ import type { Extension } from "@codemirror/state"
2
+ import type { EditorLanguage } from "./types"
3
3
 
4
4
  /**
5
5
  * Language extension loaders — lazy-loaded on demand.
6
6
  * Only the requested language is imported, keeping the initial bundle small.
7
7
  */
8
8
  const languageLoaders: Record<EditorLanguage, () => Promise<Extension>> = {
9
- javascript: () =>
10
- import('@codemirror/lang-javascript').then((m) => m.javascript()),
9
+ javascript: () => import("@codemirror/lang-javascript").then((m) => m.javascript()),
11
10
  typescript: () =>
12
- import('@codemirror/lang-javascript').then((m) =>
13
- m.javascript({ typescript: true }),
14
- ),
15
- jsx: () =>
16
- import('@codemirror/lang-javascript').then((m) =>
17
- m.javascript({ jsx: true }),
18
- ),
11
+ import("@codemirror/lang-javascript").then((m) => m.javascript({ typescript: true })),
12
+ jsx: () => import("@codemirror/lang-javascript").then((m) => m.javascript({ jsx: true })),
19
13
  tsx: () =>
20
- import('@codemirror/lang-javascript').then((m) =>
14
+ import("@codemirror/lang-javascript").then((m) =>
21
15
  m.javascript({ typescript: true, jsx: true }),
22
16
  ),
23
- html: () => import('@codemirror/lang-html').then((m) => m.html()),
24
- css: () => import('@codemirror/lang-css').then((m) => m.css()),
25
- json: () => import('@codemirror/lang-json').then((m) => m.json()),
26
- markdown: () => import('@codemirror/lang-markdown').then((m) => m.markdown()),
27
- python: () => import('@codemirror/lang-python').then((m) => m.python()),
28
- rust: () => import('@codemirror/lang-rust').then((m) => m.rust()),
29
- sql: () => import('@codemirror/lang-sql').then((m) => m.sql()),
30
- xml: () => import('@codemirror/lang-xml').then((m) => m.xml()),
31
- yaml: () => import('@codemirror/lang-yaml').then((m) => m.yaml()),
32
- cpp: () => import('@codemirror/lang-cpp').then((m) => m.cpp()),
33
- java: () => import('@codemirror/lang-java').then((m) => m.java()),
34
- go: () => import('@codemirror/lang-go').then((m) => m.go()),
35
- php: () => import('@codemirror/lang-php').then((m) => m.php()),
17
+ html: () => import("@codemirror/lang-html").then((m) => m.html()),
18
+ css: () => import("@codemirror/lang-css").then((m) => m.css()),
19
+ json: () => import("@codemirror/lang-json").then((m) => m.json()),
20
+ markdown: () => import("@codemirror/lang-markdown").then((m) => m.markdown()),
21
+ python: () => import("@codemirror/lang-python").then((m) => m.python()),
22
+ rust: () => import("@codemirror/lang-rust").then((m) => m.rust()),
23
+ sql: () => import("@codemirror/lang-sql").then((m) => m.sql()),
24
+ xml: () => import("@codemirror/lang-xml").then((m) => m.xml()),
25
+ yaml: () => import("@codemirror/lang-yaml").then((m) => m.yaml()),
26
+ cpp: () => import("@codemirror/lang-cpp").then((m) => m.cpp()),
27
+ java: () => import("@codemirror/lang-java").then((m) => m.java()),
28
+ go: () => import("@codemirror/lang-go").then((m) => m.go()),
29
+ php: () => import("@codemirror/lang-php").then((m) => m.php()),
36
30
  ruby: () => Promise.resolve([]),
37
31
  shell: () => Promise.resolve([]),
38
32
  plain: () => Promise.resolve([]),
@@ -50,9 +44,7 @@ const loaded = new Map<EditorLanguage, Extension>()
50
44
  * const ext = await loadLanguage('typescript')
51
45
  * ```
52
46
  */
53
- export async function loadLanguage(
54
- language: EditorLanguage,
55
- ): Promise<Extension> {
47
+ export async function loadLanguage(language: EditorLanguage): Promise<Extension> {
56
48
  const cached = loaded.get(language)
57
49
  if (cached) return cached
58
50
 
package/src/minimap.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { Extension } from '@codemirror/state'
2
- import { EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view'
1
+ import type { Extension } from "@codemirror/state"
2
+ import { EditorView, ViewPlugin, type ViewUpdate } from "@codemirror/view"
3
3
 
4
4
  /**
5
5
  * Canvas-based minimap extension for CodeMirror 6.
@@ -9,22 +9,22 @@ import { EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view'
9
9
  const MINIMAP_WIDTH = 80
10
10
  const CHAR_WIDTH = 1.2
11
11
  const LINE_HEIGHT = 2.5
12
- const MINIMAP_BG = '#1e1e2e'
13
- const MINIMAP_BG_LIGHT = '#f8fafc'
14
- const TEXT_COLOR = '#585b70'
15
- const TEXT_COLOR_LIGHT = '#94a3b8'
16
- const VIEWPORT_COLOR = 'rgba(59, 130, 246, 0.15)'
17
- const VIEWPORT_BORDER = 'rgba(59, 130, 246, 0.4)'
12
+ const MINIMAP_BG = "#1e1e2e"
13
+ const MINIMAP_BG_LIGHT = "#f8fafc"
14
+ const TEXT_COLOR = "#585b70"
15
+ const TEXT_COLOR_LIGHT = "#94a3b8"
16
+ const VIEWPORT_COLOR = "rgba(59, 130, 246, 0.15)"
17
+ const VIEWPORT_BORDER = "rgba(59, 130, 246, 0.4)"
18
18
 
19
19
  function createMinimapCanvas(): HTMLCanvasElement {
20
- const canvas = document.createElement('canvas')
20
+ const canvas = document.createElement("canvas")
21
21
  canvas.style.cssText = `position: absolute; right: 0; top: 0; width: ${MINIMAP_WIDTH}px; height: 100%; cursor: pointer; z-index: 5;`
22
22
  canvas.width = MINIMAP_WIDTH * 2 // retina
23
23
  return canvas
24
24
  }
25
25
 
26
26
  function renderMinimap(canvas: HTMLCanvasElement, view: EditorView): void {
27
- const ctx = canvas.getContext('2d')
27
+ const ctx = canvas.getContext("2d")
28
28
  if (!ctx) return
29
29
 
30
30
  const doc = view.state.doc
@@ -32,7 +32,7 @@ function renderMinimap(canvas: HTMLCanvasElement, view: EditorView): void {
32
32
  const height = canvas.clientHeight
33
33
  canvas.height = height * 2 // retina
34
34
 
35
- const isDark = view.dom.classList.contains('cm-dark')
35
+ const isDark = view.dom.classList.contains("cm-dark")
36
36
  const bg = isDark ? MINIMAP_BG : MINIMAP_BG_LIGHT
37
37
  const textColor = isDark ? TEXT_COLOR : TEXT_COLOR_LIGHT
38
38
 
@@ -47,19 +47,14 @@ function renderMinimap(canvas: HTMLCanvasElement, view: EditorView): void {
47
47
  const contentHeight = totalLines * LINE_HEIGHT
48
48
  const scrollFraction =
49
49
  contentHeight > height
50
- ? view.scrollDOM.scrollTop /
51
- (view.scrollDOM.scrollHeight - view.scrollDOM.clientHeight || 1)
50
+ ? view.scrollDOM.scrollTop / (view.scrollDOM.scrollHeight - view.scrollDOM.clientHeight || 1)
52
51
  : 0
53
- const offset =
54
- contentHeight > height ? scrollFraction * (contentHeight - height) : 0
52
+ const offset = contentHeight > height ? scrollFraction * (contentHeight - height) : 0
55
53
 
56
54
  // Render text lines
57
55
  ctx.fillStyle = textColor
58
56
  const startLine = Math.max(1, Math.floor(offset / LINE_HEIGHT))
59
- const endLine = Math.min(
60
- totalLines,
61
- startLine + Math.ceil(height / LINE_HEIGHT) + 1,
62
- )
57
+ const endLine = Math.min(totalLines, startLine + Math.ceil(height / LINE_HEIGHT) + 1)
63
58
 
64
59
  for (let i = startLine; i <= endLine; i++) {
65
60
  const line = doc.line(i)
@@ -69,7 +64,7 @@ function renderMinimap(canvas: HTMLCanvasElement, view: EditorView): void {
69
64
  const text = line.text
70
65
  let x = 4
71
66
  for (let j = 0; j < Math.min(text.length, 60); j++) {
72
- if (text[j] !== ' ' && text[j] !== '\t') {
67
+ if (text[j] !== " " && text[j] !== "\t") {
73
68
  ctx.fillRect(x, y, CHAR_WIDTH, 1.5)
74
69
  }
75
70
  x += CHAR_WIDTH
@@ -112,18 +107,17 @@ export function minimapExtension(): Extension {
112
107
  constructor(view: EditorView) {
113
108
  this.view = view
114
109
  this.canvas = createMinimapCanvas()
115
- view.dom.style.position = 'relative'
110
+ view.dom.style.position = "relative"
116
111
  view.dom.appendChild(this.canvas)
117
112
 
118
113
  // Click to scroll
119
- this.canvas.addEventListener('click', (e) => {
114
+ this.canvas.addEventListener("click", (e) => {
120
115
  const rect = this.canvas.getBoundingClientRect()
121
116
  const clickY = e.clientY - rect.top
122
117
  const fraction = clickY / rect.height
123
118
  const scrollTarget =
124
- fraction *
125
- (view.scrollDOM.scrollHeight - view.scrollDOM.clientHeight)
126
- view.scrollDOM.scrollTo({ top: scrollTarget, behavior: 'smooth' })
119
+ fraction * (view.scrollDOM.scrollHeight - view.scrollDOM.clientHeight)
120
+ view.scrollDOM.scrollTo({ top: scrollTarget, behavior: "smooth" })
127
121
  })
128
122
 
129
123
  this.render()
@@ -134,11 +128,7 @@ export function minimapExtension(): Extension {
134
128
  }
135
129
 
136
130
  update(update: ViewUpdate) {
137
- if (
138
- update.docChanged ||
139
- update.viewportChanged ||
140
- update.geometryChanged
141
- ) {
131
+ if (update.docChanged || update.viewportChanged || update.geometryChanged) {
142
132
  if (this.animFrame) cancelAnimationFrame(this.animFrame)
143
133
  this.animFrame = requestAnimationFrame(() => this.render())
144
134
  }
@@ -152,7 +142,7 @@ export function minimapExtension(): Extension {
152
142
  ),
153
143
  // Add padding on the right for the minimap
154
144
  EditorView.theme({
155
- '.cm-scroller': {
145
+ ".cm-scroller": {
156
146
  paddingRight: `${MINIMAP_WIDTH + 8}px`,
157
147
  },
158
148
  }),
@@ -1,11 +1,6 @@
1
- import { computed, signal } from '@pyreon/reactivity'
2
- import { createEditor } from './editor'
3
- import type {
4
- EditorLanguage,
5
- Tab,
6
- TabbedEditorConfig,
7
- TabbedEditorInstance,
8
- } from './types'
1
+ import { computed, signal } from "@pyreon/reactivity"
2
+ import { createEditor } from "./editor"
3
+ import type { EditorLanguage, Tab, TabbedEditorConfig, TabbedEditorInstance } from "./types"
9
4
 
10
5
  /**
11
6
  * Create a tabbed code editor — multiple files with tab management.
@@ -34,9 +29,7 @@ import type {
34
29
  * <TabbedEditor instance={editor} />
35
30
  * ```
36
31
  */
37
- export function createTabbedEditor(
38
- config: TabbedEditorConfig = {},
39
- ): TabbedEditorInstance {
32
+ export function createTabbedEditor(config: TabbedEditorConfig = {}): TabbedEditorInstance {
40
33
  const { tabs: initialTabs = [], theme, editorConfig = {} } = config
41
34
 
42
35
  // Ensure all tabs have IDs
@@ -49,12 +42,13 @@ export function createTabbedEditor(
49
42
  // ── State ──────────────────────────────────────────────────────────────
50
43
 
51
44
  const tabs = signal<Tab[]>(tabsWithIds)
52
- const activeTabId = signal(tabsWithIds[0]?.id ?? '')
45
+ const activeTabId = signal(tabsWithIds[0]?.id ?? "")
53
46
 
54
47
  // Content cache — stores each tab's current content
55
48
  const contentCache = new Map<string, string>()
56
49
  for (const tab of tabsWithIds) {
57
- contentCache.set(tab.id!, tab.value)
50
+ const tabId = tab.id ?? tab.name
51
+ contentCache.set(tabId, tab.value)
58
52
  }
59
53
 
60
54
  // ── Editor instance ────────────────────────────────────────────────────
@@ -65,8 +59,8 @@ export function createTabbedEditor(
65
59
  Object.entries(editorConfig).filter(([_, v]) => v !== undefined),
66
60
  )
67
61
  const editor = createEditor({
68
- value: firstTab?.value ?? '',
69
- language: (firstTab?.language ?? 'plain') as EditorLanguage,
62
+ value: firstTab?.value ?? "",
63
+ language: (firstTab?.language ?? "plain") as EditorLanguage,
70
64
  ...(theme != null ? { theme } : {}),
71
65
  ...filteredConfig,
72
66
  onChange: (value) => {
@@ -112,7 +106,7 @@ export function createTabbedEditor(
112
106
  // Restore target tab content
113
107
  const cached = contentCache.get(id)
114
108
  editor.value.set(cached ?? tab.value)
115
- editor.language.set((tab.language ?? 'plain') as EditorLanguage)
109
+ editor.language.set((tab.language ?? "plain") as EditorLanguage)
116
110
  }
117
111
 
118
112
  function openTab(tab: Tab): void {
@@ -136,8 +130,8 @@ export function createTabbedEditor(
136
130
  const tabIndex = currentTabs.findIndex((t) => (t.id ?? t.name) === id)
137
131
  if (tabIndex === -1) return
138
132
 
139
- const tab = currentTabs[tabIndex]!
140
- if (tab.closable === false) return
133
+ const tab = currentTabs[tabIndex]
134
+ if (!tab || tab.closable === false) return
141
135
 
142
136
  // Remove from state
143
137
  tabs.update((t) => t.filter((item) => (item.id ?? item.name) !== id))
@@ -148,26 +142,21 @@ export function createTabbedEditor(
148
142
  const remaining = tabs.peek()
149
143
  if (remaining.length > 0) {
150
144
  const nextIndex = Math.min(tabIndex, remaining.length - 1)
151
- switchTab(remaining[nextIndex]!.id ?? remaining[nextIndex]!.name)
145
+ const nextTab = remaining[nextIndex]
146
+ if (nextTab) switchTab(nextTab.id ?? nextTab.name)
152
147
  } else {
153
- activeTabId.set('')
154
- editor.value.set('')
148
+ activeTabId.set("")
149
+ editor.value.set("")
155
150
  }
156
151
  }
157
152
  }
158
153
 
159
154
  function renameTab(id: string, name: string): void {
160
- tabs.update((t) =>
161
- t.map((tab) => ((tab.id ?? tab.name) === id ? { ...tab, name } : tab)),
162
- )
155
+ tabs.update((t) => t.map((tab) => ((tab.id ?? tab.name) === id ? { ...tab, name } : tab)))
163
156
  }
164
157
 
165
158
  function setModified(id: string, modified: boolean): void {
166
- tabs.update((t) =>
167
- t.map((tab) =>
168
- (tab.id ?? tab.name) === id ? { ...tab, modified } : tab,
169
- ),
170
- )
159
+ tabs.update((t) => t.map((tab) => ((tab.id ?? tab.name) === id ? { ...tab, modified } : tab)))
171
160
  }
172
161
 
173
162
  function moveTab(fromIndex: number, toIndex: number): void {
@@ -191,23 +180,20 @@ export function createTabbedEditor(
191
180
  tabs.update((t) => t.filter((tab) => tab.closable === false))
192
181
  const remaining = tabs.peek()
193
182
  if (remaining.length > 0) {
194
- switchTab(remaining[0]!.id ?? remaining[0]!.name)
183
+ const first = remaining[0]
184
+ if (first) switchTab(first.id ?? first.name)
195
185
  } else {
196
- activeTabId.set('')
197
- editor.value.set('')
186
+ activeTabId.set("")
187
+ editor.value.set("")
198
188
  }
199
189
  }
200
190
 
201
191
  function closeOthers(id: string): void {
202
- const toClose = tabs
203
- .peek()
204
- .filter((t) => (t.id ?? t.name) !== id && t.closable !== false)
192
+ const toClose = tabs.peek().filter((t) => (t.id ?? t.name) !== id && t.closable !== false)
205
193
  for (const tab of toClose) {
206
194
  contentCache.delete(tab.id ?? tab.name)
207
195
  }
208
- tabs.update((t) =>
209
- t.filter((tab) => (tab.id ?? tab.name) === id || tab.closable === false),
210
- )
196
+ tabs.update((t) => t.filter((tab) => (tab.id ?? tab.name) === id || tab.closable === false))
211
197
  switchTab(id)
212
198
  }
213
199