@joinezco/codeblock 0.0.8
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/LICENSE +661 -0
- package/dist/assets/clike-C8IJ2oj_.js +1 -0
- package/dist/assets/cmake-BQqOBYOt.js +1 -0
- package/dist/assets/dockerfile-C_y-rIpk.js +1 -0
- package/dist/assets/fs.worker-BwEqZcql.ts +109 -0
- package/dist/assets/go-CTD25R5P.js +1 -0
- package/dist/assets/haskell-BWDZoCOh.js +1 -0
- package/dist/assets/index-9HdhmM_Y.js +1 -0
- package/dist/assets/index-C-QhPFHP.js +3 -0
- package/dist/assets/index-C3BnE2cG.js +222 -0
- package/dist/assets/index-CGx5MZO7.js +6 -0
- package/dist/assets/index-CIuq3uTk.js +1 -0
- package/dist/assets/index-CXFONXS8.js +1 -0
- package/dist/assets/index-D5Z27j1C.js +1 -0
- package/dist/assets/index-DWOBdRjn.js +1 -0
- package/dist/assets/index-Dvu-FFzd.js +1 -0
- package/dist/assets/index-Dx_VuNNd.js +1 -0
- package/dist/assets/index-I0dlv-r3.js +1 -0
- package/dist/assets/index-MGle_v2x.js +1 -0
- package/dist/assets/index-N-GE7HTU.js +1 -0
- package/dist/assets/index-aEsF5o-7.js +2 -0
- package/dist/assets/index-as7ELo0J.js +1 -0
- package/dist/assets/index-gUUzXNuP.js +1 -0
- package/dist/assets/index-pGm0qkrJ.js +13 -0
- package/dist/assets/javascript.worker-C1zGArKk.js +527 -0
- package/dist/assets/lua-BgMRiT3U.js +1 -0
- package/dist/assets/perl-CdXCOZ3F.js +1 -0
- package/dist/assets/process-Dw9K5EnD.js +1357 -0
- package/dist/assets/properties-C78fOPTZ.js +1 -0
- package/dist/assets/ruby-B2Rjki9n.js +1 -0
- package/dist/assets/shell-CjFT_Tl9.js +1 -0
- package/dist/assets/swift-BzpIVaGY.js +1 -0
- package/dist/assets/toml-BXUEaScT.js +1 -0
- package/dist/assets/vb-CmGdzxic.js +1 -0
- package/dist/e2e/example.spec.d.ts +5 -0
- package/dist/e2e/example.spec.js +44 -0
- package/dist/editor.d.ts +53 -0
- package/dist/editor.js +248 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.html +16 -0
- package/dist/index.js +6 -0
- package/dist/lsps/index.d.ts +96 -0
- package/dist/lsps/index.js +198 -0
- package/dist/lsps/typescript.d.ts +55 -0
- package/dist/lsps/typescript.js +48 -0
- package/dist/panels/toolbar.d.ts +20 -0
- package/dist/panels/toolbar.js +453 -0
- package/dist/panels/toolbar.test.d.ts +1 -0
- package/dist/panels/toolbar.test.js +146 -0
- package/dist/resources/config.json +13 -0
- package/dist/rpc/serde.d.ts +11 -0
- package/dist/rpc/serde.js +49 -0
- package/dist/rpc/transport.d.ts +11 -0
- package/dist/rpc/transport.js +38 -0
- package/dist/snapshot.bin +0 -0
- package/dist/styles.css +7 -0
- package/dist/themes/index.d.ts +1 -0
- package/dist/themes/index.js +169 -0
- package/dist/themes/util.d.ts +24 -0
- package/dist/themes/util.js +63 -0
- package/dist/themes/vscode.d.ts +6 -0
- package/dist/themes/vscode.js +187 -0
- package/dist/types.d.ts +64 -0
- package/dist/types.js +1 -0
- package/dist/utils/fs.d.ts +29 -0
- package/dist/utils/fs.js +310 -0
- package/dist/utils/indent.d.ts +1 -0
- package/dist/utils/indent.js +38 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/lsp.d.ts +26 -0
- package/dist/utils/lsp.js +74 -0
- package/dist/utils/search.d.ts +30 -0
- package/dist/utils/search.js +68 -0
- package/dist/utils/snapshot.d.ts +60 -0
- package/dist/utils/snapshot.js +299 -0
- package/dist/workers/fs.worker.d.ts +11 -0
- package/dist/workers/fs.worker.js +93 -0
- package/dist/workers/javascript.worker.d.ts +1 -0
- package/dist/workers/javascript.worker.js +20 -0
- package/package.json +95 -0
package/dist/utils/fs.js
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import * as Comlink from "comlink";
|
|
2
|
+
import { watchOptionsTransferHandler, asyncGeneratorTransferHandler } from "../rpc/serde";
|
|
3
|
+
import { FileType } from '@volar/language-service';
|
|
4
|
+
import { constants } from "@joinezco/jswasi";
|
|
5
|
+
Comlink.transferHandlers.set("asyncGenerator", asyncGeneratorTransferHandler);
|
|
6
|
+
Comlink.transferHandlers.set("watchOptions", watchOptionsTransferHandler);
|
|
7
|
+
export var Vfs;
|
|
8
|
+
(function (Vfs) {
|
|
9
|
+
Vfs.fromJswasiFs = async (jswasiFs) => {
|
|
10
|
+
// Map WASI filetype to @volar/language-service FileType
|
|
11
|
+
const toVolarFileType = (filetype) => {
|
|
12
|
+
// WASI preview1 common values: 3 = directory, 4 = regular file, 7 = symlink
|
|
13
|
+
switch (filetype) {
|
|
14
|
+
case 3: return FileType.Directory;
|
|
15
|
+
case 7: return FileType.SymbolicLink;
|
|
16
|
+
default: return FileType.File;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
// Normalize to absolute path for TopLevelFs
|
|
20
|
+
const ensureAbs = (p) => (p && p.startsWith("/")) ? p : `/${p ?? ""}`;
|
|
21
|
+
// Pull constants if available (fall back to literals when missing)
|
|
22
|
+
const WASI_ESUCCESS = constants.WASI_ESUCCESS ?? 0;
|
|
23
|
+
const WASI_EEXIST = constants.WASI_EEXIST ?? 20;
|
|
24
|
+
const WASI_O_TRUNC = constants.WASI_O_TRUNC ?? 0x00000010;
|
|
25
|
+
const WASI_O_CREAT = constants.WASI_O_CREAT ?? 0x00000001;
|
|
26
|
+
const WASI_O_DIRECTORY = constants.WASI_O_DIRECTORY ?? 0x00020000;
|
|
27
|
+
return {
|
|
28
|
+
async readFile(path) {
|
|
29
|
+
const abs = ensureAbs(path);
|
|
30
|
+
const { desc, err } = await jswasiFs.open(abs);
|
|
31
|
+
if (err !== WASI_ESUCCESS)
|
|
32
|
+
throw new Error(`readFile open failed (${err}) for ${abs}`);
|
|
33
|
+
const { content, err: readErr } = await desc.read_str();
|
|
34
|
+
if (readErr !== WASI_ESUCCESS)
|
|
35
|
+
throw new Error(`readFile read_str failed (${readErr}) for ${abs}`);
|
|
36
|
+
desc.close(); // Close after reading
|
|
37
|
+
return content;
|
|
38
|
+
},
|
|
39
|
+
async writeFile(path, data) {
|
|
40
|
+
const abs = ensureAbs(path);
|
|
41
|
+
const { desc, err } = await jswasiFs.open(abs, 0, WASI_O_CREAT | WASI_O_TRUNC);
|
|
42
|
+
if (err !== WASI_ESUCCESS)
|
|
43
|
+
throw new Error(`writeFile open failed (${err}) for ${abs}`);
|
|
44
|
+
const encoder = new TextEncoder();
|
|
45
|
+
const buf = encoder.encode(data);
|
|
46
|
+
const { err: writeErr } = await desc.pwrite(buf.buffer, 0n);
|
|
47
|
+
desc.close(); // Close after writing
|
|
48
|
+
if (writeErr !== WASI_ESUCCESS)
|
|
49
|
+
throw new Error(`writeFile pwrite failed (${writeErr}) for ${abs}`);
|
|
50
|
+
},
|
|
51
|
+
async *watch(_path, { signal }) {
|
|
52
|
+
return jswasiFs.watch(_path, { signal });
|
|
53
|
+
},
|
|
54
|
+
async mkdir(path, options) {
|
|
55
|
+
const abs = ensureAbs(path);
|
|
56
|
+
if (options?.recursive) {
|
|
57
|
+
const parts = abs.split("/").filter(Boolean);
|
|
58
|
+
let cur = "/";
|
|
59
|
+
for (const part of parts) {
|
|
60
|
+
cur = cur === "/" ? `/${part}` : `${cur}/${part}`;
|
|
61
|
+
console.log('creating', { cur, abs });
|
|
62
|
+
const exists = await this.exists(cur);
|
|
63
|
+
if (exists)
|
|
64
|
+
continue;
|
|
65
|
+
const res = await jswasiFs.createDir(cur);
|
|
66
|
+
if (res !== WASI_ESUCCESS && res !== WASI_EEXIST) {
|
|
67
|
+
throw new Error(`mkdir recursive failed (${res}) at ${cur}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const res = await jswasiFs.createDir(abs);
|
|
73
|
+
if (res !== WASI_ESUCCESS) {
|
|
74
|
+
throw new Error(`mkdir failed (${res}) for ${abs}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
async readDir(path) {
|
|
79
|
+
const abs = ensureAbs(path);
|
|
80
|
+
const { desc, err } = await jswasiFs.open(abs, 0, WASI_O_DIRECTORY);
|
|
81
|
+
if (err !== WASI_ESUCCESS)
|
|
82
|
+
throw new Error(`readDir open failed (${err}) for ${abs}`);
|
|
83
|
+
const { err: rerr, dirents } = await desc.readdir(true);
|
|
84
|
+
if (rerr !== WASI_ESUCCESS)
|
|
85
|
+
throw new Error(`readDir readdir failed (${rerr}) for ${abs}`);
|
|
86
|
+
return dirents.map((d) => [d.name, toVolarFileType(d.d_type)]);
|
|
87
|
+
},
|
|
88
|
+
async exists(path) {
|
|
89
|
+
const abs = ensureAbs(path);
|
|
90
|
+
const { desc, err } = await jswasiFs.open(abs);
|
|
91
|
+
if (err !== WASI_ESUCCESS)
|
|
92
|
+
return false;
|
|
93
|
+
const stat = await desc.getFilestat();
|
|
94
|
+
desc.close(); // Close after getting file status
|
|
95
|
+
return stat.err === WASI_ESUCCESS;
|
|
96
|
+
},
|
|
97
|
+
async stat(path) {
|
|
98
|
+
const abs = ensureAbs(path);
|
|
99
|
+
const { desc, err } = await jswasiFs.open(abs);
|
|
100
|
+
if (err !== WASI_ESUCCESS)
|
|
101
|
+
return null;
|
|
102
|
+
const res = await desc.getFilestat();
|
|
103
|
+
desc.close(); // Close after getting file status
|
|
104
|
+
if (res.err !== WASI_ESUCCESS)
|
|
105
|
+
return null;
|
|
106
|
+
const filestat = res.filestat;
|
|
107
|
+
// filestat times are typically in ns; convert to ms for Date
|
|
108
|
+
const nsToDate = (ns) => new Date(Number(ns / 1000000n));
|
|
109
|
+
return {
|
|
110
|
+
name: abs,
|
|
111
|
+
atime: nsToDate(filestat.atim),
|
|
112
|
+
mtime: nsToDate(filestat.mtim),
|
|
113
|
+
ctime: nsToDate(filestat.ctim),
|
|
114
|
+
size: Number(filestat.size),
|
|
115
|
+
type: toVolarFileType(filestat.filetype),
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
// TODO: this is incorrect, fs is a Comlink proxy
|
|
121
|
+
Vfs.fromMemfs = (fs) => {
|
|
122
|
+
return {
|
|
123
|
+
async readFile(path) {
|
|
124
|
+
return fs.promises.readFile(path, { encoding: "utf-8" });
|
|
125
|
+
},
|
|
126
|
+
async writeFile(path, data) {
|
|
127
|
+
await fs.promises.writeFile(path, data);
|
|
128
|
+
},
|
|
129
|
+
async *watch(path, { signal }) {
|
|
130
|
+
for await (const e of await fs.promises.watch(path, { signal, encoding: "utf-8", recursive: true })) {
|
|
131
|
+
yield e;
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
async mkdir(path, options) {
|
|
135
|
+
await fs.promises.mkdir(path, options);
|
|
136
|
+
},
|
|
137
|
+
async readDir(path) {
|
|
138
|
+
const files = await fs.readdirSync(path, { withFileTypes: true, encoding: "utf-8" });
|
|
139
|
+
// @ts-expect-error
|
|
140
|
+
return files.map((ent) => {
|
|
141
|
+
let type = FileType.File;
|
|
142
|
+
switch (ent.mode & 0o170000) {
|
|
143
|
+
case 0o040000:
|
|
144
|
+
type = FileType.Directory;
|
|
145
|
+
break;
|
|
146
|
+
case 0o120000:
|
|
147
|
+
type = FileType.SymbolicLink;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
return [ent.name, type];
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
async exists(path) {
|
|
154
|
+
return fs.existsSync(path);
|
|
155
|
+
},
|
|
156
|
+
async stat(path) {
|
|
157
|
+
try {
|
|
158
|
+
const stat = await fs.promises.stat(path);
|
|
159
|
+
let type = FileType.File;
|
|
160
|
+
switch (stat.mode & 0o170000) {
|
|
161
|
+
case 0o040000:
|
|
162
|
+
type = FileType.Directory;
|
|
163
|
+
break;
|
|
164
|
+
case 0o120000:
|
|
165
|
+
type = FileType.SymbolicLink;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
// console.debug(`Stat success "${path}"`);
|
|
169
|
+
return {
|
|
170
|
+
name: path,
|
|
171
|
+
atime: stat.atime,
|
|
172
|
+
mtime: stat.mtime,
|
|
173
|
+
ctime: stat.ctime,
|
|
174
|
+
size: stat.size,
|
|
175
|
+
type,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
Vfs.fromNodelike = (fs) => {
|
|
185
|
+
return {
|
|
186
|
+
async readFile(path) {
|
|
187
|
+
return fs.readFile(path, { encoding: "utf-8" });
|
|
188
|
+
},
|
|
189
|
+
async writeFile(path, data) {
|
|
190
|
+
await fs.writeFile(path, data);
|
|
191
|
+
},
|
|
192
|
+
async *watch(path, { signal }) {
|
|
193
|
+
for await (const e of await fs.watch(path, { signal, encoding: "utf-8", recursive: true })) {
|
|
194
|
+
yield e;
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
async mkdir(path, options) {
|
|
198
|
+
await fs.mkdir(path, options);
|
|
199
|
+
},
|
|
200
|
+
async readDir(path) {
|
|
201
|
+
const files = await fs.readdir(path, { withFileTypes: true, encoding: "utf-8" });
|
|
202
|
+
return files.map((ent) => {
|
|
203
|
+
let type = FileType.File;
|
|
204
|
+
switch (ent.stats.mode & 0o170000) {
|
|
205
|
+
case 0o040000:
|
|
206
|
+
type = FileType.Directory;
|
|
207
|
+
break;
|
|
208
|
+
case 0o120000:
|
|
209
|
+
type = FileType.SymbolicLink;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
return [ent.path, type];
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
async exists(path) {
|
|
216
|
+
try {
|
|
217
|
+
await fs.access(path);
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
async stat(path) {
|
|
225
|
+
try {
|
|
226
|
+
const stat = await fs.stat(path);
|
|
227
|
+
let type = FileType.File;
|
|
228
|
+
switch (stat.mode & 0o170000) {
|
|
229
|
+
case 0o040000:
|
|
230
|
+
type = FileType.Directory;
|
|
231
|
+
break;
|
|
232
|
+
case 0o120000:
|
|
233
|
+
type = FileType.SymbolicLink;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
// console.debug(`Stat success "${path}"`);
|
|
237
|
+
return {
|
|
238
|
+
name: path,
|
|
239
|
+
atime: stat.atime,
|
|
240
|
+
mtime: stat.mtime,
|
|
241
|
+
ctime: stat.ctime,
|
|
242
|
+
size: stat.size,
|
|
243
|
+
type,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Create a filesystem worker with optional snapshot data.
|
|
254
|
+
*
|
|
255
|
+
* @param bufferOrUrl - Either a snapshot buffer or URL to a snapshot file.
|
|
256
|
+
* If a URL is provided, it will be loaded directly in the worker
|
|
257
|
+
* for better performance with large files.
|
|
258
|
+
*/
|
|
259
|
+
Vfs.worker = async (bufferOrUrl) => {
|
|
260
|
+
const url = new URL('../workers/fs.worker.js', import.meta.url);
|
|
261
|
+
const worker = new SharedWorker(url, { type: 'module' });
|
|
262
|
+
worker.port.start();
|
|
263
|
+
const proxy = Comlink.wrap(worker.port);
|
|
264
|
+
let fs;
|
|
265
|
+
if (!bufferOrUrl) {
|
|
266
|
+
// No buffer or URL provided - create empty filesystem
|
|
267
|
+
({ fs } = await proxy.mount({ mountPoint: '/' }));
|
|
268
|
+
}
|
|
269
|
+
else if (typeof bufferOrUrl === 'string') {
|
|
270
|
+
// URL provided - use optimized mountFromUrl for better performance
|
|
271
|
+
({ fs } = await proxy.mountFromUrl({
|
|
272
|
+
url: bufferOrUrl,
|
|
273
|
+
mountPoint: '/'
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
// Buffer provided - use traditional mount method
|
|
278
|
+
({ fs } = await proxy.mount(Comlink.transfer({ buffer: bufferOrUrl, mountPoint: "/" }, [bufferOrUrl])));
|
|
279
|
+
}
|
|
280
|
+
return Comlink.proxy(Vfs.fromMemfs(fs));
|
|
281
|
+
};
|
|
282
|
+
async function* walk(fs, path) {
|
|
283
|
+
const files = await fs.readDir(path);
|
|
284
|
+
for (const [filename, type] of files) {
|
|
285
|
+
const joined = `${path === '/' ? '' : path}/${filename}`;
|
|
286
|
+
if (type === FileType.Directory) {
|
|
287
|
+
yield* walk(fs, joined);
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
yield joined;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
Vfs.walk = walk;
|
|
295
|
+
})(Vfs || (Vfs = {}));
|
|
296
|
+
export class VolarFs {
|
|
297
|
+
#fs;
|
|
298
|
+
constructor(fs) {
|
|
299
|
+
this.#fs = fs;
|
|
300
|
+
}
|
|
301
|
+
async stat(uri) {
|
|
302
|
+
return this.#fs.stat(uri.path);
|
|
303
|
+
}
|
|
304
|
+
async readDirectory(uri) {
|
|
305
|
+
return this.#fs.readDir(uri.path);
|
|
306
|
+
}
|
|
307
|
+
async readFile(uri) {
|
|
308
|
+
return this.#fs.readFile(uri.path);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const detectIndentationUnit: (content: string, limit?: number) => string | null;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const detectIndentationUnit = (content, limit = 1024) => {
|
|
2
|
+
const lines = content.split(/\r?\n/, limit).filter(line => line.trim().length > 0);
|
|
3
|
+
const indentCounts = {};
|
|
4
|
+
let indentation = new Set();
|
|
5
|
+
let tabLineCount = 0;
|
|
6
|
+
let spaceLineCount = 0;
|
|
7
|
+
let unit = '\t';
|
|
8
|
+
for (const line of lines) {
|
|
9
|
+
const match = line.match(/^(\s+)/);
|
|
10
|
+
if (!match)
|
|
11
|
+
continue;
|
|
12
|
+
const indent = match[0];
|
|
13
|
+
const isTab = indent.includes('\t');
|
|
14
|
+
if (isTab) {
|
|
15
|
+
tabLineCount++;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
spaceLineCount++;
|
|
19
|
+
}
|
|
20
|
+
indentation.add(indent);
|
|
21
|
+
indentCounts[indent] = (indentCounts[indent] || 0) + 1;
|
|
22
|
+
}
|
|
23
|
+
if (Object.keys(indentCounts).length === 0) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
unit = tabLineCount > spaceLineCount ? '\t' : ' ';
|
|
27
|
+
const counts = Array.from(indentation)
|
|
28
|
+
.filter(indent => indent.startsWith(unit))
|
|
29
|
+
.map(indent => indent.length);
|
|
30
|
+
if (counts.length > 0) {
|
|
31
|
+
const gcd = (...numbers) => {
|
|
32
|
+
return numbers.reduce((a, b) => (b === 0 ? a : gcd(b, a % b)));
|
|
33
|
+
};
|
|
34
|
+
const commonUnit = gcd(...counts);
|
|
35
|
+
return unit.repeat(commonUnit);
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { VfsInterface } from "../types";
|
|
2
|
+
import { LanguageServerClient } from "@marimo-team/codemirror-languageserver";
|
|
3
|
+
import { Extension } from "@codemirror/state";
|
|
4
|
+
import { LanguageServer } from "@volar/language-server";
|
|
5
|
+
import { EditorView } from "@codemirror/view";
|
|
6
|
+
export type LSPClientExtension = {
|
|
7
|
+
client: LanguageServerClient;
|
|
8
|
+
} & Extension;
|
|
9
|
+
export type ClientOptions = {
|
|
10
|
+
view: EditorView;
|
|
11
|
+
language: string;
|
|
12
|
+
path: string;
|
|
13
|
+
fs: VfsInterface;
|
|
14
|
+
};
|
|
15
|
+
export declare const languageServerFactory: Map<string, (args: {
|
|
16
|
+
fs: VfsInterface;
|
|
17
|
+
}) => Promise<{
|
|
18
|
+
server: LanguageServer;
|
|
19
|
+
}>>;
|
|
20
|
+
export declare const lspWorkers: Map<string, SharedWorker>;
|
|
21
|
+
export declare namespace LSP {
|
|
22
|
+
function worker(language: string, fs: VfsInterface): Promise<{
|
|
23
|
+
worker: SharedWorker;
|
|
24
|
+
}>;
|
|
25
|
+
function client({ fs, language, path, view }: ClientOptions): Promise<LSPClientExtension>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as Comlink from 'comlink';
|
|
2
|
+
import { LanguageServerClient, languageServerWithClient } from "@marimo-team/codemirror-languageserver";
|
|
3
|
+
import MessagePortTransport from "../rpc/transport";
|
|
4
|
+
import { HighlightStyle } from "@codemirror/language";
|
|
5
|
+
import { languageSupportCompartment, renderMarkdownCode } from "../editor";
|
|
6
|
+
import markdownit from 'markdown-it';
|
|
7
|
+
import { vscodeLightDark } from "../themes/vscode";
|
|
8
|
+
const clients = new Map();
|
|
9
|
+
// TODO: better fix for this reference sticking around to prevent Comlink from releasing the port
|
|
10
|
+
export const languageServerFactory = new Map();
|
|
11
|
+
export const lspWorkers = new Map();
|
|
12
|
+
export var LSP;
|
|
13
|
+
(function (LSP) {
|
|
14
|
+
async function worker(language, fs) {
|
|
15
|
+
let factory, worker;
|
|
16
|
+
console.debug('language', { language });
|
|
17
|
+
switch (language) {
|
|
18
|
+
case 'javascript':
|
|
19
|
+
case 'typescript':
|
|
20
|
+
factory = languageServerFactory.get('javascript');
|
|
21
|
+
worker = lspWorkers.get('javascript');
|
|
22
|
+
console.debug('got worker', { worker, factory });
|
|
23
|
+
if (!factory) {
|
|
24
|
+
worker = new SharedWorker(new URL('../workers/javascript.worker.js', import.meta.url), { type: 'module' });
|
|
25
|
+
worker.port.start();
|
|
26
|
+
lspWorkers.set('javascript', worker);
|
|
27
|
+
const { createLanguageServer } = Comlink.wrap(worker.port);
|
|
28
|
+
factory = createLanguageServer;
|
|
29
|
+
languageServerFactory.set('javascript', factory);
|
|
30
|
+
}
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
await factory?.(Comlink.proxy({ fs }));
|
|
34
|
+
return { worker };
|
|
35
|
+
}
|
|
36
|
+
LSP.worker = worker;
|
|
37
|
+
async function client({ fs, language, path, view }) {
|
|
38
|
+
let client = clients.get(language);
|
|
39
|
+
let clientExtension;
|
|
40
|
+
const uri = `file:///${path}`;
|
|
41
|
+
if (!client) {
|
|
42
|
+
const { worker } = await LSP.worker(language, fs);
|
|
43
|
+
if (!worker)
|
|
44
|
+
return null;
|
|
45
|
+
console.debug('got worker', { worker });
|
|
46
|
+
client = new LanguageServerClient({
|
|
47
|
+
transport: new MessagePortTransport(worker.port),
|
|
48
|
+
rootUri: 'file:///',
|
|
49
|
+
workspaceFolders: [{ name: 'workspace', uri: 'file:///' }]
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
clients.set(language, client);
|
|
53
|
+
clientExtension = { client, extension: [] };
|
|
54
|
+
clientExtension.extension = languageServerWithClient({
|
|
55
|
+
client: clientExtension.client,
|
|
56
|
+
documentUri: uri,
|
|
57
|
+
languageId: language,
|
|
58
|
+
allowHTMLContent: true,
|
|
59
|
+
markdownRenderer(markdown) {
|
|
60
|
+
const support = languageSupportCompartment.get(view.state);
|
|
61
|
+
const highlighter = vscodeLightDark[1].find(item => item.value instanceof HighlightStyle)?.value;
|
|
62
|
+
const parser = support.language?.parser;
|
|
63
|
+
const md = markdownit({
|
|
64
|
+
highlight: (str) => {
|
|
65
|
+
return renderMarkdownCode(str, parser, highlighter);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return md.render(markdown);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
return clientExtension;
|
|
72
|
+
}
|
|
73
|
+
LSP.client = client;
|
|
74
|
+
})(LSP || (LSP = {}));
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { VfsInterface } from "../types";
|
|
2
|
+
import MiniSearch, { Options, SearchResult } from 'minisearch';
|
|
3
|
+
export declare const validFields: readonly ["path", "basename", "dirname", "extension"];
|
|
4
|
+
export type IndexFields = typeof validFields[number][];
|
|
5
|
+
export declare const defaultFields: IndexFields;
|
|
6
|
+
export declare const defaultFilter: (path: string) => boolean;
|
|
7
|
+
export type SearchIndexOptions = Options & {
|
|
8
|
+
filter?: (path: string) => boolean;
|
|
9
|
+
};
|
|
10
|
+
export type SearchHighlights = {
|
|
11
|
+
fields: Record<string, [number, number][]>;
|
|
12
|
+
};
|
|
13
|
+
export type HighlightedSearch = SearchResult & {
|
|
14
|
+
highlights: SearchHighlights;
|
|
15
|
+
};
|
|
16
|
+
export declare class SearchIndex {
|
|
17
|
+
index: MiniSearch;
|
|
18
|
+
constructor(index: MiniSearch);
|
|
19
|
+
search(...params: Parameters<MiniSearch['search']>): HighlightedSearch[];
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @param results
|
|
23
|
+
* @returns ranges of found term matched by each field
|
|
24
|
+
*/
|
|
25
|
+
highlight(_results: SearchResult[]): SearchHighlights[];
|
|
26
|
+
save(fs: VfsInterface, path: string): Promise<this>;
|
|
27
|
+
static from(fs: string, fields: IndexFields): SearchIndex;
|
|
28
|
+
static get(fs: VfsInterface, path: string, fields?: IndexFields): Promise<SearchIndex>;
|
|
29
|
+
static build(fs: VfsInterface, { filter, ...rest }: SearchIndexOptions): Promise<SearchIndex>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import MiniSearch from 'minisearch';
|
|
2
|
+
import { Vfs } from "./fs";
|
|
3
|
+
export const validFields = ['path', 'basename', 'dirname', 'extension'];
|
|
4
|
+
export const defaultFields = ['path', 'basename', 'dirname', 'extension'];
|
|
5
|
+
export const defaultFilter = (path) => {
|
|
6
|
+
return !path.endsWith('.crswap');
|
|
7
|
+
};
|
|
8
|
+
export class SearchIndex {
|
|
9
|
+
index;
|
|
10
|
+
constructor(index) {
|
|
11
|
+
this.index = index;
|
|
12
|
+
}
|
|
13
|
+
search(...params) {
|
|
14
|
+
const results = this.index.search(...params);
|
|
15
|
+
const highlights = this.highlight(results);
|
|
16
|
+
return results.map((result, i) => ({
|
|
17
|
+
...result,
|
|
18
|
+
highlights: highlights[i]
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
*
|
|
23
|
+
* @param results
|
|
24
|
+
* @returns ranges of found term matched by each field
|
|
25
|
+
*/
|
|
26
|
+
highlight(_results) {
|
|
27
|
+
// TODO: implement
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
async save(fs, path) {
|
|
31
|
+
try {
|
|
32
|
+
// Extract directory from the file path and create it
|
|
33
|
+
const dir = path.substring(0, path.lastIndexOf('/'));
|
|
34
|
+
if (dir) {
|
|
35
|
+
await fs.mkdir(dir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
await fs.writeFile(path, JSON.stringify(this.index));
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error('Failed to save search index:', error);
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
static from(fs, fields) {
|
|
47
|
+
const index = MiniSearch.loadJSON(fs, { fields, idField: 'path' });
|
|
48
|
+
return new SearchIndex(index);
|
|
49
|
+
}
|
|
50
|
+
static async get(fs, path, fields = defaultFields) {
|
|
51
|
+
const index = await fs.exists(path) ? await fs.readFile(path) : null;
|
|
52
|
+
return index ?
|
|
53
|
+
SearchIndex.from(index, fields) :
|
|
54
|
+
SearchIndex.build(fs, { fields, idField: 'path' }).then(index => index.save(fs, path));
|
|
55
|
+
}
|
|
56
|
+
static async build(fs, { filter = defaultFilter, ...rest }) {
|
|
57
|
+
const index = new MiniSearch({ ...rest });
|
|
58
|
+
for await (const path of Vfs.walk(fs, '/')) {
|
|
59
|
+
if (!filter(path)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (!index.has(path.slice(1))) {
|
|
63
|
+
index.add({ path: path.slice(1) });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return new SearchIndex(index);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fsPromises from 'fs/promises';
|
|
2
|
+
import { SnapshotNode } from '@joinezco/memfs/snapshot';
|
|
3
|
+
import { CborEncoder } from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder';
|
|
4
|
+
import { CborDecoder } from '@jsonjoy.com/json-pack/lib/cbor/CborDecoder';
|
|
5
|
+
import { Writer } from '@jsonjoy.com/util/lib/buffers/Writer';
|
|
6
|
+
import { CborUint8Array } from '@jsonjoy.com/json-pack/lib/cbor/types';
|
|
7
|
+
import { FsApi } from '@joinezco/memfs/node/types';
|
|
8
|
+
export declare const writer: Writer;
|
|
9
|
+
export declare const encoder: CborEncoder<Writer>;
|
|
10
|
+
export declare const decoder: CborDecoder<import("@jsonjoy.com/util/lib/buffers").IReader & import("@jsonjoy.com/util/lib/buffers").IReaderResettable>;
|
|
11
|
+
/**
|
|
12
|
+
* Compress data using gzip compression.
|
|
13
|
+
* Uses Node.js zlib in Node.js environment, browser CompressionStream in browser.
|
|
14
|
+
*/
|
|
15
|
+
export declare const compress: (data: Uint8Array) => Promise<Uint8Array>;
|
|
16
|
+
export declare const decompress: (data: Uint8Array) => Promise<Uint8Array>;
|
|
17
|
+
export type BuildPathFilterArgs = {
|
|
18
|
+
include?: string[];
|
|
19
|
+
exclude?: string[];
|
|
20
|
+
};
|
|
21
|
+
export declare const buildFilter: ({ include, exclude }: BuildPathFilterArgs) => (path: string) => boolean;
|
|
22
|
+
export type IgnoreArgs = {
|
|
23
|
+
fs: typeof fsPromises;
|
|
24
|
+
root: string;
|
|
25
|
+
exclude: string[];
|
|
26
|
+
gitignore: string | null;
|
|
27
|
+
};
|
|
28
|
+
export declare const getGitignored: (path: string, fs?: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function") => Promise<any>;
|
|
29
|
+
export type TakeSnapshotProps = {
|
|
30
|
+
root: string;
|
|
31
|
+
filter: (path: string) => (Promise<boolean> | boolean);
|
|
32
|
+
};
|
|
33
|
+
export declare const snapshotDefaults: TakeSnapshotProps;
|
|
34
|
+
/**
|
|
35
|
+
* Takes a snapshot of the file system based on the provided properties.
|
|
36
|
+
* The snapshot is encoded with CBOR and compressed with gzip.
|
|
37
|
+
*
|
|
38
|
+
* @param props - The properties to configure the snapshot.
|
|
39
|
+
*/
|
|
40
|
+
export declare const takeSnapshot: (props?: Partial<TakeSnapshotProps>) => Promise<Uint8Array<ArrayBufferLike>>;
|
|
41
|
+
export type SnapshotOptions = {
|
|
42
|
+
fs: FsApi;
|
|
43
|
+
path?: string;
|
|
44
|
+
separator?: string;
|
|
45
|
+
};
|
|
46
|
+
export declare namespace Snapshot {
|
|
47
|
+
const take: ({ fs, path, filter, separator }: {
|
|
48
|
+
fs: typeof fsPromises;
|
|
49
|
+
path: TakeSnapshotProps["root"];
|
|
50
|
+
filter?: TakeSnapshotProps["filter"];
|
|
51
|
+
separator?: string;
|
|
52
|
+
}) => Promise<SnapshotNode>;
|
|
53
|
+
const mount: (buffer: CborUint8Array<SnapshotNode>, { fs, path, separator }: SnapshotOptions) => Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Load and mount a snapshot directly from a URL in a web worker environment.
|
|
56
|
+
* This is more efficient for large snapshots as it avoids transferring data through the main thread.
|
|
57
|
+
*/
|
|
58
|
+
const loadAndMount: (url: string, { fs, path, separator }: SnapshotOptions) => Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
export declare const fromSnapshot: (snapshot: SnapshotNode, { fs, path, separator }: SnapshotOptions) => Promise<void>;
|