@particle-academy/fancy-code 0.7.0 → 0.9.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/README.md +20 -0
- package/dist/index.cjs +115 -237
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -37
- package/dist/index.d.ts +49 -37
- package/dist/index.js +99 -227
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -44,6 +44,26 @@ function App() {
|
|
|
44
44
|
|
|
45
45
|
The component ships with no default border or rounding — add your own via `className` for standalone use, or embed directly into IDE layouts without visual conflicts.
|
|
46
46
|
|
|
47
|
+
## Diff gutter
|
|
48
|
+
|
|
49
|
+
Pass `diffBase` (the baseline document) and the line-number column shows live,
|
|
50
|
+
VS Code-style change marks of the **current value vs that baseline** — a green
|
|
51
|
+
bar for added lines, a blue bar for modified lines, and a red wedge where lines
|
|
52
|
+
were deleted. Recomputed as the user types (line-level, cheap — the engine is
|
|
53
|
+
[`@particle-academy/fancy-file-commons`](https://github.com/Particle-Academy/fancy-file-commons),
|
|
54
|
+
the same core `fancy-diff` renders).
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
<CodeEditor value={code} onChange={setCode} diffBase={savedCode} language="typescript">
|
|
58
|
+
<CodeEditor.Panel />
|
|
59
|
+
</CodeEditor>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Totals are exposed on the context as `diffStats` (`{ added, modified, removed }`)
|
|
63
|
+
for a status-bar chip or save indicator, and every marked row carries stable
|
|
64
|
+
`data-diff` / `data-diff-deleted-above` handles. Themes may override the colors
|
|
65
|
+
via `diffAdded` / `diffModified` / `diffRemoved`.
|
|
66
|
+
|
|
47
67
|
## Unified file viewer
|
|
48
68
|
|
|
49
69
|
Showing arbitrary files from a tree? `FileViewer` renders text in the
|
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
4
|
var reactFancy = require('@particle-academy/react-fancy');
|
|
5
|
+
var fancyFileCommons = require('@particle-academy/fancy-file-commons');
|
|
5
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
6
7
|
|
|
7
8
|
// src/components/CodeEditor/CodeEditor.tsx
|
|
@@ -13,6 +14,24 @@ function useCodeEditor() {
|
|
|
13
14
|
}
|
|
14
15
|
return ctx;
|
|
15
16
|
}
|
|
17
|
+
|
|
18
|
+
// src/components/CodeEditor/diff-gutter.ts
|
|
19
|
+
function gutterDiffMark(annotations, line) {
|
|
20
|
+
if (!annotations) return null;
|
|
21
|
+
const a = annotations.byLine[line];
|
|
22
|
+
if (!a) return null;
|
|
23
|
+
return { type: a.type, deletedAbove: (a.deletedAbove ?? 0) > 0 };
|
|
24
|
+
}
|
|
25
|
+
function hasDeletedAtEnd(annotations) {
|
|
26
|
+
return (annotations?.deletedAtEnd ?? 0) > 0;
|
|
27
|
+
}
|
|
28
|
+
var FALLBACK = { added: "#3fb950", modified: "#4184e4", removed: "#f85149" };
|
|
29
|
+
function diffMarkColor(type, colors) {
|
|
30
|
+
return type === "added" ? colors.diffAdded ?? FALLBACK.added : colors.diffModified ?? FALLBACK.modified;
|
|
31
|
+
}
|
|
32
|
+
function diffRemovedColor(colors) {
|
|
33
|
+
return colors.diffRemoved ?? FALLBACK.removed;
|
|
34
|
+
}
|
|
16
35
|
function CodeEditorPanel({ className }) {
|
|
17
36
|
const {
|
|
18
37
|
_engineReturn: engine,
|
|
@@ -20,6 +39,7 @@ function CodeEditorPanel({ className }) {
|
|
|
20
39
|
wordWrap,
|
|
21
40
|
readOnly,
|
|
22
41
|
placeholder,
|
|
42
|
+
_diffAnnotations: diffAnnotations,
|
|
23
43
|
_maxHeight,
|
|
24
44
|
_minHeight
|
|
25
45
|
} = useCodeEditor();
|
|
@@ -40,25 +60,80 @@ function CodeEditorPanel({ className }) {
|
|
|
40
60
|
const gutterWidth = lineNumbers ? `${Math.max(String(lineCount).length, 2) * 0.75 + 1.5}em` : "0";
|
|
41
61
|
const lineNumberElements = react.useMemo(() => {
|
|
42
62
|
if (!lineNumbers) return null;
|
|
63
|
+
const removed = diffRemovedColor(themeColors);
|
|
43
64
|
const lines = [];
|
|
44
65
|
for (let i = 1; i <= lineCount; i++) {
|
|
66
|
+
const mark = gutterDiffMark(diffAnnotations, i);
|
|
45
67
|
lines.push(
|
|
46
|
-
/* @__PURE__ */ jsxRuntime.
|
|
68
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
47
69
|
"div",
|
|
48
70
|
{
|
|
71
|
+
"data-diff": mark?.type,
|
|
72
|
+
"data-diff-deleted-above": mark?.deletedAbove ? "" : void 0,
|
|
49
73
|
style: {
|
|
74
|
+
position: "relative",
|
|
50
75
|
color: i === activeLine ? themeColors.foreground : themeColors.gutterForeground,
|
|
51
76
|
backgroundColor: i === activeLine ? themeColors.activeLineBackground : void 0
|
|
52
77
|
},
|
|
53
78
|
className: "pr-3 text-right text-[13px] leading-[1.5] select-none",
|
|
54
|
-
children:
|
|
79
|
+
children: [
|
|
80
|
+
mark?.type && /* @__PURE__ */ jsxRuntime.jsx(
|
|
81
|
+
"span",
|
|
82
|
+
{
|
|
83
|
+
"aria-hidden": "true",
|
|
84
|
+
style: {
|
|
85
|
+
position: "absolute",
|
|
86
|
+
left: 0,
|
|
87
|
+
top: 0,
|
|
88
|
+
bottom: 0,
|
|
89
|
+
width: 3,
|
|
90
|
+
borderRadius: 2,
|
|
91
|
+
backgroundColor: diffMarkColor(mark.type, themeColors)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
),
|
|
95
|
+
mark?.deletedAbove && /* @__PURE__ */ jsxRuntime.jsx(
|
|
96
|
+
"span",
|
|
97
|
+
{
|
|
98
|
+
"aria-hidden": "true",
|
|
99
|
+
style: {
|
|
100
|
+
position: "absolute",
|
|
101
|
+
left: 0,
|
|
102
|
+
top: -4,
|
|
103
|
+
width: 0,
|
|
104
|
+
height: 0,
|
|
105
|
+
borderLeft: `8px solid ${removed}`,
|
|
106
|
+
borderTop: "4px solid transparent",
|
|
107
|
+
borderBottom: "4px solid transparent"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
),
|
|
111
|
+
i,
|
|
112
|
+
i === lineCount && hasDeletedAtEnd(diffAnnotations) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
113
|
+
"span",
|
|
114
|
+
{
|
|
115
|
+
"aria-hidden": "true",
|
|
116
|
+
"data-diff-deleted-at-end": "",
|
|
117
|
+
style: {
|
|
118
|
+
position: "absolute",
|
|
119
|
+
left: 0,
|
|
120
|
+
bottom: -4,
|
|
121
|
+
width: 0,
|
|
122
|
+
height: 0,
|
|
123
|
+
borderLeft: `8px solid ${removed}`,
|
|
124
|
+
borderTop: "4px solid transparent",
|
|
125
|
+
borderBottom: "4px solid transparent"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
]
|
|
55
130
|
},
|
|
56
131
|
i
|
|
57
132
|
)
|
|
58
133
|
);
|
|
59
134
|
}
|
|
60
135
|
return lines;
|
|
61
|
-
}, [lineNumbers, lineCount, activeLine, themeColors]);
|
|
136
|
+
}, [lineNumbers, lineCount, activeLine, themeColors, diffAnnotations]);
|
|
62
137
|
const containerStyle = {
|
|
63
138
|
backgroundColor: themeColors.background,
|
|
64
139
|
color: themeColors.foreground,
|
|
@@ -371,89 +446,6 @@ var tokenizeJavaScript = (source) => {
|
|
|
371
446
|
return tokens;
|
|
372
447
|
};
|
|
373
448
|
|
|
374
|
-
// src/engine/tokenizers/html.ts
|
|
375
|
-
var tokenizeHtml = (source) => {
|
|
376
|
-
const tokens = [];
|
|
377
|
-
const len = source.length;
|
|
378
|
-
let i = 0;
|
|
379
|
-
while (i < len) {
|
|
380
|
-
if (source[i] === "<" && source.slice(i, i + 4) === "<!--") {
|
|
381
|
-
const pos2 = i;
|
|
382
|
-
i += 4;
|
|
383
|
-
while (i < len && source.slice(i, i + 3) !== "-->") i++;
|
|
384
|
-
i += 3;
|
|
385
|
-
tokens.push({ type: "comment", start: pos2, end: i });
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
if (source[i] === "<" && source.slice(i, i + 9).toUpperCase() === "<!DOCTYPE") {
|
|
389
|
-
const pos2 = i;
|
|
390
|
-
while (i < len && source[i] !== ">") i++;
|
|
391
|
-
i++;
|
|
392
|
-
tokens.push({ type: "keyword", start: pos2, end: i });
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
if (source[i] === "<") {
|
|
396
|
-
tokens.push({ type: "punctuation", start: i, end: i + 1 });
|
|
397
|
-
i++;
|
|
398
|
-
if (i < len && source[i] === "/") {
|
|
399
|
-
tokens.push({ type: "punctuation", start: i, end: i + 1 });
|
|
400
|
-
i++;
|
|
401
|
-
}
|
|
402
|
-
if (i < len && /[a-zA-Z]/.test(source[i])) {
|
|
403
|
-
const pos2 = i;
|
|
404
|
-
while (i < len && /[a-zA-Z0-9-]/.test(source[i])) i++;
|
|
405
|
-
tokens.push({ type: "tag", start: pos2, end: i });
|
|
406
|
-
}
|
|
407
|
-
while (i < len && source[i] !== ">" && !(source[i] === "/" && source[i + 1] === ">")) {
|
|
408
|
-
if (source[i] === " " || source[i] === " " || source[i] === "\n" || source[i] === "\r") {
|
|
409
|
-
i++;
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
if (/[a-zA-Z_@:]/.test(source[i])) {
|
|
413
|
-
const pos2 = i;
|
|
414
|
-
while (i < len && /[a-zA-Z0-9_\-:.]/.test(source[i])) i++;
|
|
415
|
-
tokens.push({ type: "attribute", start: pos2, end: i });
|
|
416
|
-
while (i < len && (source[i] === " " || source[i] === " ")) i++;
|
|
417
|
-
if (i < len && source[i] === "=") {
|
|
418
|
-
tokens.push({ type: "operator", start: i, end: i + 1 });
|
|
419
|
-
i++;
|
|
420
|
-
while (i < len && (source[i] === " " || source[i] === " ")) i++;
|
|
421
|
-
if (i < len && (source[i] === '"' || source[i] === "'")) {
|
|
422
|
-
const quote = source[i];
|
|
423
|
-
const pos3 = i;
|
|
424
|
-
i++;
|
|
425
|
-
while (i < len && source[i] !== quote) i++;
|
|
426
|
-
i++;
|
|
427
|
-
tokens.push({ type: "attributeValue", start: pos3, end: i });
|
|
428
|
-
} else if (i < len && /[^\s>]/.test(source[i])) {
|
|
429
|
-
const pos3 = i;
|
|
430
|
-
while (i < len && /[^\s>]/.test(source[i])) i++;
|
|
431
|
-
tokens.push({ type: "attributeValue", start: pos3, end: i });
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
continue;
|
|
435
|
-
}
|
|
436
|
-
i++;
|
|
437
|
-
}
|
|
438
|
-
if (i < len && source[i] === "/") {
|
|
439
|
-
tokens.push({ type: "punctuation", start: i, end: i + 1 });
|
|
440
|
-
i++;
|
|
441
|
-
}
|
|
442
|
-
if (i < len && source[i] === ">") {
|
|
443
|
-
tokens.push({ type: "punctuation", start: i, end: i + 1 });
|
|
444
|
-
i++;
|
|
445
|
-
}
|
|
446
|
-
continue;
|
|
447
|
-
}
|
|
448
|
-
const pos = i;
|
|
449
|
-
while (i < len && source[i] !== "<") i++;
|
|
450
|
-
if (i > pos) {
|
|
451
|
-
tokens.push({ type: "plain", start: pos, end: i });
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
return tokens;
|
|
455
|
-
};
|
|
456
|
-
|
|
457
449
|
// src/engine/tokenizers/php.ts
|
|
458
450
|
var PHP_KEYWORDS = /* @__PURE__ */ new Set([
|
|
459
451
|
"abstract",
|
|
@@ -631,7 +623,7 @@ var tokenizePhp = (source) => {
|
|
|
631
623
|
if (source[i] === "<" && source[i + 1] === "?") {
|
|
632
624
|
if (i > htmlChunkStart) {
|
|
633
625
|
const htmlChunk = source.slice(htmlChunkStart, i);
|
|
634
|
-
const htmlTokens = tokenizeHtml(htmlChunk);
|
|
626
|
+
const htmlTokens = fancyFileCommons.tokenizeHtml(htmlChunk);
|
|
635
627
|
for (const t of htmlTokens) {
|
|
636
628
|
tokens.push({ type: t.type, start: t.start + htmlChunkStart, end: t.end + htmlChunkStart });
|
|
637
629
|
}
|
|
@@ -658,7 +650,7 @@ var tokenizePhp = (source) => {
|
|
|
658
650
|
}
|
|
659
651
|
if (htmlChunkStart < len) {
|
|
660
652
|
const htmlChunk = source.slice(htmlChunkStart);
|
|
661
|
-
const htmlTokens = tokenizeHtml(htmlChunk);
|
|
653
|
+
const htmlTokens = fancyFileCommons.tokenizeHtml(htmlChunk);
|
|
662
654
|
for (const t of htmlTokens) {
|
|
663
655
|
tokens.push({ type: t.type, start: t.start + htmlChunkStart, end: t.end + htmlChunkStart });
|
|
664
656
|
}
|
|
@@ -1037,11 +1029,6 @@ var tokenizeGo = (source) => {
|
|
|
1037
1029
|
return tokens;
|
|
1038
1030
|
};
|
|
1039
1031
|
|
|
1040
|
-
// src/engine/tokenizer.ts
|
|
1041
|
-
function tok(type, s, e) {
|
|
1042
|
-
return { type, start: s, end: e };
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
1032
|
// src/engine/tokenizers/markdown.ts
|
|
1046
1033
|
function tokenizeMarkdown(source) {
|
|
1047
1034
|
const tokens = [];
|
|
@@ -1053,26 +1040,26 @@ function tokenizeMarkdown(source) {
|
|
|
1053
1040
|
const len = line.length;
|
|
1054
1041
|
offset += len + 1;
|
|
1055
1042
|
if (/^\s*(```|~~~)/.test(line)) {
|
|
1056
|
-
tokens.push(tok("string", base, base + len));
|
|
1043
|
+
tokens.push(fancyFileCommons.tok("string", base, base + len));
|
|
1057
1044
|
inFence = !inFence;
|
|
1058
1045
|
continue;
|
|
1059
1046
|
}
|
|
1060
1047
|
if (inFence) {
|
|
1061
|
-
tokens.push(tok("string", base, base + len));
|
|
1048
|
+
tokens.push(fancyFileCommons.tok("string", base, base + len));
|
|
1062
1049
|
continue;
|
|
1063
1050
|
}
|
|
1064
1051
|
if (/^#{1,6}\s/.test(line)) {
|
|
1065
|
-
tokens.push(tok("keyword", base, base + len));
|
|
1052
|
+
tokens.push(fancyFileCommons.tok("keyword", base, base + len));
|
|
1066
1053
|
continue;
|
|
1067
1054
|
}
|
|
1068
1055
|
if (/^\s*>/.test(line)) {
|
|
1069
|
-
tokens.push(tok("comment", base, base + len));
|
|
1056
|
+
tokens.push(fancyFileCommons.tok("comment", base, base + len));
|
|
1070
1057
|
continue;
|
|
1071
1058
|
}
|
|
1072
1059
|
const list = line.match(/^(\s*)([-*+]|\d+\.)\s/);
|
|
1073
1060
|
if (list) {
|
|
1074
1061
|
const start = base + list[1].length;
|
|
1075
|
-
tokens.push(tok("operator", start, start + list[2].length));
|
|
1062
|
+
tokens.push(fancyFileCommons.tok("operator", start, start + list[2].length));
|
|
1076
1063
|
}
|
|
1077
1064
|
pushInline(line, base, tokens);
|
|
1078
1065
|
}
|
|
@@ -1082,7 +1069,7 @@ function pushInline(line, base, out) {
|
|
|
1082
1069
|
if (line.length > 5e3) return;
|
|
1083
1070
|
const spans = [];
|
|
1084
1071
|
const add = (type, start, end) => {
|
|
1085
|
-
spans.push(tok(type, base + start, base + end));
|
|
1072
|
+
spans.push(fancyFileCommons.tok(type, base + start, base + end));
|
|
1086
1073
|
};
|
|
1087
1074
|
let m;
|
|
1088
1075
|
const code = /`[^`]+`/g;
|
|
@@ -1125,7 +1112,7 @@ registerLanguage({
|
|
|
1125
1112
|
registerLanguage({
|
|
1126
1113
|
name: "HTML",
|
|
1127
1114
|
aliases: ["html", "htm"],
|
|
1128
|
-
tokenize: tokenizeHtml
|
|
1115
|
+
tokenize: fancyFileCommons.tokenizeHtml
|
|
1129
1116
|
});
|
|
1130
1117
|
registerLanguage({
|
|
1131
1118
|
name: "PHP",
|
|
@@ -1246,106 +1233,17 @@ function getTheme(name) {
|
|
|
1246
1233
|
function getRegisteredThemes() {
|
|
1247
1234
|
return Array.from(themeRegistry.keys());
|
|
1248
1235
|
}
|
|
1249
|
-
|
|
1250
|
-
// src/themes/light.ts
|
|
1251
1236
|
registerTheme({
|
|
1252
1237
|
name: "light",
|
|
1253
1238
|
variant: "light",
|
|
1254
|
-
colors:
|
|
1255
|
-
background: "#ffffff",
|
|
1256
|
-
foreground: "#1e1e2e",
|
|
1257
|
-
gutterBackground: "#f8fafc",
|
|
1258
|
-
gutterForeground: "#94a3b8",
|
|
1259
|
-
gutterBorder: "#e2e8f0",
|
|
1260
|
-
activeLineBackground: "#f8fafc",
|
|
1261
|
-
selectionBackground: "#dbeafe",
|
|
1262
|
-
cursorColor: "#3b82f6",
|
|
1263
|
-
keyword: "#8b5cf6",
|
|
1264
|
-
string: "#059669",
|
|
1265
|
-
comment: "#94a3b8",
|
|
1266
|
-
number: "#d97706",
|
|
1267
|
-
operator: "#0891b2",
|
|
1268
|
-
function: "#2563eb",
|
|
1269
|
-
type: "#d97706",
|
|
1270
|
-
tag: "#dc2626",
|
|
1271
|
-
attribute: "#d97706",
|
|
1272
|
-
attributeValue: "#059669",
|
|
1273
|
-
punctuation: "#64748b",
|
|
1274
|
-
variable: "#1e1e2e"
|
|
1275
|
-
}
|
|
1239
|
+
colors: fancyFileCommons.LIGHT_COLORS
|
|
1276
1240
|
});
|
|
1277
|
-
|
|
1278
|
-
// src/themes/dark.ts
|
|
1279
1241
|
registerTheme({
|
|
1280
1242
|
name: "dark",
|
|
1281
1243
|
variant: "dark",
|
|
1282
|
-
colors:
|
|
1283
|
-
background: "#18181b",
|
|
1284
|
-
foreground: "#e4e4e7",
|
|
1285
|
-
gutterBackground: "#18181b",
|
|
1286
|
-
gutterForeground: "#52525b",
|
|
1287
|
-
gutterBorder: "#27272a",
|
|
1288
|
-
activeLineBackground: "#27272a",
|
|
1289
|
-
selectionBackground: "#1e3a5f",
|
|
1290
|
-
cursorColor: "#60a5fa",
|
|
1291
|
-
keyword: "#c084fc",
|
|
1292
|
-
string: "#34d399",
|
|
1293
|
-
comment: "#71717a",
|
|
1294
|
-
number: "#fbbf24",
|
|
1295
|
-
operator: "#22d3ee",
|
|
1296
|
-
function: "#60a5fa",
|
|
1297
|
-
type: "#fbbf24",
|
|
1298
|
-
tag: "#f87171",
|
|
1299
|
-
attribute: "#fbbf24",
|
|
1300
|
-
attributeValue: "#34d399",
|
|
1301
|
-
punctuation: "#a1a1aa",
|
|
1302
|
-
variable: "#e4e4e7"
|
|
1303
|
-
}
|
|
1244
|
+
colors: fancyFileCommons.DARK_COLORS
|
|
1304
1245
|
});
|
|
1305
1246
|
|
|
1306
|
-
// src/engine/highlight.ts
|
|
1307
|
-
function escapeHtml(text) {
|
|
1308
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1309
|
-
}
|
|
1310
|
-
var COLOR_RE = /^(#[0-9a-fA-F]{3,8}|(?:rgb|rgba|hsl|hsla)\([\d\s,.%/-]+\)|[a-zA-Z]+)$/;
|
|
1311
|
-
function safeColor(value) {
|
|
1312
|
-
return typeof value === "string" && COLOR_RE.test(value.trim()) ? value.trim() : "inherit";
|
|
1313
|
-
}
|
|
1314
|
-
function highlightCode(source, tokens, colors) {
|
|
1315
|
-
if (tokens.length === 0) return escapeHtml(source);
|
|
1316
|
-
const colorMap = {
|
|
1317
|
-
keyword: colors.keyword,
|
|
1318
|
-
string: colors.string,
|
|
1319
|
-
comment: colors.comment,
|
|
1320
|
-
number: colors.number,
|
|
1321
|
-
operator: colors.operator,
|
|
1322
|
-
function: colors.function,
|
|
1323
|
-
type: colors.type,
|
|
1324
|
-
tag: colors.tag,
|
|
1325
|
-
attribute: colors.attribute,
|
|
1326
|
-
attributeValue: colors.attributeValue,
|
|
1327
|
-
punctuation: colors.punctuation,
|
|
1328
|
-
variable: colors.variable,
|
|
1329
|
-
plain: colors.foreground
|
|
1330
|
-
};
|
|
1331
|
-
const parts = [];
|
|
1332
|
-
let pos = 0;
|
|
1333
|
-
for (const token of tokens) {
|
|
1334
|
-
if (token.start > pos) {
|
|
1335
|
-
parts.push(escapeHtml(source.slice(pos, token.start)));
|
|
1336
|
-
}
|
|
1337
|
-
const text = escapeHtml(source.slice(token.start, token.end));
|
|
1338
|
-
const color = safeColor(colorMap[token.type]);
|
|
1339
|
-
const style = token.type === "comment" ? `color:${color};font-style:italic` : `color:${color}`;
|
|
1340
|
-
parts.push(`<span style="${style}">${text}</span>`);
|
|
1341
|
-
pos = token.end;
|
|
1342
|
-
}
|
|
1343
|
-
if (pos < source.length) {
|
|
1344
|
-
parts.push(escapeHtml(source.slice(pos)));
|
|
1345
|
-
}
|
|
1346
|
-
return parts.join("");
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
1247
|
// src/engine/position.ts
|
|
1350
1248
|
function lineColumnToOffset(text, line, column = 1) {
|
|
1351
1249
|
const lines = text.split("\n");
|
|
@@ -1413,7 +1311,7 @@ function useEditorEngine({
|
|
|
1413
1311
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1414
1312
|
}
|
|
1415
1313
|
const tokens = tokenizer(value);
|
|
1416
|
-
return highlightCode(value, tokens, themeColors);
|
|
1314
|
+
return fancyFileCommons.highlightCode(value, tokens, themeColors);
|
|
1417
1315
|
}, [value, tokenizer, themeColors]);
|
|
1418
1316
|
const lineCount = react.useMemo(() => {
|
|
1419
1317
|
let count = 1;
|
|
@@ -1617,9 +1515,14 @@ function CodeEditorRoot({
|
|
|
1617
1515
|
minHeight,
|
|
1618
1516
|
maxHeight,
|
|
1619
1517
|
cursorLine,
|
|
1620
|
-
cursorColumn
|
|
1518
|
+
cursorColumn,
|
|
1519
|
+
diffBase
|
|
1621
1520
|
}) {
|
|
1622
1521
|
const [currentValue, setCurrentValue] = reactFancy.useControllableState(valueProp, defaultValue, onChange);
|
|
1522
|
+
const diffAnnotations = react.useMemo(
|
|
1523
|
+
() => diffBase == null ? null : fancyFileCommons.annotateLines(diffBase, currentValue),
|
|
1524
|
+
[diffBase, currentValue]
|
|
1525
|
+
);
|
|
1623
1526
|
const isDark = useDarkMode();
|
|
1624
1527
|
const resolvedTheme = theme === "auto" ? isDark ? "dark" : "light" : theme;
|
|
1625
1528
|
const [currentLanguage, setCurrentLanguageState] = react.useState(() => {
|
|
@@ -1698,11 +1601,17 @@ function CodeEditorRoot({
|
|
|
1698
1601
|
selectionLength,
|
|
1699
1602
|
textareaRef: engineReturn.textareaRef,
|
|
1700
1603
|
placeholder,
|
|
1604
|
+
diffStats: diffAnnotations ? {
|
|
1605
|
+
added: diffAnnotations.added,
|
|
1606
|
+
modified: diffAnnotations.modified,
|
|
1607
|
+
removed: diffAnnotations.removed
|
|
1608
|
+
} : null,
|
|
1609
|
+
_diffAnnotations: diffAnnotations,
|
|
1701
1610
|
_engineReturn: engineReturn,
|
|
1702
1611
|
_minHeight: minHeight,
|
|
1703
1612
|
_maxHeight: maxHeight
|
|
1704
1613
|
}),
|
|
1705
|
-
[engineReturn, currentValue, currentLanguage, setLanguage, resolvedTheme, readOnly, showLineNumbers, isWordWrap, tabSizeProp, cursorPosition, selectionLength, setCurrentValue, placeholder, minHeight, maxHeight]
|
|
1614
|
+
[engineReturn, currentValue, currentLanguage, setLanguage, resolvedTheme, readOnly, showLineNumbers, isWordWrap, tabSizeProp, cursorPosition, selectionLength, setCurrentValue, placeholder, diffAnnotations, minHeight, maxHeight]
|
|
1706
1615
|
);
|
|
1707
1616
|
return /* @__PURE__ */ jsxRuntime.jsx(CodeEditorContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1708
1617
|
"div",
|
|
@@ -1727,7 +1636,7 @@ var CodeEditor = Object.assign(CodeEditorRoot, {
|
|
|
1727
1636
|
});
|
|
1728
1637
|
|
|
1729
1638
|
// src/engine/markdown-render.ts
|
|
1730
|
-
function
|
|
1639
|
+
function escapeHtml(s) {
|
|
1731
1640
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1732
1641
|
}
|
|
1733
1642
|
function escapeAttr(s) {
|
|
@@ -1768,10 +1677,10 @@ function renderMarkdown(markdown) {
|
|
|
1768
1677
|
i++;
|
|
1769
1678
|
}
|
|
1770
1679
|
i++;
|
|
1771
|
-
out.push(`<pre><code${lang ? ` class="language-${escapeAttr(
|
|
1680
|
+
out.push(`<pre><code${lang ? ` class="language-${escapeAttr(escapeHtml(lang))}"` : ""}>${escapeHtml(body.join("\n"))}</code></pre>`);
|
|
1772
1681
|
continue;
|
|
1773
1682
|
}
|
|
1774
|
-
const esc =
|
|
1683
|
+
const esc = escapeHtml(line);
|
|
1775
1684
|
if (/^\s*$/.test(line)) {
|
|
1776
1685
|
flushPara();
|
|
1777
1686
|
closeList();
|
|
@@ -1878,48 +1787,10 @@ function MarkdownEditor({
|
|
|
1878
1787
|
);
|
|
1879
1788
|
}
|
|
1880
1789
|
MarkdownEditor.displayName = "MarkdownEditor";
|
|
1881
|
-
var EXT_LANGUAGE = {
|
|
1882
|
-
js: "javascript",
|
|
1883
|
-
mjs: "javascript",
|
|
1884
|
-
cjs: "javascript",
|
|
1885
|
-
jsx: "javascript",
|
|
1886
|
-
json: "javascript",
|
|
1887
|
-
// the JS tokenizer highlights JSON acceptably
|
|
1888
|
-
jsonc: "javascript",
|
|
1889
|
-
ts: "typescript",
|
|
1890
|
-
mts: "typescript",
|
|
1891
|
-
cts: "typescript",
|
|
1892
|
-
tsx: "typescript",
|
|
1893
|
-
html: "html",
|
|
1894
|
-
htm: "html",
|
|
1895
|
-
xml: "html",
|
|
1896
|
-
xhtml: "html",
|
|
1897
|
-
vue: "html",
|
|
1898
|
-
php: "php",
|
|
1899
|
-
phtml: "php",
|
|
1900
|
-
py: "python",
|
|
1901
|
-
pyw: "python",
|
|
1902
|
-
go: "go",
|
|
1903
|
-
md: "markdown",
|
|
1904
|
-
markdown: "markdown",
|
|
1905
|
-
mkd: "markdown",
|
|
1906
|
-
mdx: "markdown"
|
|
1907
|
-
};
|
|
1908
|
-
function basename(filename) {
|
|
1909
|
-
return filename.split(/[?#]/, 1)[0].split(/[\\/]/).pop() ?? "";
|
|
1910
|
-
}
|
|
1911
|
-
function languageFromFilename(filename) {
|
|
1912
|
-
if (!filename) return "plaintext";
|
|
1913
|
-
const seg = basename(filename);
|
|
1914
|
-
const dot = seg.lastIndexOf(".");
|
|
1915
|
-
if (dot <= 0) return "plaintext";
|
|
1916
|
-
const ext = seg.slice(dot + 1).toLowerCase();
|
|
1917
|
-
return EXT_LANGUAGE[ext] ?? "plaintext";
|
|
1918
|
-
}
|
|
1919
1790
|
function resolveFileKind({ filename, mime }) {
|
|
1920
1791
|
const media = reactFancy.resolveMediaType({ mime, src: filename });
|
|
1921
1792
|
if (media !== "unknown") return { kind: "media", mediaKind: media };
|
|
1922
|
-
return { kind: "text", language: languageFromFilename(filename) };
|
|
1793
|
+
return { kind: "text", language: fancyFileCommons.languageFromFilename(filename) };
|
|
1923
1794
|
}
|
|
1924
1795
|
function FileViewer({
|
|
1925
1796
|
filename,
|
|
@@ -1999,15 +1870,22 @@ function CodeEditorInputTag(props) {
|
|
|
1999
1870
|
return /* @__PURE__ */ jsxRuntime.jsx(reactFancy.InputTag, { adapter, ...props });
|
|
2000
1871
|
}
|
|
2001
1872
|
|
|
1873
|
+
Object.defineProperty(exports, "languageFromFilename", {
|
|
1874
|
+
enumerable: true,
|
|
1875
|
+
get: function () { return fancyFileCommons.languageFromFilename; }
|
|
1876
|
+
});
|
|
2002
1877
|
exports.CodeEditor = CodeEditor;
|
|
2003
1878
|
exports.CodeEditorInputTag = CodeEditorInputTag;
|
|
2004
1879
|
exports.FileViewer = FileViewer;
|
|
2005
1880
|
exports.MarkdownEditor = MarkdownEditor;
|
|
1881
|
+
exports.diffMarkColor = diffMarkColor;
|
|
1882
|
+
exports.diffRemovedColor = diffRemovedColor;
|
|
2006
1883
|
exports.getLanguage = getLanguage;
|
|
2007
1884
|
exports.getRegisteredLanguages = getRegisteredLanguages;
|
|
2008
1885
|
exports.getRegisteredThemes = getRegisteredThemes;
|
|
2009
1886
|
exports.getTheme = getTheme;
|
|
2010
|
-
exports.
|
|
1887
|
+
exports.gutterDiffMark = gutterDiffMark;
|
|
1888
|
+
exports.hasDeletedAtEnd = hasDeletedAtEnd;
|
|
2011
1889
|
exports.registerLanguage = registerLanguage;
|
|
2012
1890
|
exports.registerTheme = registerTheme;
|
|
2013
1891
|
exports.renderMarkdown = renderMarkdown;
|