@pyreon/code 0.9.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.
- package/lib/analysis/index.js.html +1 -1
- package/lib/{dist-qTrOe7xY.js → dist-5FA-omYL.js} +4 -4
- package/lib/dist-5FA-omYL.js.map +1 -0
- package/lib/{dist-CTPisNZp.js → dist-BisvZuec.js} +4 -4
- package/lib/dist-BisvZuec.js.map +1 -0
- package/lib/{dist-BAfzu5eu.js → dist-BrE8YJpx.js} +6 -6
- package/lib/dist-BrE8YJpx.js.map +1 -0
- package/lib/{dist-Dej_yf3k.js → dist-BxzbGcJt.js} +4 -4
- package/lib/dist-BxzbGcJt.js.map +1 -0
- package/lib/{dist-Ce2tvOxv.js → dist-CXUY-Nzh.js} +4 -4
- package/lib/dist-CXUY-Nzh.js.map +1 -0
- package/lib/{dist-B5vB-rif.js → dist-CfapY6Xm.js} +4 -4
- package/lib/dist-CfapY6Xm.js.map +1 -0
- package/lib/{dist-BLlV_D16.js → dist-CkzBqhDP.js} +30 -30
- package/lib/dist-CkzBqhDP.js.map +1 -0
- package/lib/{dist-BZtTlC1J.js → dist-DGt-lmGy.js} +3 -3
- package/lib/dist-DGt-lmGy.js.map +1 -0
- package/lib/{dist-DUNx9ldu.js → dist-DqTrMnaP.js} +4 -4
- package/lib/dist-DqTrMnaP.js.map +1 -0
- package/lib/{dist-CttF0OTv.js → dist-DwKx52QE.js} +5 -5
- package/lib/dist-DwKx52QE.js.map +1 -0
- package/lib/{dist-BNmKLTu8.js → dist-Oei2Buyd.js} +5 -5
- package/lib/dist-Oei2Buyd.js.map +1 -0
- package/lib/{dist-DS2tluW9.js → dist-TrbJHyZy.js} +4 -4
- package/lib/dist-TrbJHyZy.js.map +1 -0
- package/lib/{dist-DshStUxU.js → dist-jF2joTkO.js} +4 -4
- package/lib/dist-jF2joTkO.js.map +1 -0
- package/lib/{dist-CTDqGIAf.js → dist-pDxtX_z4.js} +4 -4
- package/lib/dist-pDxtX_z4.js.map +1 -0
- package/lib/{dist-v09vikKr.js → dist-sGMMCnKq.js} +5 -5
- package/lib/dist-sGMMCnKq.js.map +1 -0
- package/lib/index.js +20 -20
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +4 -4
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +14 -7
- package/src/components/code-editor.tsx +4 -8
- package/src/components/diff-editor.tsx +16 -22
- package/src/components/tabbed-editor.tsx +8 -11
- package/src/editor.ts +44 -67
- package/src/index.ts +8 -8
- package/src/languages.ts +20 -28
- package/src/minimap.ts +21 -31
- package/src/tabbed-editor.ts +24 -38
- package/src/tests/code.test.ts +193 -199
- package/src/themes.ts +48 -48
- package/src/types.ts +27 -27
- package/lib/dist-B5vB-rif.js.map +0 -1
- package/lib/dist-BAfzu5eu.js.map +0 -1
- package/lib/dist-BLlV_D16.js.map +0 -1
- package/lib/dist-BNmKLTu8.js.map +0 -1
- package/lib/dist-BZtTlC1J.js.map +0 -1
- package/lib/dist-CTDqGIAf.js.map +0 -1
- package/lib/dist-CTPisNZp.js.map +0 -1
- package/lib/dist-Ce2tvOxv.js.map +0 -1
- package/lib/dist-CttF0OTv.js.map +0 -1
- package/lib/dist-DS2tluW9.js.map +0 -1
- package/lib/dist-DUNx9ldu.js.map +0 -1
- package/lib/dist-Dej_yf3k.js.map +0 -1
- package/lib/dist-DshStUxU.js.map +0 -1
- package/lib/dist-qTrOe7xY.js.map +0 -1
- 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
|
|
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
|
|
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
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
48
|
-
import { computed, effect, signal } from
|
|
49
|
-
import { loadLanguage } from
|
|
50
|
-
import { minimapExtension } from
|
|
51
|
-
import { resolveTheme } from
|
|
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 =
|
|
87
|
-
theme: initialTheme =
|
|
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(
|
|
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(
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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(
|
|
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(
|
|
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 ===
|
|
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
|
|
556
|
-
while (end < text.length && /\w/.test(text
|
|
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:
|
|
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 =
|
|
577
|
-
const emacsPkg =
|
|
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
|
|
28
|
-
export { DiffEditor } from
|
|
29
|
-
export { TabbedEditor } from
|
|
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
|
|
31
|
+
export { createEditor } from "./editor"
|
|
32
32
|
// Languages
|
|
33
|
-
export { getAvailableLanguages, loadLanguage } from
|
|
33
|
+
export { getAvailableLanguages, loadLanguage } from "./languages"
|
|
34
34
|
// Minimap
|
|
35
|
-
export { minimapExtension } from
|
|
35
|
+
export { minimapExtension } from "./minimap"
|
|
36
36
|
// Themes
|
|
37
|
-
export { darkTheme, lightTheme, resolveTheme } from
|
|
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
|
|
52
|
+
} from "./types"
|
package/src/languages.ts
CHANGED
|
@@ -1,38 +1,32 @@
|
|
|
1
|
-
import type { Extension } from
|
|
2
|
-
import type { EditorLanguage } from
|
|
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(
|
|
13
|
-
|
|
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(
|
|
14
|
+
import("@codemirror/lang-javascript").then((m) =>
|
|
21
15
|
m.javascript({ typescript: true, jsx: true }),
|
|
22
16
|
),
|
|
23
|
-
html: () => import(
|
|
24
|
-
css: () => import(
|
|
25
|
-
json: () => import(
|
|
26
|
-
markdown: () => import(
|
|
27
|
-
python: () => import(
|
|
28
|
-
rust: () => import(
|
|
29
|
-
sql: () => import(
|
|
30
|
-
xml: () => import(
|
|
31
|
-
yaml: () => import(
|
|
32
|
-
cpp: () => import(
|
|
33
|
-
java: () => import(
|
|
34
|
-
go: () => import(
|
|
35
|
-
php: () => import(
|
|
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
|
|
2
|
-
import { EditorView, ViewPlugin, type ViewUpdate } from
|
|
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 =
|
|
13
|
-
const MINIMAP_BG_LIGHT =
|
|
14
|
-
const TEXT_COLOR =
|
|
15
|
-
const TEXT_COLOR_LIGHT =
|
|
16
|
-
const VIEWPORT_COLOR =
|
|
17
|
-
const VIEWPORT_BORDER =
|
|
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(
|
|
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(
|
|
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(
|
|
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] !==
|
|
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 =
|
|
110
|
+
view.dom.style.position = "relative"
|
|
116
111
|
view.dom.appendChild(this.canvas)
|
|
117
112
|
|
|
118
113
|
// Click to scroll
|
|
119
|
-
this.canvas.addEventListener(
|
|
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
|
-
|
|
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
|
-
|
|
145
|
+
".cm-scroller": {
|
|
156
146
|
paddingRight: `${MINIMAP_WIDTH + 8}px`,
|
|
157
147
|
},
|
|
158
148
|
}),
|
package/src/tabbed-editor.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import { computed, signal } from
|
|
2
|
-
import { createEditor } from
|
|
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
|
-
|
|
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 ??
|
|
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 ??
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|