seendiff 0.0.2
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 +124 -0
- package/bin/seendiff.js +9 -0
- package/package.json +38 -0
- package/src/cli.js +216 -0
- package/src/git.js +615 -0
- package/src/highlight.js +220 -0
- package/src/server.js +580 -0
- package/src/store.js +128 -0
- package/src/theme-base.css +482 -0
- package/src/theme.js +14 -0
- package/src/walkthrough.js +338 -0
- package/static/fonts/JetBrainsMono.woff2 +0 -0
- package/static/fonts/LICENCE-UbuntuSansMono.txt +96 -0
- package/static/fonts/LICENSE-JetBrainsMono.txt +93 -0
- package/static/fonts/README.md +29 -0
- package/static/fonts/UbuntuSansMono.woff2 +0 -0
- package/static/index.html +3175 -0
package/src/highlight.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import hljs from "highlight.js";
|
|
2
|
+
|
|
3
|
+
export const MAX_LINES = 10_000;
|
|
4
|
+
export const MAX_BYTES = 1_000_000;
|
|
5
|
+
export const MAX_MEAN_LINE_LEN = 500;
|
|
6
|
+
|
|
7
|
+
const EXT_LANG = {
|
|
8
|
+
".js": "javascript", ".mjs": "javascript", ".cjs": "javascript",
|
|
9
|
+
".jsx": "javascript", ".ts": "typescript", ".tsx": "typescript",
|
|
10
|
+
".py": "python", ".pyw": "python",
|
|
11
|
+
".rb": "ruby", ".go": "go", ".rs": "rust",
|
|
12
|
+
".java": "java", ".kt": "kotlin", ".kts": "kotlin",
|
|
13
|
+
".c": "c", ".h": "c", ".cc": "cpp", ".cpp": "cpp", ".cxx": "cpp",
|
|
14
|
+
".hpp": "cpp", ".hh": "cpp", ".cs": "csharp",
|
|
15
|
+
".php": "php", ".swift": "swift", ".m": "objectivec", ".mm": "objectivec",
|
|
16
|
+
".scala": "scala", ".sh": "bash", ".bash": "bash", ".zsh": "bash",
|
|
17
|
+
".ps1": "powershell", ".pl": "perl", ".pm": "perl", ".lua": "lua",
|
|
18
|
+
".r": "r", ".jl": "julia", ".ex": "elixir", ".exs": "elixir",
|
|
19
|
+
".erl": "erlang", ".hs": "haskell", ".clj": "clojure", ".cljs": "clojure",
|
|
20
|
+
".ml": "ocaml", ".fs": "fsharp", ".fsx": "fsharp", ".dart": "dart",
|
|
21
|
+
".sql": "sql", ".html": "xml", ".htm": "xml", ".xml": "xml",
|
|
22
|
+
".svg": "xml", ".css": "css", ".scss": "scss", ".less": "less",
|
|
23
|
+
".json": "json", ".jsonc": "json", ".yaml": "yaml", ".yml": "yaml",
|
|
24
|
+
".toml": "ini", ".ini": "ini", ".cfg": "ini", ".md": "markdown",
|
|
25
|
+
".markdown": "markdown", ".dockerfile": "dockerfile", ".vue": "xml",
|
|
26
|
+
".graphql": "graphql", ".gql": "graphql", ".proto": "protobuf",
|
|
27
|
+
".diff": "diff", ".patch": "diff", ".makefile": "makefile",
|
|
28
|
+
".cmake": "cmake", ".vim": "vim", ".zig": "zig", ".nim": "nim",
|
|
29
|
+
".sol": "solidity",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function extOf(filename) {
|
|
33
|
+
const base = filename.split("/").pop() || filename;
|
|
34
|
+
if (base === "Dockerfile") return ".dockerfile";
|
|
35
|
+
if (base === "Makefile" || base === "makefile") return ".makefile";
|
|
36
|
+
if (base === "CMakeLists.txt") return ".cmake";
|
|
37
|
+
const i = base.lastIndexOf(".");
|
|
38
|
+
return i >= 0 ? base.slice(i).toLowerCase() : "";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const SCOPE_MAP = [
|
|
42
|
+
[/^keyword$/, "k"],
|
|
43
|
+
[/^built_in$/, "nb"],
|
|
44
|
+
[/^type$/, "kt"],
|
|
45
|
+
[/^literal$/, "kc"],
|
|
46
|
+
[/^number$/, "m"],
|
|
47
|
+
[/^operator$/, "o"],
|
|
48
|
+
[/^punctuation$/, "p"],
|
|
49
|
+
[/^comment$/, "c1"],
|
|
50
|
+
[/^doctag$/, "c1"],
|
|
51
|
+
[/^meta/, "cp"],
|
|
52
|
+
[/^string$/, "s1"],
|
|
53
|
+
[/^regexp$/, "sr"],
|
|
54
|
+
[/^subst$/, "si"],
|
|
55
|
+
[/^symbol$/, "ss"],
|
|
56
|
+
[/^class$/, "nc"],
|
|
57
|
+
[/^title\.class/, "nc"],
|
|
58
|
+
[/^title\.function/, "nf"],
|
|
59
|
+
[/^title\.function_/, "nf"],
|
|
60
|
+
[/^function$/, "nf"],
|
|
61
|
+
[/^title$/, "nf"],
|
|
62
|
+
[/^params$/, "n"],
|
|
63
|
+
[/^variable$/, "nv"],
|
|
64
|
+
[/^variable\.language/, "nb"],
|
|
65
|
+
[/^variable\.constant/, "no"],
|
|
66
|
+
[/^property$/, "n"],
|
|
67
|
+
[/^attr$/, "na"],
|
|
68
|
+
[/^attribute$/, "na"],
|
|
69
|
+
[/^tag$/, "nt"],
|
|
70
|
+
[/^name$/, "nt"],
|
|
71
|
+
[/^selector-tag$/, "nt"],
|
|
72
|
+
[/^selector-id$/, "nd"],
|
|
73
|
+
[/^selector-class$/, "nc"],
|
|
74
|
+
[/^selector-attr$/, "na"],
|
|
75
|
+
[/^selector-pseudo$/, "nd"],
|
|
76
|
+
[/^template-tag$/, "k"],
|
|
77
|
+
[/^template-variable$/, "nv"],
|
|
78
|
+
[/^addition$/, "gi"],
|
|
79
|
+
[/^deletion$/, "gd"],
|
|
80
|
+
[/^emphasis$/, "ge"],
|
|
81
|
+
[/^strong$/, "gs"],
|
|
82
|
+
[/^formula$/, "n"],
|
|
83
|
+
[/^section$/, "gh"],
|
|
84
|
+
[/^bullet$/, "nt"],
|
|
85
|
+
[/^link$/, "nt"],
|
|
86
|
+
[/^quote$/, "c1"],
|
|
87
|
+
[/^code$/, "s1"],
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
function shortClass(scopeAttr) {
|
|
91
|
+
const parts = scopeAttr.split(/\s+/).filter(Boolean);
|
|
92
|
+
for (const part of parts) {
|
|
93
|
+
for (const [re, cls] of SCOPE_MAP) {
|
|
94
|
+
if (re.test(part)) return cls;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return "n";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function langFor(filename) {
|
|
101
|
+
const ext = extOf(filename);
|
|
102
|
+
const lang = EXT_LANG[ext];
|
|
103
|
+
if (!lang) return null;
|
|
104
|
+
return hljs.getLanguage(lang) ? lang : null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const NAMED_ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
|
|
108
|
+
const ENTITY_RE = /&(#x[0-9a-fA-F]+|#\d+|[a-zA-Z]+);/g;
|
|
109
|
+
function decodeEntities(s) {
|
|
110
|
+
return s.replace(ENTITY_RE, (m, body) => {
|
|
111
|
+
if (body[0] === "#") {
|
|
112
|
+
const code = body[1] === "x" || body[1] === "X"
|
|
113
|
+
? parseInt(body.slice(2), 16)
|
|
114
|
+
: parseInt(body.slice(1), 10);
|
|
115
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : m;
|
|
116
|
+
}
|
|
117
|
+
return NAMED_ENTITIES[body] ?? m;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function parseHljsHtml(html) {
|
|
122
|
+
const tokens = [];
|
|
123
|
+
const classStack = [];
|
|
124
|
+
let i = 0;
|
|
125
|
+
const n = html.length;
|
|
126
|
+
let buf = "";
|
|
127
|
+
|
|
128
|
+
function flush() {
|
|
129
|
+
if (buf) {
|
|
130
|
+
const cls = classStack.length ? shortClass(classStack[classStack.length - 1]) : "";
|
|
131
|
+
tokens.push([cls, decodeEntities(buf)]);
|
|
132
|
+
buf = "";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
while (i < n) {
|
|
137
|
+
if (html.startsWith("<span class=\"", i)) {
|
|
138
|
+
flush();
|
|
139
|
+
const start = i + '<span class="'.length;
|
|
140
|
+
const end = html.indexOf('"', start);
|
|
141
|
+
classStack.push(html.slice(start, end).replace(/^hljs-/, ""));
|
|
142
|
+
i = html.indexOf(">", end) + 1;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (html.startsWith("</span>", i)) {
|
|
146
|
+
flush();
|
|
147
|
+
classStack.pop();
|
|
148
|
+
i += "</span>".length;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
buf += html[i];
|
|
152
|
+
i++;
|
|
153
|
+
}
|
|
154
|
+
flush();
|
|
155
|
+
return tokens;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function tokenizeLines(text, lang, nLines) {
|
|
159
|
+
let html;
|
|
160
|
+
try {
|
|
161
|
+
html = hljs.highlight(text, { language: lang, ignoreIllegals: true }).value;
|
|
162
|
+
} catch {
|
|
163
|
+
html = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
164
|
+
}
|
|
165
|
+
const flat = parseHljsHtml(html);
|
|
166
|
+
const lines = [];
|
|
167
|
+
let cur = [];
|
|
168
|
+
for (const [cls, value] of flat) {
|
|
169
|
+
const parts = value.split("\n");
|
|
170
|
+
parts.forEach((piece, i) => {
|
|
171
|
+
if (i) {
|
|
172
|
+
lines.push(cur);
|
|
173
|
+
cur = [];
|
|
174
|
+
}
|
|
175
|
+
if (piece) cur.push([cls, piece]);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
lines.push(cur);
|
|
179
|
+
while (lines.length < nLines) lines.push([]);
|
|
180
|
+
return lines.slice(0, nLines);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function guard(rows) {
|
|
184
|
+
if (rows.length > MAX_LINES) return `file too large (${rows.length} lines)`;
|
|
185
|
+
const total = rows.reduce((n, r) => n + r.text.length, 0);
|
|
186
|
+
if (total > MAX_BYTES) return `file too large (${Math.floor(total / 1024)} kB)`;
|
|
187
|
+
if (rows.length && total / rows.length > MAX_MEAN_LINE_LEN) return "minified content (very long lines)";
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function highlightRows(rows, filename) {
|
|
192
|
+
if (guard(rows) !== null) return null;
|
|
193
|
+
const lang = langFor(filename);
|
|
194
|
+
if (!lang) return null;
|
|
195
|
+
try {
|
|
196
|
+
const newRows = rows.filter((r) => r.type !== "del");
|
|
197
|
+
const oldRows = rows.filter((r) => r.type !== "add");
|
|
198
|
+
const newToks = tokenizeLines(newRows.map((r) => r.text).join("\n"), lang, newRows.length);
|
|
199
|
+
const oldToks = tokenizeLines(oldRows.map((r) => r.text).join("\n"), lang, oldRows.length);
|
|
200
|
+
const out = [];
|
|
201
|
+
let ni = 0;
|
|
202
|
+
let oi = 0;
|
|
203
|
+
for (const r of rows) {
|
|
204
|
+
if (r.type === "del") {
|
|
205
|
+
out.push(oldToks[oi]);
|
|
206
|
+
oi++;
|
|
207
|
+
} else if (r.type === "add") {
|
|
208
|
+
out.push(newToks[ni]);
|
|
209
|
+
ni++;
|
|
210
|
+
} else {
|
|
211
|
+
out.push(newToks[ni]);
|
|
212
|
+
ni++;
|
|
213
|
+
oi++;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
} catch {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|