@faiezhares/source-code-highlighter 0.0.0-stage → 0.0.17-beta

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/index.js ADDED
@@ -0,0 +1,4686 @@
1
+ #!/usr/bin/env node
2
+ import{createRequire as __cr}from"module";import{fileURLToPath as __fup}from"url";import{dirname as __dn}from"path";const require=__cr(import.meta.url);const __filename=__fup(import.meta.url);const __dirname=__dn(__filename);
3
+
4
+ // server/index.js
5
+ import express from "express";
6
+ import path8 from "path";
7
+
8
+ // server/ws/WebSocketServerLuncher.js
9
+ import { createRequire } from "module";
10
+
11
+ // server/constants.js
12
+ var LOCAL_OPENCODE_BASE_URL = "http://127.0.0.1:4096";
13
+ var DEFAULT_OPENCODE_TIMEOUT = 6e4;
14
+ var MAX_FILE_SIZE = 1024 * 20;
15
+ var MAX_LINES_PER_FILE_CHUNK = 20;
16
+ var ANNOTABLE_EXTENSIONS = /\.(js|ts|jsx|tsx|vue)$/i;
17
+ var VUE_JS_EXTENSIONS = /\.(vue)$/i;
18
+ var ENV_PATH_VARIABLE_NAME = "TODIDO_STORAGE_PATH";
19
+ var HOME_SUBDIR_NAME = ".@faiezhares_source-code-highlighter/";
20
+ var APP_NAME = "@faiezhares/source-code-highlighter";
21
+ var INLINE_APP_NAME = "todido-io";
22
+ var APP_WEB_URLS = {
23
+ UpgradeGuide: "https://substack.com/home/post/p-188695237",
24
+ Troubleshooting: "https://substack.com/home/post/p-188695237",
25
+ ConnectionErrorTroubleshooting: "https://substack.com/home/post/p-188695237",
26
+ ReportIssuesUrl: "https://github.com/haresfaiez/todido-issues/issues"
27
+ };
28
+ var READABLE_EXTENSIONS = [
29
+ ".txt",
30
+ ".md",
31
+ ".js",
32
+ ".ts",
33
+ ".jsx",
34
+ ".tsx",
35
+ ".json",
36
+ ".css",
37
+ ".scss",
38
+ ".html",
39
+ ".xml",
40
+ ".yml",
41
+ ".yaml",
42
+ ".csv",
43
+ ".log",
44
+ ".py",
45
+ ".java",
46
+ ".c",
47
+ ".cpp",
48
+ ".h",
49
+ ".hpp",
50
+ ".rb",
51
+ ".go",
52
+ ".rs",
53
+ ".php",
54
+ ".sh",
55
+ ".bat",
56
+ ".pl",
57
+ ".ini",
58
+ ".conf",
59
+ ".env",
60
+ ".vue"
61
+ ];
62
+
63
+ // server/ws/WebSocketServerLuncher.js
64
+ var _require = createRequire(import.meta.url);
65
+ var { Server: WebSocketServer } = _require("ws");
66
+ var MAX_RESTART_ATTEMPTS = 5;
67
+ var RESTART_ATTEMPTS_INTERVAL = 2e3;
68
+ var WebSocketServerLuncher = class {
69
+ start(startWebServer2) {
70
+ this.startWebServer = startWebServer2;
71
+ this.restartAttempts = 0;
72
+ this.attemptStart();
73
+ }
74
+ attemptStart() {
75
+ try {
76
+ const webServer = this.startWebServer();
77
+ this.webSocket = new WebSocketServer({ server: webServer });
78
+ this.webSocket.on("connection", (ws) => {
79
+ this.restartAttempts = 0;
80
+ ws.on("message", async (chunk) => {
81
+ await this.handle(ws, chunk);
82
+ });
83
+ });
84
+ this.webSocket.on("error", (error) => {
85
+ this.restart(error);
86
+ });
87
+ } catch (error) {
88
+ this.restart(error);
89
+ }
90
+ }
91
+ restart(error) {
92
+ this.restartAttempts++;
93
+ if (this.restartAttempts > MAX_RESTART_ATTEMPTS) {
94
+ console.error(MESSAGES.giveUp(MAX_RESTART_ATTEMPTS, error));
95
+ return;
96
+ }
97
+ console.error(MESSAGES.retry(this.restartAttempts, MAX_RESTART_ATTEMPTS, error));
98
+ setTimeout(() => {
99
+ this.attemptStart();
100
+ }, RESTART_ATTEMPTS_INTERVAL);
101
+ }
102
+ handle() {
103
+ throw new Error("handle() method must be implemented by subclass");
104
+ }
105
+ };
106
+ var MESSAGES = {
107
+ giveUp: (attempts, error) => `
108
+ [${APP_NAME}]: GIVING UP
109
+
110
+ [${APP_NAME}]: Failed to restart server after ${attempts} attempts.
111
+ [${APP_NAME}]: Please check the error message and restart manually.
112
+
113
+ [${APP_NAME}]: Error details:
114
+ ${error?.message || error || "Unknown error"}
115
+
116
+ [${APP_NAME}]: If this issue persists, please check:
117
+ [${APP_NAME}]: ${APP_WEB_URLS.Troubleshooting}`,
118
+ retry: (currentAttempt, maxAttempts, error) => `
119
+ [${APP_NAME}]: Automatic Restart (Attempt ${currentAttempt}/${maxAttempts})
120
+ [${APP_NAME}]: Error: ${error?.message || error || "Unknown error"}
121
+ [${APP_NAME}]: Restarting WebSocket server in 2 seconds...
122
+ `
123
+ };
124
+
125
+ // server/ws/MessageTypes.js
126
+ var MessageTypes = {
127
+ GET_FILE_METADATA: "GET_FILE_METADATA",
128
+ CHECK_UPDATE: "CHECK_UPDATE",
129
+ PROJECT_METADATA: "PROJECT_METADATA",
130
+ FIND_TOKEN_USAGES: "FIND_TOKEN_USAGES",
131
+ ROOT: "ROOT",
132
+ GET_NOTES: "GET_NOTES",
133
+ SET_NOTES: "SET_NOTES",
134
+ SEARCH_FILES: "SEARCH_FILES",
135
+ RECENTLY_MODIFIED_FILES: "RECENTLY_MODIFIED_FILES",
136
+ MODIFIED_FILES: "MODIFIED_FILES",
137
+ DIR_CONTENT: "DIR_CONTENT",
138
+ SET_STORAGE_FILE_PATH: "SET_STORAGE_FILE_PATH",
139
+ SET_DARK_MODE: "SET_DARK_MODE",
140
+ DEBUG_TIMELINE: "DEBUG_TIMELINE",
141
+ GET_DEBUG_EVENT_BY_ID: "GET_DEBUG_EVENT_BY_ID",
142
+ // AI
143
+ CHECK_AI_SERVER: "CHECK_AI_SERVER",
144
+ PROMPT_AI: "PROMPT_AI",
145
+ // file view
146
+ FETCH_TEXT: "FETCH_TEXT",
147
+ FETCH_TOKENS: "FETCH_TOKENS",
148
+ FETCH_NOTES_HIGHLIGHTS: "FETCH_NOTES_HIGHLIGHTS",
149
+ FETCH_FOCUSED_NOTE_HIGHLIGHTS: "FETCH_FOCUSED_NOTE_HIGHLIGHTS",
150
+ FETCH_TEMPORAL_NOTES_HIGHLIGHTS: "FETCH_TEMPORAL_NOTES_HIGHLIGHTS",
151
+ EXPAND_TOKEN: "EXPAND_TOKEN",
152
+ // session
153
+ CREATE_SESSION: "CREATE_SESSION",
154
+ GET_SESSIONS: "GET_SESSIONS",
155
+ RENAME_SESSION: "RENAME_SESSION",
156
+ EXPORT_SESSION: "EXPORT_SESSION",
157
+ IMPORT_SESSION: "IMPORT_SESSION",
158
+ // diff
159
+ GET_COMMITS: "GET_COMMITS",
160
+ GET_COMMIT_NOTE: "GET_COMMIT_NOTE",
161
+ DIFF_RANGES: "DIFF_RANGES",
162
+ GET_RANGE_IN_COMMIT: "GET_RANGE_IN_COMMIT"
163
+ };
164
+ var MessageTypes_default = MessageTypes;
165
+
166
+ // server/ws/WebSocketServer.js
167
+ var WebSocketServer2 = class extends WebSocketServerLuncher {
168
+ constructor(messageHandlerFactory2, storage2, cache2) {
169
+ super();
170
+ this.messageHandlerFactory = messageHandlerFactory2;
171
+ this.storage = storage2;
172
+ this.cache = cache2;
173
+ }
174
+ async handle(ws, chunk) {
175
+ this.ws = ws;
176
+ const message = JSON.parse(chunk);
177
+ const handler = this.messageHandlerFactory.create(message, ws);
178
+ this.storage.registerDebugEvent("request/fe->be", message);
179
+ try {
180
+ if (message.TYPE === MessageTypes_default.GET_FILE_METADATA) {
181
+ handler.getFileMetadata();
182
+ return;
183
+ }
184
+ if (message.TYPE === MessageTypes_default.CHECK_UPDATE) {
185
+ handler.checkUpdate();
186
+ return;
187
+ }
188
+ if (message.TYPE === MessageTypes_default.PROJECT_METADATA) {
189
+ handler.projectMetadata();
190
+ return;
191
+ }
192
+ if (message.TYPE === MessageTypes_default.FIND_TOKEN_USAGES) {
193
+ handler.findTokenUsages();
194
+ return;
195
+ }
196
+ if (message.TYPE === MessageTypes_default.ROOT) {
197
+ handler.root();
198
+ return;
199
+ }
200
+ if (message.TYPE === MessageTypes_default.GET_NOTES) {
201
+ handler.getNotes();
202
+ return;
203
+ }
204
+ if (message.TYPE === MessageTypes_default.SET_NOTES) {
205
+ await handler.setNotes();
206
+ return;
207
+ }
208
+ if (message.TYPE === MessageTypes_default.SEARCH_FILES) {
209
+ handler.searchFiles();
210
+ return;
211
+ }
212
+ if (message.TYPE === MessageTypes_default.RECENTLY_MODIFIED_FILES) {
213
+ handler.recentlyModifiedFiles();
214
+ return;
215
+ }
216
+ if (message.TYPE === MessageTypes_default.MODIFIED_FILES) {
217
+ handler.gitModifiedFiles();
218
+ return;
219
+ }
220
+ if (message.TYPE === MessageTypes_default.DIR_CONTENT) {
221
+ handler.dirContent();
222
+ return;
223
+ }
224
+ if (message.TYPE === MessageTypes_default.SET_STORAGE_FILE_PATH) {
225
+ handler.setStorageFilePath();
226
+ return;
227
+ }
228
+ if (message.TYPE === MessageTypes_default.SET_DARK_MODE) {
229
+ handler.setDarkMode();
230
+ return;
231
+ }
232
+ if (message.TYPE === MessageTypes_default.DEBUG_TIMELINE) {
233
+ handler.debugTimeline();
234
+ return;
235
+ }
236
+ if (message.TYPE === MessageTypes_default.GET_DEBUG_EVENT_BY_ID) {
237
+ handler.getDebugEventById(message.eventId);
238
+ return;
239
+ }
240
+ if (message.TYPE === MessageTypes_default.CHECK_AI_SERVER) {
241
+ await handler.checkAiServer();
242
+ return;
243
+ }
244
+ if (message.TYPE === MessageTypes_default.PROMPT_AI) {
245
+ await handler.promptAi();
246
+ return;
247
+ }
248
+ if (message.TYPE === MessageTypes_default.FETCH_TEXT) {
249
+ handler.fetchText();
250
+ return;
251
+ }
252
+ if (message.TYPE === MessageTypes_default.FETCH_TOKENS) {
253
+ handler.fetchTokens();
254
+ return;
255
+ }
256
+ if (message.TYPE === MessageTypes_default.FETCH_NOTES_HIGHLIGHTS) {
257
+ handler.fetchNotesHighlights();
258
+ return;
259
+ }
260
+ if (message.TYPE === MessageTypes_default.FETCH_FOCUSED_NOTE_HIGHLIGHTS) {
261
+ handler.fetchFocusedNoteHighlights();
262
+ return;
263
+ }
264
+ if (message.TYPE === MessageTypes_default.FETCH_TEMPORAL_NOTES_HIGHLIGHTS) {
265
+ handler.fetchTemporalNotesHighlights();
266
+ return;
267
+ }
268
+ if (message.TYPE === MessageTypes_default.EXPAND_TOKEN) {
269
+ handler.expandToken();
270
+ return;
271
+ }
272
+ if (message.TYPE === MessageTypes_default.CREATE_SESSION) {
273
+ handler.createSession();
274
+ return;
275
+ }
276
+ if (message.TYPE === MessageTypes_default.GET_SESSIONS) {
277
+ handler.getSessions();
278
+ return;
279
+ }
280
+ if (message.TYPE === MessageTypes_default.RENAME_SESSION) {
281
+ handler.renameSession();
282
+ return;
283
+ }
284
+ if (message.TYPE === MessageTypes_default.EXPORT_SESSION) {
285
+ handler.exportSession();
286
+ return;
287
+ }
288
+ if (message.TYPE === MessageTypes_default.IMPORT_SESSION) {
289
+ handler.importSession();
290
+ return;
291
+ }
292
+ if (message.TYPE === MessageTypes_default.GET_COMMITS) {
293
+ handler.getCommits();
294
+ return;
295
+ }
296
+ if (message.TYPE === MessageTypes_default.GET_COMMIT_NOTE) {
297
+ handler.getCommitNote();
298
+ return;
299
+ }
300
+ if (message.TYPE === MessageTypes_default.DIFF_RANGES) {
301
+ handler.diffs();
302
+ return;
303
+ }
304
+ if (message.TYPE === MessageTypes_default.GET_RANGE_IN_COMMIT) {
305
+ handler.getRangeInCommit();
306
+ return;
307
+ }
308
+ } catch (error) {
309
+ const errorObject = {
310
+ TYPE: message.TYPE,
311
+ id: message.id,
312
+ error: error.message || error || `Unknown error while processing ${message.TYPE}.`,
313
+ callStack: error.stack
314
+ };
315
+ this.storage.registerDebugEvent("errormessage/be->fe", errorObject);
316
+ ws.send(JSON.stringify(errorObject));
317
+ }
318
+ }
319
+ };
320
+
321
+ // server/os/FileReader.js
322
+ import fs2 from "fs";
323
+ import { createPatch, applyPatch } from "diff";
324
+
325
+ // server/os/FileCommandLine.js
326
+ import { execSync } from "child_process";
327
+ var FileCommandLine = class {
328
+ gitShow(absoluteFilePath, commitHash) {
329
+ const lastSlash = absoluteFilePath.lastIndexOf("/");
330
+ const dir = absoluteFilePath.slice(0, lastSlash);
331
+ const fileName = absoluteFilePath.slice(lastSlash + 1);
332
+ const command = `git show ${commitHash}:./${fileName}`;
333
+ const content = execSync(command, {
334
+ cwd: dir,
335
+ encoding: "utf8"
336
+ }).toString();
337
+ return content;
338
+ }
339
+ linesCount(absoluteFilePath, commitHash) {
340
+ if (commitHash) {
341
+ const content = this.gitShow(absoluteFilePath, commitHash);
342
+ return content.split("\n").length;
343
+ }
344
+ let output;
345
+ if (process.platform === "win32") {
346
+ output = execSync(`find /c /v "" "${absoluteFilePath}"`, {
347
+ encoding: "utf-8",
348
+ stdio: ["pipe", "pipe", "ignore"]
349
+ });
350
+ const match = output.match(/:\s*(\d+)/);
351
+ return match ? parseInt(match[1], 10) : 0;
352
+ } else {
353
+ output = execSync(`wc -l < "${absoluteFilePath}"`, {
354
+ encoding: "utf-8",
355
+ stdio: ["pipe", "pipe", "ignore"]
356
+ });
357
+ return parseInt(output.trim(), 10) || 0;
358
+ }
359
+ }
360
+ };
361
+
362
+ // server/parser/token/ASTTokensPlaceholder.js
363
+ var ASTTokensPlaceholder = class {
364
+ constructor(startLine, endLine) {
365
+ this.className = "ASTTokensPlaceholder";
366
+ this.startLine = startLine;
367
+ this.endLine = endLine;
368
+ }
369
+ };
370
+
371
+ // server/os/TextReader.js
372
+ import fs from "fs";
373
+ import path from "path";
374
+ var NEW_LINE_CHARACTER = "\n";
375
+ var LINE_BREAK_REGEX = /\r\n|\r|\n/;
376
+ var EACH_LINE_CONTENT_REGEX = /.*?(?=\r\n|\r|\n|$)/g;
377
+ var isNewLine = (str) => LINE_BREAK_REGEX.test(str);
378
+ var endsWithNewLine = (str) => /[\s\r\n]+$/.test(str);
379
+ var TextReader = class {
380
+ constructor(content) {
381
+ this.content = content;
382
+ }
383
+ cleanLines() {
384
+ this.content = this.content.trim();
385
+ return this.lines().filter(Boolean);
386
+ }
387
+ lines() {
388
+ return this.content.split(LINE_BREAK_REGEX);
389
+ }
390
+ toEmptyLines() {
391
+ return this.content.replace(EACH_LINE_CONTENT_REGEX, "");
392
+ }
393
+ // TODO: Merge this 3 methods (Make this class mutable)
394
+ toDirOrFileList() {
395
+ return this.lines().filter((line) => line.trim().length > 0 && line.trim() !== "." && line.trim() !== "..").map((line) => {
396
+ const parts = line.split(" ");
397
+ const timestamp = parseFloat(parts[0]);
398
+ const fullPath = parts.slice(1).join(" ");
399
+ const name = path.basename(fullPath);
400
+ const stat = fs.statSync(fullPath);
401
+ const isDir = stat.isDirectory();
402
+ return {
403
+ name,
404
+ type: isDir ? "dir" : "file",
405
+ path: name,
406
+ size: isDir ? null : stat.size,
407
+ modified: Math.floor(timestamp * 1e3)
408
+ // Convert to milliseconds
409
+ };
410
+ });
411
+ }
412
+ formatFilesList(absoluteProjectRoot, transformation = (e) => e) {
413
+ return this.lines().filter(
414
+ (file) => file.trim().length > 0 && (READABLE_EXTENSIONS.some((ext) => file.toLowerCase().endsWith(ext)) || CONFIG_FILES.some((cfg) => path.basename(file).toLowerCase() === cfg))
415
+ ).map((file) => {
416
+ const result = transformation(file.replace(/^(\s*\.\/|\s+)/g, ""));
417
+ const resultAbsolutePath = path.join(absoluteProjectRoot, result);
418
+ const stat = fs.statSync(resultAbsolutePath);
419
+ return {
420
+ path: resultAbsolutePath,
421
+ type: "file",
422
+ size: stat.size,
423
+ modified: stat.mtime.getTime()
424
+ };
425
+ });
426
+ }
427
+ toGitRootRelativeFilesList(absoluteProjectRoot, gitRoot) {
428
+ const gitRelativePathLength = absoluteProjectRoot.length - gitRoot.length;
429
+ return this.formatFilesList(absoluteProjectRoot, (line) => {
430
+ const prePathLength = line.lastIndexOf(" ") + 1;
431
+ return line.substring(prePathLength + gitRelativePathLength);
432
+ });
433
+ }
434
+ };
435
+
436
+ // server/os/SourceCodeFileReader.js
437
+ var SourceCode = class {
438
+ constructor(content, lines, linesStarts, tokensPlaceholders, longestLineLength) {
439
+ this.content = content;
440
+ this.lines = lines;
441
+ this.linesStarts = linesStarts;
442
+ this.tokensPlaceholders = tokensPlaceholders;
443
+ this.longestLineLength = longestLineLength;
444
+ }
445
+ get linesCount() {
446
+ return this.lines.length;
447
+ }
448
+ calculateContent() {
449
+ this.content = this.lines.join("\n");
450
+ }
451
+ calculateLines() {
452
+ this.lines = this.content.split(LINE_BREAK_REGEX);
453
+ }
454
+ calculateTokensPlaceholders() {
455
+ this.tokensPlaceholders = [];
456
+ for (let start = 0; start < this.lines.length; start += MAX_LINES_PER_FILE_CHUNK) {
457
+ const end = Math.min(start + MAX_LINES_PER_FILE_CHUNK - 1, this.lines.length - 1);
458
+ this.tokensPlaceholders.push(new ASTTokensPlaceholder(start + 1, end + 1));
459
+ }
460
+ }
461
+ // TODO: Improve perfs of this
462
+ calculateLinesStart() {
463
+ this.linesStarts = /* @__PURE__ */ new Map();
464
+ let currentIndex = 0;
465
+ for (let i = 0; i < this.lines.length; i++) {
466
+ this.linesStarts.set(i + 1, currentIndex);
467
+ currentIndex += this.lines[i].length + 1;
468
+ }
469
+ }
470
+ // TODO: Improve perfs of this
471
+ calculateLongestLineLength() {
472
+ this.longestLineLength = 0;
473
+ for (const line of this.lines) {
474
+ if (line.length > this.longestLineLength) {
475
+ this.longestLineLength = line.length;
476
+ }
477
+ }
478
+ }
479
+ };
480
+ var SourceCodeFileReader = class {
481
+ readFileRange(fileObject, startLine, endLine) {
482
+ const lines = fileObject.lines.slice(startLine - 1, endLine);
483
+ const result = new SourceCode(null, lines, fileObject.linesStarts);
484
+ result.calculateContent();
485
+ return result;
486
+ }
487
+ createReadResult(content) {
488
+ const result = new SourceCode(content);
489
+ result.calculateLines();
490
+ result.calculateLinesStart();
491
+ result.calculateTokensPlaceholders();
492
+ result.calculateLongestLineLength();
493
+ return result;
494
+ }
495
+ };
496
+
497
+ // server/os/FileReader.js
498
+ var FileReader = class _FileReader {
499
+ constructor(environment2) {
500
+ this.environment = environment2;
501
+ }
502
+ get absoluteProjectRoot() {
503
+ return this.environment.absoluteProjectRoot || ".";
504
+ }
505
+ withEnvironment(environment2) {
506
+ return new _FileReader(environment2);
507
+ }
508
+ calculateRevision(filePath) {
509
+ const { base, current } = this.environment.gitChangeStatus(this.environment.commitHash);
510
+ const baseContent = this.fileContent(filePath, base);
511
+ const currentContent = this.fileContent(filePath, current);
512
+ const diff = createPatch("source", baseContent, currentContent, base, "working-tree");
513
+ return {
514
+ commitHash: base,
515
+ diff
516
+ };
517
+ }
518
+ readFileRange(absoluteFilePath, startLine, endLine) {
519
+ const fileObject = this.read(absoluteFilePath);
520
+ return new SourceCodeFileReader().readFileRange(fileObject, startLine, endLine);
521
+ }
522
+ readFileRangeFromSourceFile(fileObject, startLine, endLine) {
523
+ return new SourceCodeFileReader().readFileRange(fileObject, startLine, endLine);
524
+ }
525
+ // TODO: Add try/catch for commit no longer reachable
526
+ readRevision(absoluteFilePath, revision) {
527
+ const baseContent = this.fileContent(absoluteFilePath, revision.commitHash);
528
+ const content = applyPatch(baseContent, revision.diff);
529
+ return new SourceCodeFileReader().createReadResult(content);
530
+ }
531
+ // TODO: Make it async
532
+ fileContent(absoluteFilePath, commitHash) {
533
+ if (commitHash) {
534
+ return new FileCommandLine().gitShow(absoluteFilePath, commitHash);
535
+ }
536
+ const content = fs2.readFileSync(absoluteFilePath, "utf-8");
537
+ return content;
538
+ }
539
+ read(absoluteFilePath) {
540
+ const fileContent = this.fileContent(absoluteFilePath, this.environment.commitHash);
541
+ return new SourceCodeFileReader().createReadResult(fileContent);
542
+ }
543
+ readCurrent(absoluteFilePath) {
544
+ const fileContent = this.fileContent(absoluteFilePath);
545
+ return new SourceCodeFileReader().createReadResult(fileContent);
546
+ }
547
+ lineAt(absoluteFilePath, lineNumber) {
548
+ const file = this.read(absoluteFilePath);
549
+ const line = file.lines.at(lineNumber - 1);
550
+ return {
551
+ text: line.trim(),
552
+ start: file.linesStarts.get(lineNumber)
553
+ };
554
+ }
555
+ timeStamp(absoluteFilePath) {
556
+ return fs2.statSync(absoluteFilePath).mtimeMs;
557
+ }
558
+ };
559
+ var FileReader_default = FileReader;
560
+
561
+ // server/storage/Storage.js
562
+ import fs3 from "fs";
563
+ import path2 from "path";
564
+ import Database from "better-sqlite3";
565
+ var StorageStrategy = {
566
+ FilePath: "FilePath",
567
+ InMemory: "InMemory",
568
+ TestingEnvInMemory: "TestingEnvInMemory"
569
+ };
570
+ var DB_FILE_EXTENSION = "db";
571
+ var Storage = class {
572
+ constructor(absoluteProjectRoot, storageStrategy = StorageStrategy.InMemory) {
573
+ this.absoluteProjectRoot = absoluteProjectRoot;
574
+ this.storagePath = null;
575
+ this.storageStrategy = storageStrategy;
576
+ this.db = null;
577
+ this.statements = null;
578
+ if (this.isInMemoryStorage) {
579
+ this.delegate = new InMemoryStorage();
580
+ }
581
+ this.events = [];
582
+ }
583
+ get isInMemoryStorage() {
584
+ return this.storageStrategy !== StorageStrategy.FilePath;
585
+ }
586
+ initStoragePath(customPath) {
587
+ const dirPath = path2.dirname(customPath);
588
+ fs3.mkdirSync(dirPath, { recursive: true });
589
+ this.init(customPath);
590
+ }
591
+ getNotes(fileName = null, sessionId) {
592
+ if (this.isInMemoryStorage) {
593
+ return this.delegate.getNotes(fileName, sessionId);
594
+ }
595
+ if (!fileName) {
596
+ const list2 = this.statements.notesQuery.all(String(sessionId));
597
+ const listData2 = list2.map((row) => JSON.parse(row.data));
598
+ const removedList = this.statements.removedQuery.all(String(sessionId)).map((row) => JSON.parse(row.data));
599
+ return { list: listData2, removedList };
600
+ }
601
+ const list = this.statements.notesFileQuery.all(fileName, String(sessionId));
602
+ const listData = list.map((row) => JSON.parse(row.data));
603
+ return { list: listData };
604
+ }
605
+ getNoteByIdx(idx, sessionId) {
606
+ if (this.isInMemoryStorage) {
607
+ return this.delegate.getNoteByIdx(idx, sessionId);
608
+ }
609
+ const row = this.statements.noteByIdxQuery.get(String(idx), String(sessionId));
610
+ if (row) {
611
+ return JSON.parse(row.data);
612
+ }
613
+ const allRows = this.statements.notesQuery.all(String(sessionId));
614
+ for (const noteRow of allRows) {
615
+ const note = JSON.parse(noteRow.data);
616
+ const subnote = note.subNotes?.find((s) => String(s.idx) === String(idx));
617
+ if (subnote) return subnote;
618
+ }
619
+ return null;
620
+ }
621
+ setNotes(list, removedList, sessionId) {
622
+ if (this.isInMemoryStorage) {
623
+ this.delegate.setNotes(list, removedList, sessionId);
624
+ return;
625
+ }
626
+ const transaction = this.db.transaction(() => {
627
+ this.statements.deleteNotes.run(String(sessionId));
628
+ this.statements.deleteRemoved.run(String(sessionId));
629
+ for (const note of list) {
630
+ this.statements.notesInsert.run(
631
+ String(note.idx),
632
+ note.file,
633
+ String(sessionId),
634
+ JSON.stringify(note)
635
+ );
636
+ }
637
+ for (const note of removedList) {
638
+ this.statements.removedInsert.run(
639
+ String(note.idx),
640
+ note.file,
641
+ String(sessionId),
642
+ JSON.stringify(note)
643
+ );
644
+ }
645
+ });
646
+ transaction();
647
+ }
648
+ init(storagePath) {
649
+ const initialData = {
650
+ list: [],
651
+ removedList: [],
652
+ consentTimestamp: Date.now()
653
+ };
654
+ if (this.storageStrategy === StorageStrategy.TestingEnvInMemory) {
655
+ this.delegate.init(initialData);
656
+ return;
657
+ }
658
+ this.storagePath = storagePath || this.findStoragePath();
659
+ if (!this.storagePath || this.storageStrategy === StorageStrategy.InMemory) {
660
+ return;
661
+ }
662
+ const isDatabaseExisting = fs3.existsSync(this.storagePath);
663
+ this.db = new Database(this.storagePath);
664
+ this.db.pragma("journal_mode = WAL");
665
+ this.db.exec(SQL.createTables);
666
+ try {
667
+ if (!isDatabaseExisting) {
668
+ const metaInsert = this.db.prepare(SQL.metaInsert);
669
+ metaInsert.run("consentTimestamp", String(initialData.consentTimestamp));
670
+ metaInsert.run("isDarkMode", "false");
671
+ }
672
+ } catch (e) {
673
+ console.error("Storage init error:", e);
674
+ }
675
+ this.statements = {
676
+ notesQuery: this.db.prepare(SQL.notesQuery),
677
+ notesFileQuery: this.db.prepare(SQL.notesFileQuery),
678
+ noteByIdxQuery: this.db.prepare(SQL.noteByIdxQuery),
679
+ notesInsert: this.db.prepare(SQL.notesInsert),
680
+ deleteNotes: this.db.prepare(SQL.deleteNotes),
681
+ removedQuery: this.db.prepare(SQL.removedQuery),
682
+ removedInsert: this.db.prepare(SQL.removedInsert),
683
+ deleteRemoved: this.db.prepare(SQL.deleteRemoved),
684
+ metadataQuery: this.db.prepare(SQL.metadataQuery),
685
+ metaUpdate: this.db.prepare(SQL.metaUpdate),
686
+ sessionInsert: this.db.prepare(SQL.sessionInsert),
687
+ sessionsQuery: this.db.prepare(SQL.sessionsQuery),
688
+ sessionRename: this.db.prepare(SQL.sessionRename),
689
+ sessionByIdQuery: this.db.prepare(SQL.sessionByIdQuery)
690
+ };
691
+ }
692
+ consentTimestamp() {
693
+ if (this.isInMemoryStorage) {
694
+ return this.delegate.consentTimestamp();
695
+ }
696
+ const row = this.statements.metadataQuery.get("consentTimestamp");
697
+ return row ? parseInt(row.value) : Date.now();
698
+ }
699
+ isDarkMode() {
700
+ if (this.isInMemoryStorage) {
701
+ return this.delegate.isDarkMode();
702
+ }
703
+ const row = this.statements.metadataQuery.get("isDarkMode");
704
+ return row ? row.value === "true" : false;
705
+ }
706
+ setIsDarkMode(isDarkMode) {
707
+ if (this.isInMemoryStorage) {
708
+ this.delegate.setIsDarkMode(isDarkMode);
709
+ return;
710
+ }
711
+ this.statements.metaUpdate.run("isDarkMode", isDarkMode ? "true" : "false");
712
+ }
713
+ createSession(sessionName) {
714
+ const now = Date.now();
715
+ const id = Date.now().toString(36) + Math.random().toString(36).substr(2);
716
+ if (this.isInMemoryStorage) return { id, name: sessionName, lastModified: now };
717
+ this.statements.sessionInsert.run(id, sessionName, now, now);
718
+ return { id, name: sessionName, lastModified: now };
719
+ }
720
+ getSessions() {
721
+ if (this.isInMemoryStorage) return [];
722
+ return this.statements.sessionsQuery.all();
723
+ }
724
+ getSessionById(sessionId) {
725
+ if (this.isInMemoryStorage) return null;
726
+ return this.statements.sessionByIdQuery.get(String(sessionId)) || null;
727
+ }
728
+ renameSession(sessionId, name) {
729
+ if (this.isInMemoryStorage) return null;
730
+ const now = Date.now();
731
+ this.statements.sessionRename.run(name, now, String(sessionId));
732
+ return { id: sessionId, name, lastModified: now };
733
+ }
734
+ //
735
+ storageFileName() {
736
+ if (!this.absoluteProjectRoot) {
737
+ return `${INLINE_APP_NAME}-default.${DB_FILE_EXTENSION}`;
738
+ }
739
+ let hash = 0;
740
+ for (let i = 0; i < this.absoluteProjectRoot.length; i++) {
741
+ const char = this.absoluteProjectRoot.charCodeAt(i);
742
+ hash = (hash << 5) - hash + char;
743
+ hash = hash & hash;
744
+ }
745
+ const shortHash = Math.abs(hash).toString(36).substring(0, 8);
746
+ return `${INLINE_APP_NAME}-${shortHash}.${DB_FILE_EXTENSION}`;
747
+ }
748
+ findStoragePath(onlyIfExists = true) {
749
+ const envPath = process.env[ENV_PATH_VARIABLE_NAME];
750
+ if (envPath) {
751
+ let resolvedPath;
752
+ if (envPath.endsWith(`.${DB_FILE_EXTENSION}`)) {
753
+ resolvedPath = path2.resolve(envPath);
754
+ } else {
755
+ resolvedPath = path2.resolve(envPath, this.storageFileName());
756
+ }
757
+ return !onlyIfExists || fs3.existsSync(resolvedPath) ? resolvedPath : null;
758
+ }
759
+ const possiblePaths = [
760
+ path2.resolve(process.env.HOME, HOME_SUBDIR_NAME + this.storageFileName())
761
+ ];
762
+ for (const eachPossiblePath of possiblePaths) {
763
+ if (!onlyIfExists || fs3.existsSync(eachPossiblePath)) {
764
+ return eachPossiblePath;
765
+ }
766
+ }
767
+ return null;
768
+ }
769
+ setStorageStrategy(storageStrategy) {
770
+ this.storageStrategy = storageStrategy;
771
+ }
772
+ // Debug events
773
+ registerDebugEvent(scope, event) {
774
+ if (process.env.NODE_ENV === "development") {
775
+ this.events.push({
776
+ time: /* @__PURE__ */ new Date(),
777
+ scope,
778
+ eventType: event.TYPE,
779
+ id: event.id,
780
+ event: JSON.stringify(event)
781
+ });
782
+ }
783
+ }
784
+ debugTimeline() {
785
+ return this.events.map((eachEntry) => ({
786
+ time: eachEntry.time,
787
+ scope: eachEntry.scope,
788
+ eventType: eachEntry.eventType,
789
+ id: eachEntry.id
790
+ })).reverse();
791
+ }
792
+ getDebugEventById(id) {
793
+ return this.events.filter((eachEntry) => eachEntry.id === id);
794
+ }
795
+ };
796
+ var InMemoryStorage = class {
797
+ constructor() {
798
+ this.data = { list: [], removedList: [] };
799
+ }
800
+ getNotes(fileName = null) {
801
+ if (!fileName) return this.data;
802
+ return { list: this.data.list.filter((note) => note.file === fileName) };
803
+ }
804
+ getNoteByIdx(idx) {
805
+ for (let i = 0; i < this.data.list.length; i++) {
806
+ const eachNote = this.data.list[i];
807
+ if (eachNote.idx === idx) {
808
+ return eachNote;
809
+ }
810
+ if (eachNote.subNotes) {
811
+ for (let j = 0; j < eachNote.subNotes.length; j++) {
812
+ if (eachNote.subNotes[j].idx === idx) {
813
+ return eachNote.subNotes[j];
814
+ }
815
+ }
816
+ }
817
+ }
818
+ }
819
+ setNotes(list, removedList) {
820
+ this.data = { ...this.data || {}, list, removedList };
821
+ }
822
+ init(initialData) {
823
+ this.data = initialData;
824
+ }
825
+ consentTimestamp() {
826
+ return this.data?.consentTimestamp;
827
+ }
828
+ isDarkMode() {
829
+ return !!this.data?.isDarkMode;
830
+ }
831
+ setIsDarkMode(isDarkMode) {
832
+ this.data.isDarkMode = isDarkMode;
833
+ }
834
+ };
835
+ var SQL = {
836
+ createTables: `
837
+ CREATE TABLE IF NOT EXISTS sessions (
838
+ id TEXT PRIMARY KEY,
839
+ name TEXT UNIQUE NOT NULL,
840
+ created_at INTEGER NOT NULL,
841
+ last_modified INTEGER NOT NULL
842
+ );
843
+
844
+ CREATE TABLE IF NOT EXISTS notes (
845
+ idx TEXT PRIMARY KEY,
846
+ file TEXT,
847
+ session_id TEXT NOT NULL,
848
+ data TEXT NOT NULL,
849
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
850
+ );
851
+
852
+ CREATE TABLE IF NOT EXISTS removed_notes (
853
+ idx TEXT PRIMARY KEY,
854
+ file TEXT,
855
+ session_id TEXT NOT NULL,
856
+ data TEXT NOT NULL,
857
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
858
+ );
859
+
860
+ CREATE TABLE IF NOT EXISTS metadata (
861
+ key TEXT PRIMARY KEY,
862
+ value TEXT
863
+ );
864
+ `,
865
+ notesQuery: "SELECT * FROM notes WHERE session_id = ?",
866
+ notesFileQuery: "SELECT * FROM notes WHERE file = ? AND session_id = ?",
867
+ noteByIdxQuery: "SELECT * FROM notes WHERE idx = ? AND session_id = ?",
868
+ notesInsert: "INSERT OR REPLACE INTO notes (idx, file, session_id, data) VALUES (?, ?, ?, ?)",
869
+ deleteNotes: "DELETE FROM notes WHERE session_id = ?",
870
+ removedQuery: "SELECT data FROM removed_notes WHERE session_id = ? ",
871
+ removedInsert: "INSERT OR REPLACE INTO removed_notes (idx, file, session_id, data) VALUES (?, ?, ?, ?)",
872
+ deleteRemoved: "DELETE FROM removed_notes WHERE session_id = ?",
873
+ metadataQuery: "SELECT value FROM metadata WHERE key = ?",
874
+ metaUpdate: "INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)",
875
+ metaInsert: "INSERT INTO metadata (key, value) VALUES (?, ?)",
876
+ sessionInsert: "INSERT INTO sessions (id, name, created_at, last_modified) VALUES (?, ?, ?, ?)",
877
+ sessionRename: "UPDATE sessions SET name = ?, last_modified = ? WHERE id = ?",
878
+ sessionsQuery: "SELECT id, name, created_at, last_modified FROM sessions ORDER BY last_modified DESC",
879
+ sessionByIdQuery: "SELECT id, name, created_at, last_modified FROM sessions WHERE id = ?"
880
+ };
881
+
882
+ // server/controllers/FsMetadataController.js
883
+ var FsMetadataController = class {
884
+ constructor(environment2, fileCommandLine2, storage2, myPackage2) {
885
+ this.environment = environment2;
886
+ this.fileCommandLine = fileCommandLine2;
887
+ this.storage = storage2;
888
+ this.myPackage = myPackage2;
889
+ }
890
+ getCurrentAndLatestVersions() {
891
+ return {
892
+ currentVersion: this.myPackage.currentVersion(),
893
+ latestVersion: this.myPackage.latestVersion(),
894
+ isDevBuild: process.env.NODE_ENV === "development"
895
+ };
896
+ }
897
+ getProjectMetadata() {
898
+ return {
899
+ projectMetadata: {
900
+ root: this.environment.absoluteProjectRoot,
901
+ revision: this.environment.gitRevision()
902
+ },
903
+ links: {
904
+ connectionErrorTroubleshooting: APP_WEB_URLS.ConnectionErrorTroubleshooting,
905
+ upgradeGuide: APP_WEB_URLS.UpgradeGuide,
906
+ reportIssuesUrl: APP_WEB_URLS.ReportIssuesUrl
907
+ },
908
+ appName: APP_NAME,
909
+ packageVersion: this.myPackage.currentVersion(),
910
+ consentTimestamp: this.storage.consentTimestamp(),
911
+ storageFilePath: this.storage.findStoragePath(false),
912
+ isDarkMode: this.storage.isDarkMode()
913
+ };
914
+ }
915
+ };
916
+
917
+ // server/os/Environment.js
918
+ import path3 from "path";
919
+ import { execSync as execSync2 } from "child_process";
920
+ var Environment = class _Environment {
921
+ constructor(absoluteProjectRoot, port, gitRoot, commitHash) {
922
+ this.absoluteProjectRoot = absoluteProjectRoot;
923
+ this.port = port;
924
+ this.gitRoot = gitRoot;
925
+ this.commitHash = commitHash;
926
+ }
927
+ static parse(args2, launchDir2, defaultPort2) {
928
+ const result = new _Environment(launchDir2, defaultPort2);
929
+ result.parseArguments(args2);
930
+ result.setGitRoot();
931
+ return result;
932
+ }
933
+ withCommitHash(commitHash) {
934
+ return new _Environment(this.absoluteProjectRoot, this.port, this.gitRoot, commitHash);
935
+ }
936
+ parseArguments(args2) {
937
+ let projectRootArgument = ".";
938
+ for (let i = 0; i < args2.length; i++) {
939
+ if (args2[i] === "--root" && i + 1 < args2.length) {
940
+ projectRootArgument = args2[i + 1];
941
+ i++;
942
+ } else if (args2[i] === "--port" && i + 1 < args2.length) {
943
+ this.port = parseInt(args2[i + 1], 10) || this.port;
944
+ i++;
945
+ }
946
+ }
947
+ this.absoluteProjectRoot = path3.resolve(projectRootArgument);
948
+ }
949
+ commitMessages(commitHashes = []) {
950
+ const output = execSync2(`git show -s --format=%H%x00%s ${commitHashes.join(" ")}`, {
951
+ cwd: this.gitRoot,
952
+ encoding: "utf8"
953
+ });
954
+ const commits = new TextReader(output).cleanLines().map((line) => {
955
+ const [hash, message] = line.split("\0");
956
+ return { hash, message };
957
+ });
958
+ return commits;
959
+ }
960
+ setGitRoot() {
961
+ try {
962
+ this.gitRoot = execSync2("git rev-parse --show-toplevel", {
963
+ cwd: this.absoluteProjectRoot,
964
+ encoding: "utf8"
965
+ }).trim();
966
+ } catch (error) {
967
+ this.gitRoot = null;
968
+ }
969
+ }
970
+ gitRevision() {
971
+ if (!this.gitRoot) {
972
+ return "";
973
+ }
974
+ const branchName = execSync2("git rev-parse --abbrev-ref HEAD", {
975
+ cwd: this.absoluteProjectRoot,
976
+ encoding: "utf8"
977
+ }).trim();
978
+ const commitHash = execSync2("git rev-parse --short HEAD", {
979
+ cwd: this.absoluteProjectRoot,
980
+ encoding: "utf8"
981
+ }).trim();
982
+ return `${branchName}/${commitHash}`;
983
+ }
984
+ headCommitHash() {
985
+ return execSync2("git rev-parse HEAD", { cwd: this.absoluteProjectRoot, encoding: "utf8" }).toString().trim();
986
+ }
987
+ gitChangeStatus(commitHash) {
988
+ const head = this.headCommitHash();
989
+ const base = !commitHash || commitHash === "HEAD" ? head : commitHash;
990
+ const current = base === head ? void 0 : base;
991
+ return { base, current };
992
+ }
993
+ // TODO: Test these 2 methods on windows
994
+ recentlyModifiedFiles() {
995
+ const filesCount = 8;
996
+ const filesList = execSync2(
997
+ `find . -type f -not -path '*/.*' -exec ls -lt {} + 2>/dev/null | head -${filesCount} | awk '{print $NF}' || echo ""`,
998
+ {
999
+ cwd: this.absoluteProjectRoot,
1000
+ encoding: "utf8"
1001
+ }
1002
+ );
1003
+ return new TextReader(filesList).formatFilesList(this.absoluteProjectRoot);
1004
+ }
1005
+ changedFilesRelativePaths(commitHash, comparedToCommitHash) {
1006
+ const command = `git diff --diff-filter=AMR --name-only --relative ${comparedToCommitHash} ${commitHash} -- .`;
1007
+ const output = execSync2(command, {
1008
+ cwd: this.absoluteProjectRoot,
1009
+ encoding: "utf8"
1010
+ });
1011
+ return output.split("\n").map((line) => line.trim()).filter(Boolean);
1012
+ }
1013
+ gitModifiedFiles() {
1014
+ if (!this.gitRoot) {
1015
+ return [];
1016
+ }
1017
+ const filesList = execSync2(`git status --porcelain . | grep -E "^ *[AM][^D]" || echo ""`, {
1018
+ cwd: this.absoluteProjectRoot,
1019
+ encoding: "utf8"
1020
+ });
1021
+ return new TextReader(filesList).toGitRootRelativeFilesList(
1022
+ this.absoluteProjectRoot,
1023
+ this.gitRoot
1024
+ );
1025
+ }
1026
+ };
1027
+
1028
+ // server/os/DirCommandLine.js
1029
+ import fs4 from "fs";
1030
+ import path4 from "path";
1031
+ import { execSync as execSync3, spawnSync } from "child_process";
1032
+ var IGNORED_DIRS = ["node_modules", ".git", "dist", "build", "out"];
1033
+ var DirCommandLine = class {
1034
+ constructor(environment2) {
1035
+ this.setEnvironment(environment2);
1036
+ }
1037
+ setEnvironment(environment2) {
1038
+ this.environment = environment2;
1039
+ this.backend = process.platform === "win32" ? new WindowsDirCommandLine(this.environment) : new UnixDirCommandLine(this.environment);
1040
+ }
1041
+ search(query, isRegex, isCaseSensitive) {
1042
+ return this.backend.search(query, isRegex, isCaseSensitive);
1043
+ }
1044
+ dirContent(absolutePath) {
1045
+ return this.backend.dirContent(absolutePath);
1046
+ }
1047
+ // TODO: Use command line for this (dir / find)
1048
+ // TODO: Remove this (search with purpose instead)
1049
+ findAllFilesPathsRecursively(dir = this.environment.absoluteProjectRoot, fileList = [], rootDir = dir) {
1050
+ const files = fs4.readdirSync(dir);
1051
+ files.forEach((file) => {
1052
+ const filePath = path4.join(dir, file);
1053
+ const stat = fs4.statSync(filePath);
1054
+ if (stat.isDirectory()) {
1055
+ if (!IGNORED_DIRS.includes(file)) {
1056
+ this.findAllFilesPathsRecursively(filePath, fileList, rootDir);
1057
+ }
1058
+ } else {
1059
+ fileList.push(filePath);
1060
+ }
1061
+ });
1062
+ return fileList;
1063
+ }
1064
+ };
1065
+ var WindowsDirCommandLine = class {
1066
+ constructor(environment2) {
1067
+ this.absoluteProjectRoot = environment2.absoluteProjectRoot;
1068
+ }
1069
+ search(query, isRegex, isCaseSensitive) {
1070
+ let files;
1071
+ function walk(dir, fileList = []) {
1072
+ fs4.readdirSync(dir).forEach((file) => {
1073
+ const filePath = path4.join(dir, file);
1074
+ if (fs4.statSync(filePath).isDirectory()) {
1075
+ walk(filePath, fileList);
1076
+ } else {
1077
+ fileList.push(path4.relative(this.absoluteProjectRoot, filePath));
1078
+ }
1079
+ });
1080
+ return fileList;
1081
+ }
1082
+ files = walk(this.absoluteProjectRoot);
1083
+ files = files.filter((file) => {
1084
+ let matches;
1085
+ if (isRegex) {
1086
+ const regex = new RegExp(query, isCaseSensitive ? "" : "i");
1087
+ matches = regex.test(file);
1088
+ } else {
1089
+ const searchFile = isCaseSensitive ? file : file.toLowerCase();
1090
+ const searchQuery = isCaseSensitive ? query : query.toLowerCase();
1091
+ matches = searchFile.includes(searchQuery);
1092
+ }
1093
+ const isValidFileType = READABLE_EXTENSIONS.some((ext) => file.toLowerCase().endsWith(ext)) || CONFIG_FILES2.some((cfg) => path4.basename(file).toLowerCase() === cfg);
1094
+ return matches && isValidFileType;
1095
+ });
1096
+ return files.map((file) => path4.join(this.absoluteProjectRoot, file));
1097
+ }
1098
+ dirContent(absolutePath) {
1099
+ const fullPath = absolutePath;
1100
+ const items = fs4.readdirSync(fullPath);
1101
+ const content = items.map((item) => {
1102
+ const stat = fs4.statSync(path4.join(fullPath, item));
1103
+ return {
1104
+ name: item,
1105
+ type: stat.isDirectory() ? "dir" : "file",
1106
+ relativePath: path4.join(relativePath, item),
1107
+ size: stat.isFile() ? stat.size : null,
1108
+ modified: stat.mtime.getTime()
1109
+ };
1110
+ });
1111
+ content.sort((a, b) => {
1112
+ if (a.type !== b.type) {
1113
+ return a.type === "dir" ? -1 : 1;
1114
+ }
1115
+ return a.name.localeCompare(b.name);
1116
+ });
1117
+ return content;
1118
+ }
1119
+ };
1120
+ var UnixDirCommandLine = class {
1121
+ constructor(environment2) {
1122
+ this.absoluteProjectRoot = environment2.absoluteProjectRoot;
1123
+ }
1124
+ search(query, isRegex, isCaseSensitive) {
1125
+ const extPredicates = READABLE_EXTENSIONS.flatMap((ext) => ["-o", "-name", `*${ext}`]);
1126
+ extPredicates.shift();
1127
+ const cfgPredicates = CONFIG_FILES2.flatMap((cfg) => ["-o", "-name", cfg]);
1128
+ const namePredicates = ["(", ...extPredicates, ...cfgPredicates, ")"];
1129
+ const ignorePredicates = IGNORED_DIRS.flatMap((dir) => [
1130
+ "-not",
1131
+ "(",
1132
+ "-type",
1133
+ "d",
1134
+ "-name",
1135
+ dir,
1136
+ ")"
1137
+ ]);
1138
+ const findArgs = [".", "-type", "f", ...ignorePredicates, ...namePredicates];
1139
+ const result = spawnSync("find", findArgs, {
1140
+ encoding: "utf8",
1141
+ cwd: this.absoluteProjectRoot,
1142
+ maxBuffer: 10 * 1024 * 1024
1143
+ });
1144
+ const lines = (result.stdout || "").split("\n").filter(Boolean);
1145
+ const filtered = lines.filter((line) => {
1146
+ if (isRegex) {
1147
+ try {
1148
+ return new RegExp(query, isCaseSensitive ? "" : "i").test(line);
1149
+ } catch {
1150
+ return false;
1151
+ }
1152
+ }
1153
+ const haystack = isCaseSensitive ? line : line.toLowerCase();
1154
+ const needle = isCaseSensitive ? query : query.toLowerCase();
1155
+ return haystack.includes(needle);
1156
+ });
1157
+ return new TextReader(filtered.join("\n")).formatFilesList(this.absoluteProjectRoot);
1158
+ }
1159
+ dirContent(absolutePath) {
1160
+ let stdout;
1161
+ if (process.platform === "darwin") {
1162
+ stdout = execSync3(
1163
+ `find "${absolutePath}" -maxdepth 1 -not -path "${absolutePath}" -exec stat -f "%m %N" {} + 2>/dev/null | sort || echo ""`,
1164
+ {
1165
+ encoding: "utf8"
1166
+ }
1167
+ );
1168
+ } else {
1169
+ const command = `find "${absolutePath}" -maxdepth 1 -not -path "${absolutePath}" -printf "%T@ %p\\n" 2>/dev/null | sort || echo ""`;
1170
+ stdout = execSync3(command, {
1171
+ encoding: "utf8"
1172
+ });
1173
+ }
1174
+ return new TextReader(stdout).toDirOrFileList();
1175
+ }
1176
+ };
1177
+ var CONFIG_FILES2 = [
1178
+ ".editorconfig",
1179
+ ".env",
1180
+ ".gitignore",
1181
+ ".npmrc",
1182
+ ".babelrc",
1183
+ ".eslintrc",
1184
+ ".prettierrc",
1185
+ ".stylelintrc",
1186
+ ".dockerignore",
1187
+ ".gitattributes",
1188
+ ".eslintignore",
1189
+ ".prettierignore"
1190
+ ];
1191
+
1192
+ // server/parser/ast/ASTBuilder.js
1193
+ import ts5 from "typescript";
1194
+
1195
+ // server/parser/range/ASTTokensRange.js
1196
+ var ASTTokensRange = class _ASTTokensRange {
1197
+ constructor(startToken, endToken) {
1198
+ this.startToken = startToken;
1199
+ this.endToken = endToken;
1200
+ }
1201
+ // TODO: Duplicated in src/
1202
+ containsToken(childToken) {
1203
+ return this.startToken.rawToken.getStart() <= childToken.rawToken.getStart() && this.endToken.rawToken.getEnd() >= childToken.rawToken.getEnd();
1204
+ }
1205
+ equalsBoundaries(other) {
1206
+ return other.startToken.rawToken.getStart() === this.startToken.rawToken.getStart() && other.endToken.rawToken.getEnd() === this.endToken.rawToken.getEnd();
1207
+ }
1208
+ toResponseJSON() {
1209
+ return {
1210
+ startToken: this.startToken.toResponseJSON(),
1211
+ endToken: this.endToken.toResponseJSON()
1212
+ };
1213
+ }
1214
+ static copy(object) {
1215
+ if (object.ranges) {
1216
+ return new ASTTokensRangeSet(
1217
+ object.ranges.map((eachRange) => _ASTTokensRange.copy(eachRange))
1218
+ );
1219
+ }
1220
+ return new _ASTTokensRange(object.startToken, object.endToken);
1221
+ }
1222
+ };
1223
+ var ASTTokensRangeSet = class {
1224
+ constructor(ranges = []) {
1225
+ this.ranges = ranges;
1226
+ }
1227
+ get startToken() {
1228
+ throw new Error("ASTTokensRangeSet does not have startToken");
1229
+ }
1230
+ get endToken() {
1231
+ throw new Error("ASTTokensRangeSet does not have endToken");
1232
+ }
1233
+ containsToken(childToken) {
1234
+ return this.ranges.some((range) => range.containsToken(childToken));
1235
+ }
1236
+ toResponseJSON() {
1237
+ return { ranges: this.ranges.map((eachTokensRange) => eachTokensRange.toResponseJSON()) };
1238
+ }
1239
+ };
1240
+
1241
+ // server/parser/token/ASTRawToken.js
1242
+ var rawTokenToJSON = (rawToken) => {
1243
+ return {
1244
+ start: rawToken.getStart(),
1245
+ end: rawToken.getEnd(),
1246
+ kind: rawToken.kind,
1247
+ name: rawToken.name && rawTokenToJSON(rawToken.name)
1248
+ };
1249
+ };
1250
+ var rawTokenFromJSON = (json) => {
1251
+ return {
1252
+ ...json,
1253
+ name: json.name && rawTokenFromJSON(json.name),
1254
+ getStart() {
1255
+ return json.start;
1256
+ },
1257
+ getEnd() {
1258
+ return json.end;
1259
+ }
1260
+ };
1261
+ };
1262
+ var createCommentRawToken = (token, fullText) => {
1263
+ return {
1264
+ ...token,
1265
+ start: token.pos,
1266
+ end: token.end,
1267
+ fullText,
1268
+ getStart() {
1269
+ return this.start;
1270
+ },
1271
+ getEnd() {
1272
+ return this.end;
1273
+ },
1274
+ getFullText() {
1275
+ return this.fullText;
1276
+ }
1277
+ };
1278
+ };
1279
+
1280
+ // server/parser/token/ASTToken.js
1281
+ var ASTToken = class {
1282
+ constructor(rawToken, collapsionBlock, isHidden = false, isManuallyExpanded = false) {
1283
+ this.rawToken = rawToken;
1284
+ this.collapsionBlock = collapsionBlock;
1285
+ this.isHidden = isHidden;
1286
+ this.isManuallyExpanded = isManuallyExpanded;
1287
+ }
1288
+ equals(otherToken) {
1289
+ if (!otherToken?.rawToken || !this.rawToken) return false;
1290
+ return this.rawToken.kind === otherToken.rawToken.kind && this.rawToken.getStart() === otherToken.rawToken.getStart();
1291
+ }
1292
+ compareCoordinates(other) {
1293
+ return this.rawToken.getStart() === other.rawToken.getStart() ? other.rawToken.getEnd() - this.rawToken.getEnd() : this.rawToken.getStart() - other.rawToken.getStart();
1294
+ }
1295
+ range() {
1296
+ return new ASTTokensRange(this, this);
1297
+ }
1298
+ get start() {
1299
+ return this.rawToken?.getStart();
1300
+ }
1301
+ get end() {
1302
+ return this.rawToken?.getEnd();
1303
+ }
1304
+ toResponseJSON() {
1305
+ const rawTokenJson = this.rawToken && rawTokenToJSON(this.rawToken);
1306
+ return {
1307
+ className: this.className,
1308
+ text: this.text,
1309
+ isNewLine: this.isNewLine,
1310
+ isSecondNewLine: this.isSecondNewLine,
1311
+ isHidden: this.isHidden,
1312
+ isManuallyExpanded: this.isManuallyExpanded,
1313
+ lineNumber: this.lineNumber,
1314
+ rawToken: rawTokenJson,
1315
+ collapsionBlock: this.collapsionBlock?.toResponseJSON(),
1316
+ type: this.type,
1317
+ coordinates: this.coordinates
1318
+ };
1319
+ }
1320
+ };
1321
+
1322
+ // server/parser/token/ASTImportToken.js
1323
+ var ASTImportToken = class _ASTImportToken extends ASTToken {
1324
+ constructor(rawToken, absoluteFilePath = null, importString = null, identifierToken = null, importedIdentifiersNames = []) {
1325
+ super(rawToken);
1326
+ this.absoluteFilePath = absoluteFilePath;
1327
+ this.importString = importString;
1328
+ this.importedIdentifiersNames = importedIdentifiersNames;
1329
+ this.identifierToken = identifierToken;
1330
+ }
1331
+ withAbsoluteFilePath(absoluteFilePath) {
1332
+ return new _ASTImportToken(
1333
+ this.rawToken,
1334
+ absoluteFilePath,
1335
+ this.importString,
1336
+ this.identifierToken,
1337
+ this.importedIdentifiersNames
1338
+ );
1339
+ }
1340
+ withImportString(importString) {
1341
+ return new _ASTImportToken(
1342
+ this.rawToken,
1343
+ this.absoluteFilePath,
1344
+ importString,
1345
+ this.identifierToken,
1346
+ this.importedIdentifiersNames
1347
+ );
1348
+ }
1349
+ withQueryToken(identifierToken) {
1350
+ return new _ASTImportToken(
1351
+ this.rawToken,
1352
+ this.absoluteFilePath,
1353
+ this.importString,
1354
+ identifierToken,
1355
+ this.importedIdentifiersNames
1356
+ );
1357
+ }
1358
+ withImportedIdentifiersNames(importedIdentifiersNames) {
1359
+ return new _ASTImportToken(
1360
+ this.rawToken,
1361
+ this.absoluteFilePath,
1362
+ this.importString,
1363
+ this.identifierToken,
1364
+ importedIdentifiersNames
1365
+ );
1366
+ }
1367
+ isImporting(absoluteFilePath, identifierToken) {
1368
+ if (this.absoluteFilePath !== absoluteFilePath) {
1369
+ return false;
1370
+ }
1371
+ return this.isImportingIdentifier(identifierToken);
1372
+ }
1373
+ isImportingIdentifier(identifierToken) {
1374
+ const identifierName = identifierToken.text;
1375
+ return this.importedIdentifiersNames.includes(identifierName);
1376
+ }
1377
+ };
1378
+
1379
+ // server/parser/token/ASTCollapsionBlock.js
1380
+ import ts from "typescript";
1381
+
1382
+ // server/parser/range/ASTCoordinatesRange.js
1383
+ var ASTCoordinatesRange = class {
1384
+ constructor(coordinates) {
1385
+ this.coordinates = coordinates;
1386
+ }
1387
+ containsLineColumn(line, column) {
1388
+ const startsBefore = this.coordinates.startLine < line || this.coordinates.startLine === line && this.coordinates.startColumn <= column;
1389
+ const endsAfter = this.coordinates.endLine > line || this.coordinates.endLine === line && this.coordinates.endColumn > column;
1390
+ return startsBefore && endsAfter;
1391
+ }
1392
+ containsCoordinates(childCoordinates) {
1393
+ const { startLine, startColumn, endLine, endColumn } = childCoordinates;
1394
+ const result = (this.coordinates.startLine < startLine || this.coordinates.startLine === startLine && this.coordinates.startColumn <= startColumn) && (this.coordinates.endLine > endLine || this.coordinates.endLine === endLine && this.coordinates.endColumn >= endColumn);
1395
+ return result;
1396
+ }
1397
+ containsOrContainedBy(otherCoordinatesRange) {
1398
+ return this.containsCoordinates(otherCoordinatesRange.coordinates) || otherCoordinatesRange.containsCoordinates(this.coordinates);
1399
+ }
1400
+ isEndLineAfter(line) {
1401
+ return this.coordinates.endLine > line;
1402
+ }
1403
+ // TODO: Remove this after removing its usage
1404
+ startsAt(line, column) {
1405
+ return this.coordinates.startLine === line && this.coordinates.startColumn === column;
1406
+ }
1407
+ };
1408
+ var ASTCoordinatesRangeSet = class {
1409
+ constructor(ranges = []) {
1410
+ this.ranges = ranges;
1411
+ }
1412
+ containsLineColumn(line, column) {
1413
+ return this.ranges.some((range) => range.containsLineColumn(line, column));
1414
+ }
1415
+ containsCoordinates(childCoordinates) {
1416
+ return this.ranges.some((range) => range.containsCoordinates(childCoordinates));
1417
+ }
1418
+ containsOrContainedBy(otherCoordinatesRange) {
1419
+ return this.ranges.some((range) => range.containsOrContainedBy(otherCoordinatesRange));
1420
+ }
1421
+ isEndLineAfter(line) {
1422
+ return this.ranges.some((range) => range.isEndLineAfter(line));
1423
+ }
1424
+ // TODO: Remove this after removing its usage
1425
+ startsAt(line, column) {
1426
+ return this.ranges.some((range) => range.startsAt(line, column));
1427
+ }
1428
+ };
1429
+
1430
+ // server/parser/token/ASTCollapsionBlock.js
1431
+ var COLLAPSABLE_NODES_TYPES = [
1432
+ ts.SyntaxKind.FunctionDeclaration,
1433
+ ts.SyntaxKind.PropertyDeclaration,
1434
+ ts.SyntaxKind.PropertyAssignment,
1435
+ ts.SyntaxKind.MethodDeclaration,
1436
+ ts.SyntaxKind.ClassDeclaration,
1437
+ ts.SyntaxKind.VariableDeclaration,
1438
+ ts.SyntaxKind.ImportDeclaration,
1439
+ ts.SyntaxKind.SourceFile,
1440
+ // Additional function types
1441
+ // ts.SyntaxKind.FunctionExpression,
1442
+ // ts.SyntaxKind.ArrowFunction,
1443
+ // Additional class/method types
1444
+ ts.SyntaxKind.MethodSignature,
1445
+ ts.SyntaxKind.Constructor,
1446
+ ts.SyntaxKind.GetAccessor,
1447
+ ts.SyntaxKind.SetAccessor,
1448
+ ts.SyntaxKind.ClassExpression,
1449
+ // TypeScript declarations
1450
+ ts.SyntaxKind.InterfaceDeclaration,
1451
+ ts.SyntaxKind.EnumDeclaration,
1452
+ ts.SyntaxKind.TypeAliasDeclaration,
1453
+ ts.SyntaxKind.ModuleDeclaration,
1454
+ // Variable and import/export
1455
+ // ts.SyntaxKind.VariableStatement,
1456
+ ts.SyntaxKind.ExportDeclaration
1457
+ // Control flow statements
1458
+ // ts.SyntaxKind.IfStatement,
1459
+ // ts.SyntaxKind.ForStatement,
1460
+ // ts.SyntaxKind.ForInStatement,
1461
+ // ts.SyntaxKind.ForOfStatement,
1462
+ // ts.SyntaxKind.WhileStatement,
1463
+ // ts.SyntaxKind.DoStatement,
1464
+ // ts.SyntaxKind.SwitchStatement,
1465
+ // ts.SyntaxKind.TryStatement,
1466
+ // ts.SyntaxKind.CatchClause,
1467
+ // Other
1468
+ // ts.SyntaxKind.Block,
1469
+ // ts.SyntaxKind.FunctionExpression,
1470
+ // ts.SyntaxKind.ArrowFunction,
1471
+ ];
1472
+ var ASTCollapsionBlock = class _ASTCollapsionBlock extends ASTToken {
1473
+ constructor(rawToken, coordinates, text, lineNumber, isExpanded = false, type = null, idToken = null) {
1474
+ super(rawToken);
1475
+ this.coordinates = coordinates;
1476
+ this.text = text;
1477
+ this.lineNumber = lineNumber;
1478
+ this.isExpanded = isExpanded;
1479
+ this.type = type;
1480
+ this.idToken = idToken;
1481
+ this.className = "ASTCollapsionBlock";
1482
+ }
1483
+ isCollapsable() {
1484
+ return COLLAPSABLE_NODES_TYPES.includes(this.rawToken.kind);
1485
+ }
1486
+ static fromJSON(json) {
1487
+ return new _ASTCollapsionBlock(
1488
+ rawTokenFromJSON(json.rawToken),
1489
+ json.coordinates,
1490
+ json.text,
1491
+ json.lineNumber,
1492
+ json.isExpanded,
1493
+ json.type,
1494
+ json.idToken ? ASTMatchedToken.fromJSON(json.idToken) : null
1495
+ );
1496
+ }
1497
+ withType(type) {
1498
+ return new _ASTCollapsionBlock(
1499
+ this.rawToken,
1500
+ this.coordinates,
1501
+ this.text,
1502
+ this.lineNumber,
1503
+ this.isExpanded,
1504
+ type,
1505
+ this.idToken
1506
+ );
1507
+ }
1508
+ toResponseJSON() {
1509
+ return {
1510
+ ...super.toResponseJSON(),
1511
+ coordinates: this.coordinates,
1512
+ isExpanded: this.isExpanded,
1513
+ idToken: this.idToken?.toResponseJSON(),
1514
+ type: this.type
1515
+ };
1516
+ }
1517
+ coordinatesRange() {
1518
+ return new ASTCoordinatesRange(this.coordinates);
1519
+ }
1520
+ isOneLine() {
1521
+ return this.coordinates.startLine === this.coordinates.endLine;
1522
+ }
1523
+ expanded() {
1524
+ return new _ASTCollapsionBlock(
1525
+ this.rawToken,
1526
+ this.coordinates,
1527
+ this.text,
1528
+ this.lineNumber,
1529
+ true,
1530
+ this.type,
1531
+ this.idToken
1532
+ );
1533
+ }
1534
+ findHeadIn(expandedTokens) {
1535
+ return expandedTokens.find((token) => this.isHead(token));
1536
+ }
1537
+ // TODO: Duplicated in src/
1538
+ isHead(token) {
1539
+ if (this.rawToken.kind === ts.SyntaxKind.Constructor) {
1540
+ return this.equals(token);
1541
+ }
1542
+ return this.idToken?.equals(token);
1543
+ }
1544
+ };
1545
+
1546
+ // server/parser/token/ASTMatchedToken.js
1547
+ import ts2 from "typescript";
1548
+ var VIEWABLE_TOKENS_TYPES = [
1549
+ ts2.SyntaxKind.Identifier,
1550
+ ts2.SyntaxKind.PrivateIdentifier,
1551
+ ts2.SyntaxKind.NumericLiteral,
1552
+ ts2.SyntaxKind.StringLiteral,
1553
+ ts2.SyntaxKind.BigIntLiteral,
1554
+ ts2.SyntaxKind.TrueKeyword,
1555
+ ts2.SyntaxKind.FalseKeyword,
1556
+ ts2.SyntaxKind.NullKeyword,
1557
+ ts2.SyntaxKind.UndefinedKeyword,
1558
+ ts2.SyntaxKind.ThisKeyword,
1559
+ ts2.SyntaxKind.SuperKeyword,
1560
+ ts2.SyntaxKind.JsxText,
1561
+ ts2.SyntaxKind.JsxTextAllWhiteSpaces,
1562
+ ts2.SyntaxKind.RegularExpressionLiteral,
1563
+ ts2.SyntaxKind.NoSubstitutionTemplateLiteral,
1564
+ ts2.SyntaxKind.TemplateHead,
1565
+ ts2.SyntaxKind.TemplateMiddle,
1566
+ ts2.SyntaxKind.TemplateTail
1567
+ ];
1568
+ var ContextTokenType = {
1569
+ Parent: "Parent",
1570
+ Neighbor: "Neighbor",
1571
+ DirectParent: "DirectParent",
1572
+ FirstOnLine: "FirstOnLine",
1573
+ LastOnLine: "LastOnLine"
1574
+ };
1575
+ var ASTMatchedToken = class _ASTMatchedToken extends ASTToken {
1576
+ constructor(rawToken, view, type, collapsionBlock, isHidden = false, isManuallyExpanded = false, text = "", lineNumber = 0, parents = null) {
1577
+ super(rawToken, collapsionBlock, isHidden, isManuallyExpanded);
1578
+ this.className = "ASTMatchedToken";
1579
+ this.view = view;
1580
+ this.type = type;
1581
+ this.text = text;
1582
+ this.lineNumber = lineNumber;
1583
+ this.parents = parents;
1584
+ }
1585
+ isViewable() {
1586
+ return VIEWABLE_TOKENS_TYPES.includes(this.rawToken.kind);
1587
+ }
1588
+ withLineNumber(lineNumber) {
1589
+ return new _ASTMatchedToken(
1590
+ this.rawToken,
1591
+ this.view,
1592
+ this.type,
1593
+ this.collapsionBlock,
1594
+ this.isHidden,
1595
+ this.isManuallyExpanded,
1596
+ this.text,
1597
+ lineNumber,
1598
+ this.parents
1599
+ );
1600
+ }
1601
+ withParents(parents) {
1602
+ return new _ASTMatchedToken(
1603
+ this.rawToken,
1604
+ this.view,
1605
+ this.type,
1606
+ this.collapsionBlock,
1607
+ this.isHidden,
1608
+ this.isManuallyExpanded,
1609
+ this.text,
1610
+ this.lineNumber,
1611
+ parents
1612
+ );
1613
+ }
1614
+ withType(type) {
1615
+ return new _ASTMatchedToken(
1616
+ this.rawToken,
1617
+ this.view,
1618
+ type,
1619
+ this.collapsionBlock,
1620
+ this.isHidden,
1621
+ this.isManuallyExpanded,
1622
+ this.text,
1623
+ this.lineNumber,
1624
+ this.parents
1625
+ );
1626
+ }
1627
+ withCollapsionBlock(collapsionBlock) {
1628
+ return new _ASTMatchedToken(
1629
+ this.rawToken,
1630
+ this.view,
1631
+ this.type,
1632
+ collapsionBlock,
1633
+ this.isHidden,
1634
+ this.isManuallyExpanded,
1635
+ this.text,
1636
+ this.lineNumber,
1637
+ this.parents
1638
+ );
1639
+ }
1640
+ withRawToken(rawToken) {
1641
+ return new _ASTMatchedToken(
1642
+ rawToken,
1643
+ this.view,
1644
+ this.type,
1645
+ this.collapsionBlock,
1646
+ this.isHidden,
1647
+ this.isManuallyExpanded,
1648
+ this.text,
1649
+ this.lineNumber,
1650
+ this.parents
1651
+ );
1652
+ }
1653
+ withText(text) {
1654
+ return new _ASTMatchedToken(
1655
+ this.rawToken,
1656
+ this.view,
1657
+ this.type,
1658
+ this.collapsionBlock,
1659
+ this.isHidden,
1660
+ this.isManuallyExpanded,
1661
+ text,
1662
+ this.lineNumber,
1663
+ this.parents
1664
+ );
1665
+ }
1666
+ hidden() {
1667
+ return new _ASTMatchedToken(
1668
+ this.rawToken,
1669
+ this.view,
1670
+ this.type,
1671
+ this.collapsionBlock,
1672
+ true,
1673
+ this.isManuallyExpanded,
1674
+ this.text,
1675
+ this.lineNumber,
1676
+ this.parents
1677
+ );
1678
+ }
1679
+ visible() {
1680
+ return new _ASTMatchedToken(
1681
+ this.rawToken,
1682
+ this.view,
1683
+ this.type,
1684
+ this.collapsionBlock,
1685
+ false,
1686
+ this.isManuallyExpanded,
1687
+ this.text,
1688
+ this.lineNumber,
1689
+ this.parents
1690
+ );
1691
+ }
1692
+ manuallyExpanded() {
1693
+ return new _ASTMatchedToken(
1694
+ this.rawToken,
1695
+ this.view,
1696
+ this.type,
1697
+ this.collapsionBlock,
1698
+ this.isHidden,
1699
+ true,
1700
+ this.text,
1701
+ this.lineNumber,
1702
+ this.parents
1703
+ );
1704
+ }
1705
+ // TODO: Instanciate this properly
1706
+ toASTImportToken() {
1707
+ return new ASTImportToken(this.rawToken);
1708
+ }
1709
+ static fromJSON(json) {
1710
+ return new _ASTMatchedToken(
1711
+ rawTokenFromJSON(json.rawToken),
1712
+ json.view,
1713
+ json.type,
1714
+ json.collapsionBlock && ASTCollapsionBlock.fromJSON(json.collapsionBlock),
1715
+ json.isHidden,
1716
+ json.isManuallyExpanded,
1717
+ json.text,
1718
+ json.lineNumber,
1719
+ json.parents
1720
+ );
1721
+ }
1722
+ };
1723
+
1724
+ // server/parser/token/ASTCommentToken.js
1725
+ var ASTCommentToken = class _ASTCommentToken extends ASTMatchedToken {
1726
+ constructor(rawToken, view, type, collapsionBlock, isHidden, isManuallyExpanded, text, lineNumber, parents) {
1727
+ super(
1728
+ rawToken,
1729
+ view,
1730
+ type,
1731
+ collapsionBlock,
1732
+ isHidden,
1733
+ isManuallyExpanded,
1734
+ text,
1735
+ lineNumber,
1736
+ parents
1737
+ );
1738
+ this.className = "ASTCommentToken";
1739
+ }
1740
+ withCollapsionBlock(collapsionBlock) {
1741
+ return new _ASTCommentToken(
1742
+ this.rawToken,
1743
+ this.view,
1744
+ this.type,
1745
+ collapsionBlock,
1746
+ this.isHidden,
1747
+ this.isManuallyExpanded,
1748
+ this.text,
1749
+ this.lineNumber,
1750
+ this.parents
1751
+ );
1752
+ }
1753
+ withRawToken(rawToken) {
1754
+ return new _ASTCommentToken(
1755
+ rawToken,
1756
+ this.view,
1757
+ this.type,
1758
+ this.collapsionBlock,
1759
+ this.isHidden,
1760
+ this.isManuallyExpanded,
1761
+ this.text,
1762
+ this.lineNumber,
1763
+ this.parents
1764
+ );
1765
+ }
1766
+ withParents(parents) {
1767
+ return new _ASTCommentToken(
1768
+ this.rawToken,
1769
+ this.view,
1770
+ this.type,
1771
+ this.collapsionBlock,
1772
+ this.isHidden,
1773
+ this.isManuallyExpanded,
1774
+ this.text,
1775
+ this.lineNumber,
1776
+ parents
1777
+ );
1778
+ }
1779
+ createIdRawToken() {
1780
+ return {
1781
+ type: "BlockCommentId",
1782
+ start: this.rawToken.start,
1783
+ end: this.rawToken.start + 1,
1784
+ loc: {
1785
+ start: {
1786
+ line: this.rawToken.loc.start.line,
1787
+ column: this.rawToken.loc.start.column
1788
+ },
1789
+ end: {
1790
+ line: this.rawToken.loc.start.line,
1791
+ column: this.rawToken.loc.start.column + 1
1792
+ }
1793
+ }
1794
+ };
1795
+ }
1796
+ endsAt(end) {
1797
+ return this.rawToken.end === end && endsWithNewLine(this.rawToken.value);
1798
+ }
1799
+ endsAtCoordinates(line, column) {
1800
+ return this.rawToken.loc.end.line === line && this.rawToken.loc.end.column === column && endsWithNewLine(this.rawToken.value);
1801
+ }
1802
+ startsAtCoordinates(line, column) {
1803
+ return this.rawToken.loc.start.line === line && this.rawToken.loc.start.column === column;
1804
+ }
1805
+ static chainItem(rawToken, collapsionBlock, text, lineNumber) {
1806
+ return new _ASTCommentToken(
1807
+ rawToken,
1808
+ null,
1809
+ null,
1810
+ collapsionBlock,
1811
+ false,
1812
+ true,
1813
+ text,
1814
+ lineNumber
1815
+ );
1816
+ }
1817
+ static build(token, fullText) {
1818
+ return new _ASTCommentToken(createCommentRawToken(token, fullText));
1819
+ }
1820
+ };
1821
+
1822
+ // server/parser/ast/ASTTokenResolver.js
1823
+ var STRATEGIES = {
1824
+ NewLineToken: "NewLineToken"
1825
+ };
1826
+ var ASTTokenResolver = class {
1827
+ constructor(ast, linesToTokens) {
1828
+ this.ast = ast;
1829
+ this.linesToTokens = linesToTokens;
1830
+ this.line = 0;
1831
+ this.column = 0;
1832
+ this.type = null;
1833
+ }
1834
+ coordinates(rawToken) {
1835
+ const startLoc = this.ast.getLineAndCharacterOfPosition(rawToken.getStart());
1836
+ const endLoc = this.ast.getLineAndCharacterOfPosition(rawToken.getEnd());
1837
+ return {
1838
+ startLine: startLoc.line + 1,
1839
+ startColumn: startLoc.character,
1840
+ endLine: endLoc.line + 1,
1841
+ endColumn: endLoc.character,
1842
+ type: rawToken.kind
1843
+ };
1844
+ }
1845
+ _searchContainerInLine(lineNumber) {
1846
+ const lineTokens = this.linesToTokens.get(lineNumber);
1847
+ if (!lineTokens) {
1848
+ return;
1849
+ }
1850
+ for (let i = lineTokens.length - 1; i >= 0; i--) {
1851
+ const token = lineTokens[i];
1852
+ const coordinatesRange = new ASTCoordinatesRange(this.coordinates(token.rawToken));
1853
+ if (coordinatesRange.containsLineColumn(this.line, this.column)) {
1854
+ if (!this.strategy) {
1855
+ return token;
1856
+ }
1857
+ const isEqual = token.rawToken.kind === this.type && coordinatesRange.startsAt(this.line, this.column);
1858
+ if (isEqual) {
1859
+ continue;
1860
+ }
1861
+ if (this.strategy === STRATEGIES.NewLineToken && coordinatesRange.isEndLineAfter(this.line)) {
1862
+ return token;
1863
+ }
1864
+ }
1865
+ }
1866
+ }
1867
+ _tokenIndexInLine() {
1868
+ let searchLine = this.line;
1869
+ let tokenIndex = null;
1870
+ while (searchLine >= 0 && !tokenIndex) {
1871
+ tokenIndex = this._searchContainerInLine(searchLine);
1872
+ searchLine--;
1873
+ }
1874
+ return this.linesToTokens.get(searchLine + 1)?.indexOf(tokenIndex);
1875
+ }
1876
+ newLineToken(token) {
1877
+ const tokenCoordinates = this.coordinates(token.rawToken);
1878
+ this.line = tokenCoordinates.startLine;
1879
+ this.column = tokenCoordinates.startColumn;
1880
+ this.type = tokenCoordinates.type;
1881
+ this.strategy = STRATEGIES.NewLineToken;
1882
+ return this.token(this.line, this.column);
1883
+ }
1884
+ // TODO: what if target location outside file boundaries
1885
+ token(line, column) {
1886
+ this.line = line;
1887
+ this.column = column;
1888
+ let searchLine = this.line;
1889
+ let token = null;
1890
+ while (searchLine >= 0 && !token) {
1891
+ token = this._searchContainerInLine(searchLine);
1892
+ searchLine--;
1893
+ }
1894
+ return token;
1895
+ }
1896
+ tokenAtStart(start) {
1897
+ const coordinates = this.ast.getLineAndCharacterOfPosition(start);
1898
+ return this.token(coordinates.line + 1, coordinates.character);
1899
+ }
1900
+ tokensInBetween(range) {
1901
+ const tokenCoordinates = this.coordinates(range.startToken.rawToken);
1902
+ this.line = tokenCoordinates.startLine;
1903
+ this.column = tokenCoordinates.startColumn;
1904
+ this.type = tokenCoordinates.type;
1905
+ let searchLine = this.line;
1906
+ let lineTokens = this.linesToTokens.get(searchLine);
1907
+ let tokenIndexCursorIndex = this._tokenIndexInLine();
1908
+ let token = lineTokens?.at(tokenIndexCursorIndex);
1909
+ const result = [];
1910
+ while (token?.rawToken.getStart() <= range.endToken.rawToken.getEnd()) {
1911
+ if (range.containsToken(token)) {
1912
+ result.push(token);
1913
+ }
1914
+ tokenIndexCursorIndex = (tokenIndexCursorIndex + 1) % lineTokens?.length;
1915
+ if (tokenIndexCursorIndex === 0) {
1916
+ searchLine = searchLine + 1;
1917
+ lineTokens = this.linesToTokens.get(searchLine);
1918
+ }
1919
+ token = lineTokens?.at(tokenIndexCursorIndex);
1920
+ }
1921
+ return result;
1922
+ }
1923
+ };
1924
+
1925
+ // server/parser/ast/AST.js
1926
+ import ts4 from "typescript";
1927
+
1928
+ // server/parser/token/ASTCollapsionBlockFactory.js
1929
+ import ts3 from "typescript";
1930
+ var ASTCollapsionBlockFactory = class {
1931
+ constructor(resolver) {
1932
+ this.resolver = resolver;
1933
+ }
1934
+ isCollapsable(node) {
1935
+ return COLLAPSABLE_NODES_TYPES.includes(node.kind);
1936
+ }
1937
+ createInDirectChildren(token) {
1938
+ const result = [];
1939
+ const addCollapsionBlock = (node) => {
1940
+ switch (node.kind) {
1941
+ case ts3.SyntaxKind.ImportDeclaration:
1942
+ if (node.importClause?.name) {
1943
+ result.push(this.createFrom(node));
1944
+ }
1945
+ break;
1946
+ case ts3.SyntaxKind.VariableStatement:
1947
+ ts3.forEachChild(node.declarationList, addCollapsionBlock);
1948
+ break;
1949
+ case ts3.SyntaxKind.ExportDeclaration:
1950
+ if (node.exportClause && node.exportClause.kind === ts3.SyntaxKind.NamedExports) {
1951
+ result.push(this.createFrom(node));
1952
+ }
1953
+ break;
1954
+ case ts3.SyntaxKind.VariableDeclaration:
1955
+ case ts3.SyntaxKind.FunctionDeclaration:
1956
+ case ts3.SyntaxKind.ClassDeclaration:
1957
+ case ts3.SyntaxKind.ClassExpression:
1958
+ case ts3.SyntaxKind.PropertyDeclaration:
1959
+ case ts3.SyntaxKind.PropertyAssignment:
1960
+ case ts3.SyntaxKind.MethodDeclaration:
1961
+ case ts3.SyntaxKind.MethodSignature:
1962
+ case ts3.SyntaxKind.GetAccessor:
1963
+ case ts3.SyntaxKind.SetAccessor:
1964
+ case ts3.SyntaxKind.InterfaceDeclaration:
1965
+ case ts3.SyntaxKind.EnumDeclaration:
1966
+ case ts3.SyntaxKind.TypeAliasDeclaration:
1967
+ case ts3.SyntaxKind.ModuleDeclaration:
1968
+ if (node.name && node.pos !== token.rawToken.pos) {
1969
+ result.push(this.createFrom(node));
1970
+ }
1971
+ break;
1972
+ }
1973
+ };
1974
+ ts3.forEachChild(token.rawToken, addCollapsionBlock);
1975
+ return result;
1976
+ }
1977
+ createIdToken(node) {
1978
+ let result;
1979
+ switch (node.kind) {
1980
+ case ts3.SyntaxKind.VariableDeclaration:
1981
+ case ts3.SyntaxKind.FunctionDeclaration:
1982
+ case ts3.SyntaxKind.ClassDeclaration:
1983
+ case ts3.SyntaxKind.ClassExpression:
1984
+ case ts3.SyntaxKind.PropertyDeclaration:
1985
+ case ts3.SyntaxKind.PropertyAssignment:
1986
+ case ts3.SyntaxKind.MethodDeclaration:
1987
+ case ts3.SyntaxKind.MethodSignature:
1988
+ case ts3.SyntaxKind.GetAccessor:
1989
+ case ts3.SyntaxKind.SetAccessor:
1990
+ case ts3.SyntaxKind.InterfaceDeclaration:
1991
+ case ts3.SyntaxKind.EnumDeclaration:
1992
+ case ts3.SyntaxKind.TypeAliasDeclaration:
1993
+ case ts3.SyntaxKind.ModuleDeclaration:
1994
+ if (node.name) {
1995
+ result = node.name;
1996
+ }
1997
+ break;
1998
+ // TODO: can we collapse the constructor?
1999
+ case ts3.SyntaxKind.Constructor:
2000
+ result = node.getFirstToken();
2001
+ break;
2002
+ case ts3.SyntaxKind.ImportDeclaration:
2003
+ if (node.importClause?.name) {
2004
+ result = node.importClause.name;
2005
+ } else {
2006
+ result = node.importClause?.namedBindings?.elements?.[0]?.name;
2007
+ }
2008
+ break;
2009
+ case ts3.SyntaxKind.ExportDeclaration:
2010
+ if (node.moduleSpecifier) {
2011
+ result = node.moduleSpecifier;
2012
+ }
2013
+ break;
2014
+ case ts3.SyntaxKind.SourceFile:
2015
+ result = null;
2016
+ break;
2017
+ case "Block": {
2018
+ result = new ASTCommentToken(node).createIdRawToken();
2019
+ break;
2020
+ }
2021
+ }
2022
+ if (!result) {
2023
+ return null;
2024
+ }
2025
+ return new ASTMatchedToken(result);
2026
+ }
2027
+ // TODO: Implement this properly
2028
+ createFrom(node) {
2029
+ return new ASTCollapsionBlock(
2030
+ node,
2031
+ this.resolver.coordinates(node),
2032
+ null,
2033
+ 0,
2034
+ false,
2035
+ null,
2036
+ this.createIdToken(node)
2037
+ );
2038
+ }
2039
+ };
2040
+
2041
+ // server/parser/ast/AST.js
2042
+ var AST = class {
2043
+ constructor(ast, linesToTokens, collapsionBlocks, collapsableCollapsionBlocks) {
2044
+ this.ast = ast;
2045
+ this.linesToTokens = linesToTokens;
2046
+ this.collapsionBlocks = collapsionBlocks;
2047
+ this.collapsableCollapsionBlocks = collapsableCollapsionBlocks;
2048
+ this.resolver = new ASTTokenResolver(this.ast, this.linesToTokens);
2049
+ this.collapsionBlockFactory = new ASTCollapsionBlockFactory(this.resolver);
2050
+ }
2051
+ // TODO: Duplicated in ASTBuilder.js
2052
+ nodeId(token) {
2053
+ const node = token.rawToken;
2054
+ return `${node.getStart()}-${node.getEnd()}-${node.kind}`;
2055
+ }
2056
+ // TODO: Remove second arg flag, only for testing now
2057
+ contextTokens(range, includeNonCollapsable = false) {
2058
+ const tokensInBetween = this.resolver.tokensInBetween(range);
2059
+ const rawResult = /* @__PURE__ */ new Map();
2060
+ for (let i = 0; i < tokensInBetween.length; i++) {
2061
+ const eachToken = tokensInBetween[i];
2062
+ for (let j = 0; j < eachToken.parents.length; j++) {
2063
+ const eachParent = eachToken.parents[j];
2064
+ if (eachParent.isCollapsable() || includeNonCollapsable)
2065
+ rawResult.set(this.nodeId(eachParent), eachParent);
2066
+ const collapsionBlocks = this.collapsionBlockFactory.createInDirectChildren(eachParent);
2067
+ for (let k = 0; k < collapsionBlocks.length; k++) {
2068
+ const eachNeighobor = collapsionBlocks[k];
2069
+ if ((eachNeighobor.isCollapsable() || includeNonCollapsable) && !rawResult.has(this.nodeId(eachNeighobor))) {
2070
+ rawResult.set(
2071
+ this.nodeId(eachNeighobor),
2072
+ eachNeighobor.withType(ContextTokenType.Neighbor)
2073
+ );
2074
+ }
2075
+ }
2076
+ }
2077
+ }
2078
+ return Array.from(rawResult.values());
2079
+ }
2080
+ findIdentifierTokenIn(node, identifierName) {
2081
+ switch (node.kind) {
2082
+ case ts4.SyntaxKind.Identifier:
2083
+ if (node.getText() === identifierName) {
2084
+ return this.resolver.tokenAtStart(node.getStart());
2085
+ }
2086
+ break;
2087
+ }
2088
+ return null;
2089
+ }
2090
+ // TODO: Improve this
2091
+ findImportTokenIn(node) {
2092
+ let importString = null, importedIdentifiersNames = [];
2093
+ switch (node.kind) {
2094
+ // TODO: Handle default import: import * as X from 'module'
2095
+ // TODO: export { X } from 'module'
2096
+ case ts4.SyntaxKind.ImportDeclaration:
2097
+ if (this.isImportDeclarationForIdentifier(node)) {
2098
+ importString = node.moduleSpecifier.text;
2099
+ importedIdentifiersNames.push(
2100
+ ...node.importClause.namedBindings.elements.map((el) => el.name.text)
2101
+ );
2102
+ }
2103
+ break;
2104
+ // TODO: Test this well
2105
+ case ts4.SyntaxKind.CallExpression:
2106
+ if (this.isImportExpressionForIdentifier(node)) {
2107
+ importString = node.arguments[0].text;
2108
+ importedIdentifiersNames.push(node.parent.name.text);
2109
+ }
2110
+ break;
2111
+ }
2112
+ if (!importString) {
2113
+ return null;
2114
+ }
2115
+ const token = this.resolver.tokenAtStart(node.getStart());
2116
+ return token.toASTImportToken().withImportString(importString).withImportedIdentifiersNames(importedIdentifiersNames);
2117
+ }
2118
+ // TODO: Improve this
2119
+ isImportDeclarationForIdentifier(node) {
2120
+ let result = false;
2121
+ if (node.importClause && node.moduleSpecifier) {
2122
+ if (node.importClause.namedBindings?.kind === ts4.SyntaxKind.NamedImports) {
2123
+ result = node.importClause.namedBindings.elements.length > 0;
2124
+ }
2125
+ }
2126
+ return result;
2127
+ }
2128
+ // TODO: Handle import() calls
2129
+ // TODO: Improve this
2130
+ isImportExpressionForIdentifier(node) {
2131
+ if (node.expression.kind === ts4.SyntaxKind.Identifier && node.expression.text === "require" && node.arguments.length > 0 && node.arguments[0].kind === ts4.SyntaxKind.StringLiteral) {
2132
+ if (node.parent && node.parent.kind === ts4.SyntaxKind.VariableDeclaration) {
2133
+ const varDecl = node.parent;
2134
+ if (varDecl.name && varDecl.name.kind === ts4.SyntaxKind.Identifier) {
2135
+ return !!varDecl.name.text;
2136
+ }
2137
+ }
2138
+ }
2139
+ }
2140
+ };
2141
+
2142
+ // server/parser/range/ASTTokenRangeFactory.js
2143
+ var ASTTokenRangeFactory = class {
2144
+ constructor(astTokensRange, ast) {
2145
+ this.astTokensRange = astTokensRange;
2146
+ this.ast = ast;
2147
+ }
2148
+ correctBoundaries() {
2149
+ return this.astTokensRange instanceof ASTTokensRangeSet ? new ASTTokensRangeSet(
2150
+ this.astTokensRange.ranges.map(
2151
+ (each) => new ASTTokensRange(
2152
+ this.ast.resolver.tokenAtStart(each.startToken.rawToken.getStart()),
2153
+ this.ast.resolver.tokenAtStart(each.endToken.rawToken.getEnd())
2154
+ )
2155
+ )
2156
+ ) : new ASTTokensRange(
2157
+ this.ast.resolver.tokenAtStart(this.astTokensRange.startToken.rawToken.getStart()),
2158
+ this.ast.resolver.tokenAtStart(this.astTokensRange.endToken.rawToken.getEnd())
2159
+ );
2160
+ }
2161
+ createContextTokens() {
2162
+ return this.astTokensRange instanceof ASTTokensRangeSet ? this.astTokensRange.ranges.flatMap((each) => this.ast.contextTokens(each)) : this.ast.contextTokens(this.astTokensRange);
2163
+ }
2164
+ fromJson() {
2165
+ return this.astTokensRange.ranges ? new ASTTokensRangeSet(
2166
+ this.astTokensRange.ranges.map(
2167
+ (each) => new ASTTokensRange(
2168
+ ASTMatchedToken.fromJSON(each.startToken),
2169
+ ASTMatchedToken.fromJSON(each.endToken)
2170
+ )
2171
+ )
2172
+ ) : new ASTTokensRange(
2173
+ ASTMatchedToken.fromJSON(this.astTokensRange.startToken),
2174
+ ASTMatchedToken.fromJSON(this.astTokensRange.endToken)
2175
+ );
2176
+ }
2177
+ };
2178
+
2179
+ // server/note/ASTNote.js
2180
+ var ASTNote = class _ASTNote {
2181
+ constructor(idx, file, selection, contextTokens = []) {
2182
+ if (!idx) {
2183
+ this.idx = Math.floor(Math.random() * 999999) + 1;
2184
+ } else {
2185
+ this.idx = idx;
2186
+ }
2187
+ this.file = file;
2188
+ this.selection = selection;
2189
+ this.contextTokens = contextTokens;
2190
+ }
2191
+ recreate() {
2192
+ return new _ASTNote(null, this.file, this.selection, this.contextTokens);
2193
+ }
2194
+ takeBoundaryTokensFrom(ast) {
2195
+ const selection = new ASTTokenRangeFactory(this.selection, ast).correctBoundaries();
2196
+ return new _ASTNote(this.idx, this.file, selection, this.contextTokens);
2197
+ }
2198
+ takeContextTokensFrom(ast) {
2199
+ const contextTokens = new ASTTokenRangeFactory(this.selection, ast).createContextTokens();
2200
+ return new _ASTNote(this.idx, this.file, this.selection, contextTokens);
2201
+ }
2202
+ toStorageJSON() {
2203
+ return {
2204
+ idx: this.idx,
2205
+ file: this.file,
2206
+ selection: this.selection.toResponseJSON(),
2207
+ contextTokens: this.contextTokens.map((token) => token.toResponseJSON())
2208
+ };
2209
+ }
2210
+ toResponseJSON() {
2211
+ return {
2212
+ idx: this.idx,
2213
+ file: this.file,
2214
+ selection: this.selection.toResponseJSON(),
2215
+ contextTokens: this.contextTokens.map((token) => token.toResponseJSON())
2216
+ };
2217
+ }
2218
+ static fromJSON(noteObject) {
2219
+ if (!noteObject?.selection && !noteObject?.subNotes?.length) {
2220
+ throw new Error("ASTNote.fromJSON: missing selection: " + JSON.stringify(noteObject));
2221
+ }
2222
+ const selection = new ASTTokenRangeFactory(noteObject.selection).fromJson();
2223
+ return new _ASTNote(noteObject.idx, noteObject.file, selection, noteObject.contextTokens);
2224
+ }
2225
+ };
2226
+ var ASTNote_default = ASTNote;
2227
+
2228
+ // server/note/ASTSelectionBuilder.js
2229
+ var ASTSelectionBuilder = class {
2230
+ constructor(astResolver) {
2231
+ this.astResolver = astResolver;
2232
+ this.startLine = null;
2233
+ this.startColumn = null;
2234
+ this.endLine = null;
2235
+ this.endColumn = null;
2236
+ }
2237
+ startLineIndex() {
2238
+ return this.startLine - 1;
2239
+ }
2240
+ setResolver(astResolver) {
2241
+ this.astResolver = astResolver;
2242
+ }
2243
+ // 2. After parse(), before analyse(), called for new note
2244
+ setCoordinatesFrom(selection) {
2245
+ const resolvedStartCoordinates = this.astResolver.coordinates(selection.startToken.rawToken);
2246
+ const resolvedEndCoordinates = this.astResolver.coordinates(selection.endToken.rawToken);
2247
+ this.startLine = resolvedStartCoordinates.startLine;
2248
+ this.startColumn = resolvedStartCoordinates.startColumn;
2249
+ this.endLine = resolvedEndCoordinates.startLine;
2250
+ this.endColumn = resolvedEndCoordinates.endColumn - 1;
2251
+ }
2252
+ // 2. After parse(), before analyse()
2253
+ shiftCoordinates(oldCoordinates, lineIndex) {
2254
+ this.startLine = lineIndex + 1;
2255
+ this.endLine = lineIndex + 1 + (oldCoordinates.endLine - oldCoordinates.startLine);
2256
+ this.startColumn = oldCoordinates.startColumn;
2257
+ this.endColumn = oldCoordinates.endColumn;
2258
+ }
2259
+ // 4. After analyse()
2260
+ createSelection() {
2261
+ return new ASTTokensRange(
2262
+ this.astResolver.token(this.startLine, this.startColumn),
2263
+ this.astResolver.token(this.endLine, this.endColumn)
2264
+ );
2265
+ }
2266
+ //
2267
+ createCoordinatesJSON() {
2268
+ return {
2269
+ startLine: this.startLine,
2270
+ startColumn: this.startColumn,
2271
+ endLine: this.endLine,
2272
+ endColumn: this.endColumn
2273
+ };
2274
+ }
2275
+ };
2276
+ var ASTSelectionBuilderSet = class {
2277
+ constructor(astResolver) {
2278
+ this.astResolver = astResolver;
2279
+ this.ranges = [];
2280
+ }
2281
+ startLineIndex() {
2282
+ return this.ranges.map((each) => each.startLine);
2283
+ }
2284
+ setResolver(astResolver) {
2285
+ this.astResolver = astResolver;
2286
+ this.ranges.forEach((builder) => builder.setResolver(astResolver));
2287
+ }
2288
+ setCoordinatesFrom(selection) {
2289
+ this.ranges = selection.ranges.map((range) => {
2290
+ const builder = new ASTSelectionBuilder(this.astResolver);
2291
+ builder.setCoordinatesFrom(range);
2292
+ return builder;
2293
+ });
2294
+ }
2295
+ shiftCoordinates(oldCoordinates, lineIndex) {
2296
+ this.ranges = oldCoordinates.ranges.map((rangeCoordinates, i) => {
2297
+ const builder = new ASTSelectionBuilder(this.astResolver);
2298
+ builder.shiftCoordinates(rangeCoordinates, lineIndex[i]);
2299
+ return builder;
2300
+ });
2301
+ }
2302
+ createSelection() {
2303
+ return new ASTTokensRangeSet(this.ranges.map((builder) => builder.createSelection()));
2304
+ }
2305
+ createCoordinatesJSON() {
2306
+ return {
2307
+ ranges: this.ranges.map((builder) => builder.createCoordinatesJSON())
2308
+ };
2309
+ }
2310
+ };
2311
+
2312
+ // server/text/SourceCodeLine.js
2313
+ import { distance } from "fastest-levenshtein";
2314
+ var SourceCodeLine = class {
2315
+ constructor(content) {
2316
+ this.content = content;
2317
+ }
2318
+ isSameAs(otherLineContent) {
2319
+ return this.content === otherLineContent;
2320
+ }
2321
+ isApproxiametly(otherLineContent) {
2322
+ if (this.content.replace(/\s/g, "") === otherLineContent.replace(/\s/g, "")) {
2323
+ return true;
2324
+ }
2325
+ const maxLen = Math.max(this.content.length, otherLineContent.length);
2326
+ if (maxLen === 0) return true;
2327
+ return distance(this.content, otherLineContent) / maxLen <= 0.1;
2328
+ }
2329
+ };
2330
+
2331
+ // server/note/LinesWindow.js
2332
+ var LinesWindow = class _LinesWindow {
2333
+ constructor(coordinates, fileLines, revision) {
2334
+ this.coordinates = coordinates;
2335
+ this.fileLines = fileLines;
2336
+ this.revision = revision;
2337
+ this.linesBefore = [];
2338
+ this.currentLines = [];
2339
+ this.linesAfter = [];
2340
+ }
2341
+ shouldAddLinesContext() {
2342
+ const isNewNote = !this.coordinates;
2343
+ return !isNewNote && !this.currentLines.length;
2344
+ }
2345
+ linesBeforeStartIndex() {
2346
+ return Math.max(0, this.coordinates.startLine - 6);
2347
+ }
2348
+ addLinesContext() {
2349
+ this.linesBefore = this.calculateLinesBefore();
2350
+ this.currentLines = this.calculateCurrentLines();
2351
+ this.linesAfter = this.calculateLinesAfter();
2352
+ }
2353
+ calculateLinesBefore() {
2354
+ return this.fileLines.slice(this.linesBeforeStartIndex(), this.coordinates.startLine - 1);
2355
+ }
2356
+ calculateLinesAfter() {
2357
+ return this.fileLines.slice(this.coordinates.endLine, this.coordinates.endLine + 5);
2358
+ }
2359
+ calculateCurrentLines() {
2360
+ return this.fileLines.slice(this.coordinates.startLine - 1, this.coordinates.endLine);
2361
+ }
2362
+ areCurrentLinesUnchanged() {
2363
+ const newCurrentLines = this.calculateCurrentLines();
2364
+ return Array.isArray(this.currentLines) && Array.isArray(newCurrentLines) && this.currentLines.length === newCurrentLines.length && this.currentLines.every(
2365
+ (line, idx) => new SourceCodeLine(line).isApproxiametly(newCurrentLines[idx])
2366
+ );
2367
+ }
2368
+ newStartLineIndex() {
2369
+ let startIndexOffset = [
2370
+ ...this.calculateLinesBefore(),
2371
+ ...this.calculateCurrentLines(),
2372
+ ...this.calculateLinesAfter()
2373
+ ].indexOf(this.currentLines[0]);
2374
+ if (this.currentLines.length > 0 && startIndexOffset === -1) {
2375
+ return null;
2376
+ }
2377
+ const startIndex = this.linesBeforeStartIndex() + startIndexOffset;
2378
+ const isSame = this.currentLines.every(
2379
+ (line, idx) => new SourceCodeLine(line).isApproxiametly(this.fileLines[startIndex + idx])
2380
+ );
2381
+ if (isSame) {
2382
+ return startIndex;
2383
+ }
2384
+ return null;
2385
+ }
2386
+ setCoordinates(coordinates) {
2387
+ this.coordinates = coordinates;
2388
+ }
2389
+ //
2390
+ toStorageJSON() {
2391
+ return {
2392
+ linesBefore: this.linesBefore,
2393
+ currentLines: this.currentLines,
2394
+ linesAfter: this.linesAfter,
2395
+ revision: this.revision
2396
+ };
2397
+ }
2398
+ static fromJSON(storageJSON, fileLines) {
2399
+ if (storageJSON.startLine) {
2400
+ return new _LinesWindow(storageJSON, fileLines);
2401
+ }
2402
+ const result = new _LinesWindow(storageJSON.coordinates, fileLines, storageJSON.revision);
2403
+ result.linesBefore = storageJSON.linesBefore;
2404
+ result.currentLines = storageJSON.currentLines;
2405
+ result.linesAfter = storageJSON.linesAfter;
2406
+ return result;
2407
+ }
2408
+ };
2409
+ var MultiRangeLinesWindows = class _MultiRangeLinesWindows {
2410
+ constructor(ranges = [], fileLines, revision) {
2411
+ this.ranges = ranges;
2412
+ this.fileLines = fileLines;
2413
+ this.revision = revision;
2414
+ }
2415
+ setFileLines(fileLines) {
2416
+ this.fileLines = fileLines;
2417
+ this.ranges.forEach((each) => each.fileLines = fileLines);
2418
+ }
2419
+ setCoordinates(coordinatesWithRanges) {
2420
+ this.ranges = coordinatesWithRanges.ranges.map((each) => new LinesWindow(each, this.fileLines));
2421
+ }
2422
+ shouldAddLinesContext() {
2423
+ return this.ranges.some((w) => w.shouldAddLinesContext());
2424
+ }
2425
+ addLinesContext() {
2426
+ this.ranges.forEach((each) => each.addLinesContext());
2427
+ }
2428
+ areCurrentLinesUnchanged() {
2429
+ return this.ranges.every((each) => each.areCurrentLinesUnchanged());
2430
+ }
2431
+ newStartLineIndex() {
2432
+ const result = this.ranges.map((each) => each.newStartLineIndex());
2433
+ if (result.every((each) => !each)) {
2434
+ return null;
2435
+ }
2436
+ return result;
2437
+ }
2438
+ toStorageJSON() {
2439
+ return {
2440
+ ranges: this.ranges.map((each) => each.toStorageJSON()),
2441
+ revision: this.revision
2442
+ };
2443
+ }
2444
+ static fromCoordinates(coordinates, fileLines) {
2445
+ return new _MultiRangeLinesWindows(
2446
+ coordinates?.ranges.map((eachCoodinates) => new LinesWindow(eachCoodinates, fileLines)),
2447
+ fileLines
2448
+ );
2449
+ }
2450
+ static fromJSON(storageJSON, fileLines) {
2451
+ const result = new _MultiRangeLinesWindows(
2452
+ storageJSON.coordinates?.ranges.map((each) => LinesWindow.fromJSON(each, fileLines)) ?? [],
2453
+ fileLines,
2454
+ storageJSON.revision
2455
+ );
2456
+ return result;
2457
+ }
2458
+ };
2459
+
2460
+ // server/storage/StoredNote.js
2461
+ var NOTES_TYPES = {
2462
+ NOTE: "NOTE",
2463
+ OUTDATED_NOTE: "OUTDATED_NOTE"
2464
+ };
2465
+ var StoredNote = class _StoredNote {
2466
+ constructor(note, fileLines, shouldRecalculateSelection = false, newLineIndex = 0, subNotes = [], coordinates = null, diff = null) {
2467
+ this.note = note;
2468
+ this.fileLines = fileLines;
2469
+ this.shouldRecalculateSelection = shouldRecalculateSelection;
2470
+ this.newLineIndex = newLineIndex;
2471
+ this.subNotes = subNotes;
2472
+ this.coordinates = coordinates;
2473
+ this.diff = diff;
2474
+ this.type = NOTES_TYPES.NOTE;
2475
+ this.inputValue = null;
2476
+ this.inputPosition = null;
2477
+ const isMultiSelection = this.note.selection instanceof ASTTokensRangeSet;
2478
+ this.linesWindow = isMultiSelection ? MultiRangeLinesWindows.fromCoordinates(coordinates, fileLines) : new LinesWindow(coordinates, fileLines);
2479
+ }
2480
+ get isOutdated() {
2481
+ return this.type === NOTES_TYPES.OUTDATED_NOTE;
2482
+ }
2483
+ // 1. before parse()
2484
+ checkIfUpToDate() {
2485
+ if (!this.coordinates) {
2486
+ this.shouldRecalculateSelection = true;
2487
+ return;
2488
+ }
2489
+ if (this.linesWindow.areCurrentLinesUnchanged()) {
2490
+ this.linesWindow.addLinesContext();
2491
+ return;
2492
+ }
2493
+ const newIndex = this.linesWindow.newStartLineIndex();
2494
+ if (newIndex === null) {
2495
+ this.toOutdatedNote();
2496
+ return;
2497
+ }
2498
+ this.shouldRecalculateSelection = true;
2499
+ this.newLineIndex = newIndex;
2500
+ }
2501
+ // 2. After parse(), before analyse(), called if shouldRecalculateSelection is true
2502
+ createSelectionBuilderFrom(astResolver) {
2503
+ const isNewNote = !this.newLineIndex;
2504
+ if (!isNewNote && !this.coordinates) {
2505
+ throw new Error(
2506
+ "StoredNote#createSelectionBuilderFrom: Coordinates must be provided for existing note"
2507
+ );
2508
+ }
2509
+ const oldCoordinates = this.coordinates;
2510
+ const isMultiSelection = isNewNote ? this.note.selection instanceof ASTTokensRangeSet : oldCoordinates.ranges;
2511
+ this.coordinates = isMultiSelection ? new ASTSelectionBuilderSet(astResolver) : new ASTSelectionBuilder(astResolver);
2512
+ if (isNewNote) {
2513
+ this.coordinates.setCoordinatesFrom(this.note.selection);
2514
+ this.newLineIndex = this.coordinates.startLineIndex();
2515
+ } else {
2516
+ this.coordinates.shiftCoordinates(oldCoordinates, this.newLineIndex);
2517
+ }
2518
+ this.linesWindow.setCoordinates(this.coordinates);
2519
+ }
2520
+ // 3. During analyse(), after parse()
2521
+ coordinatesRange() {
2522
+ const rangeJSON = this.shouldRecalculateSelection ? this.coordinates.createCoordinatesJSON() : this.coordinates;
2523
+ return rangeJSON.ranges ? new ASTCoordinatesRangeSet(rangeJSON.ranges.map((range) => new ASTCoordinatesRange(range))) : new ASTCoordinatesRange(rangeJSON);
2524
+ }
2525
+ // 4. After both analyse() and parse(), called if shouldRecalculateSelection is true
2526
+ recalculateSelectionAndLines(resolver, currentRevision) {
2527
+ this.coordinates.setResolver(resolver);
2528
+ this.note.selection = this.coordinates.createSelection();
2529
+ this.linesWindow.addLinesContext();
2530
+ this.linesWindow.revision = currentRevision;
2531
+ }
2532
+ //
2533
+ toOutdatedNote() {
2534
+ this.type = NOTES_TYPES.OUTDATED_NOTE;
2535
+ }
2536
+ //
2537
+ toStorageJSON() {
2538
+ const isSelectionBuilder = this.coordinates instanceof ASTSelectionBuilder || this.coordinates instanceof ASTSelectionBuilderSet;
2539
+ const coordinates = isSelectionBuilder ? this.coordinates.createCoordinatesJSON() : this.coordinates;
2540
+ return {
2541
+ ...this.note.toStorageJSON(),
2542
+ ...this.linesWindow.toStorageJSON(),
2543
+ coordinates,
2544
+ inputValue: this.inputValue,
2545
+ inputPosition: this.inputPosition,
2546
+ diff: this.diff,
2547
+ subNotes: this.subNotes?.map((eachSubNote) => eachSubNote.toStorageJSON()),
2548
+ type: this.type
2549
+ };
2550
+ }
2551
+ toResponseJSON() {
2552
+ return {
2553
+ ...this.note.toResponseJSON(),
2554
+ inputValue: this.inputValue,
2555
+ inputPosition: this.inputPosition,
2556
+ diff: this.diff,
2557
+ subNotes: this.subNotes.map((eachSubNote) => eachSubNote.toResponseJSON())
2558
+ };
2559
+ }
2560
+ // TODO: Fix this
2561
+ createChildFromDiff(diff) {
2562
+ const note = this.note.recreate();
2563
+ const result = new _StoredNote(note);
2564
+ result.inputValue = diff.title.trim() || "AI suggestion [Diff]";
2565
+ result.diff = diff;
2566
+ return result;
2567
+ }
2568
+ // TODO: Fix this
2569
+ createChildFromAnnotation(annotation) {
2570
+ const note = this.note.recreate();
2571
+ const result = new _StoredNote(note);
2572
+ result.coordinates = this.coordinates;
2573
+ result.inputValue = annotation.title.trim() || "AI suggestion [Annotation]";
2574
+ return result;
2575
+ }
2576
+ static fromJSON(storageJSON, fileLines) {
2577
+ const result = new _StoredNote(ASTNote_default.fromJSON(storageJSON), fileLines);
2578
+ const isMultiSelectionNote = result.linesWindow instanceof MultiRangeLinesWindows;
2579
+ result.linesWindow = isMultiSelectionNote ? MultiRangeLinesWindows.fromJSON(storageJSON, fileLines) : LinesWindow.fromJSON(storageJSON, fileLines);
2580
+ if (fileLines && result.linesWindow.shouldAddLinesContext()) {
2581
+ result.linesWindow.addLinesContext();
2582
+ }
2583
+ result.coordinates = storageJSON.coordinates;
2584
+ result.inputValue = storageJSON.inputValue;
2585
+ result.inputPosition = storageJSON.inputPosition;
2586
+ result.diff = storageJSON.diff;
2587
+ result.subNotes = storageJSON.subNotes?.map(
2588
+ (eachSubNote) => _StoredNote.fromJSON(eachSubNote, fileLines)
2589
+ );
2590
+ return result;
2591
+ }
2592
+ };
2593
+
2594
+ // server/parser/ast/ASTBuilder.js
2595
+ var ASTBuilder = class {
2596
+ constructor(filePath, content, focused) {
2597
+ this.filePath = filePath;
2598
+ this.content = content;
2599
+ this.focused = focused;
2600
+ this.ast = null;
2601
+ this.astTokenResolver = null;
2602
+ this.astCollapsionBlockFactory = null;
2603
+ this.tsCompilerAST = null;
2604
+ this.linesToTokens = /* @__PURE__ */ new Map();
2605
+ this.nodeIdToCollapsionBlock = /* @__PURE__ */ new Map();
2606
+ this.collapsableCollapsionBlocks = /* @__PURE__ */ new Set();
2607
+ this.nodeIdToComment = /* @__PURE__ */ new Map();
2608
+ }
2609
+ // TODO: Duplicated in AST.js
2610
+ nodeId(node) {
2611
+ return `${node.getStart()}-${node.getEnd()}-${node.kind}`;
2612
+ }
2613
+ // TODO: Generalize this, parse <script .../> blocks for all HTML files too
2614
+ extractCodeFromVueFile() {
2615
+ let content = this.content;
2616
+ const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
2617
+ let beforeScript = "";
2618
+ let afterScript = "";
2619
+ if (scriptMatch) {
2620
+ const scriptStart = scriptMatch.index;
2621
+ const scriptEnd = scriptStart + scriptMatch[0].length;
2622
+ const scriptTagStart = content.indexOf("<script", scriptStart);
2623
+ beforeScript = content.slice(0, scriptTagStart + "<script".length);
2624
+ const scriptTagEnd = content.indexOf("</script>", scriptEnd - "</script>".length);
2625
+ afterScript = content.slice(scriptTagEnd);
2626
+ const beforeLines = new TextReader(beforeScript).toEmptyLines();
2627
+ const afterLines = new TextReader(afterScript).toEmptyLines();
2628
+ return beforeLines + scriptMatch[1] + afterLines;
2629
+ }
2630
+ }
2631
+ // TODO: Optimize this (it's called by every node from this.parentsTokens())
2632
+ createCollapsionBlock(node) {
2633
+ const nodeId = this.nodeId(node);
2634
+ let collapsionBlock;
2635
+ if (this.nodeIdToCollapsionBlock.has(nodeId)) {
2636
+ collapsionBlock = this.nodeIdToCollapsionBlock.get(nodeId);
2637
+ } else {
2638
+ collapsionBlock = this.astCollapsionBlockFactory.createFrom(node);
2639
+ }
2640
+ const isExpanded = !this.focused || this.focused.type === NOTES_TYPES.OUTDATED_NOTE || this.focused.coordinatesRange().containsOrContainedBy(collapsionBlock.coordinatesRange());
2641
+ const result = isExpanded ? collapsionBlock.expanded() : collapsionBlock;
2642
+ this.nodeIdToCollapsionBlock.set(nodeId, result);
2643
+ return result;
2644
+ }
2645
+ tokenContext(ancestors) {
2646
+ const node = ancestors.at(-1);
2647
+ let collapsionBlock = node.kind === ts5.SyntaxKind.SourceFile ? this.createCollapsionBlock(node) : null, parents = [];
2648
+ for (let i = ancestors.length - 2; i >= 0; i--) {
2649
+ const eachAncestor = ancestors[i];
2650
+ const ancestorCollapsionBlock = this.createCollapsionBlock(eachAncestor);
2651
+ if (!!collapsionBlock || !this.astCollapsionBlockFactory.isCollapsable(eachAncestor)) {
2652
+ parents.push(ancestorCollapsionBlock.withType(ContextTokenType.Parent));
2653
+ }
2654
+ if (!collapsionBlock && this.astCollapsionBlockFactory.isCollapsable(eachAncestor)) {
2655
+ collapsionBlock = ancestorCollapsionBlock;
2656
+ parents.unshift(ancestorCollapsionBlock.withType(ContextTokenType.DirectParent));
2657
+ this.collapsableCollapsionBlocks.add(ancestorCollapsionBlock);
2658
+ }
2659
+ }
2660
+ return { collapsionBlock, parents };
2661
+ }
2662
+ parse() {
2663
+ let content = "";
2664
+ if (!ANNOTABLE_EXTENSIONS.test(this.filePath)) {
2665
+ content = "";
2666
+ } else if (VUE_JS_EXTENSIONS.test(this.filePath)) {
2667
+ content = this.extractCodeFromVueFile() || this.content;
2668
+ } else {
2669
+ content = this.content;
2670
+ }
2671
+ this.tsCompilerAST = ts5.createSourceFile(
2672
+ this.filePath,
2673
+ content,
2674
+ ts5.ScriptTarget.Latest,
2675
+ true,
2676
+ // TODO: inject these
2677
+ this.filePath.endsWith(".jsx") || this.filePath.endsWith(".tsx") ? ts5.ScriptKind.JSX : this.filePath.endsWith(".ts") || this.filePath.endsWith(".tsx") ? ts5.ScriptKind.TS : ts5.ScriptKind.JS
2678
+ );
2679
+ this.astTokenResolver = new ASTTokenResolver(this.tsCompilerAST);
2680
+ this.astCollapsionBlockFactory = new ASTCollapsionBlockFactory(this.astTokenResolver);
2681
+ }
2682
+ insert(token, lineNumber) {
2683
+ let lineMap = this.linesToTokens.get(lineNumber);
2684
+ if (!lineMap) {
2685
+ lineMap = [];
2686
+ }
2687
+ lineMap.push(token);
2688
+ this.linesToTokens.set(
2689
+ lineNumber,
2690
+ lineMap.sort((a, b) => a.compareCoordinates(b))
2691
+ );
2692
+ }
2693
+ analyse() {
2694
+ const visit = (node, ancestors = []) => {
2695
+ const currentAncestors = [...ancestors, node];
2696
+ const isOwnDirectParent = node.kind === ts5.SyntaxKind.Constructor && ancestors.at(-1)?.kind === ts5.SyntaxKind.ClassDeclaration;
2697
+ const tokenContextAncestors = isOwnDirectParent ? [...currentAncestors, node] : currentAncestors;
2698
+ const tokenContext = this.tokenContext(tokenContextAncestors);
2699
+ const sourceText = this.tsCompilerAST.getFullText();
2700
+ const leading = ts5.getLeadingCommentRanges(sourceText, node.pos) || [];
2701
+ const trailing = ts5.getTrailingCommentRanges(sourceText, node.end) || [];
2702
+ for (const comment of [...leading, ...trailing]) {
2703
+ const start = this.tsCompilerAST.getLineAndCharacterOfPosition(comment.pos);
2704
+ const commentToken = ASTCommentToken.build(
2705
+ comment,
2706
+ sourceText.slice(comment.pos, comment.end)
2707
+ ).withCollapsionBlock(tokenContext.collapsionBlock).withParents(tokenContext.parents);
2708
+ const commentNodeId = this.nodeId(commentToken.rawToken);
2709
+ if (!this.nodeIdToComment.has(commentNodeId)) {
2710
+ this.nodeIdToComment.set(commentNodeId, commentToken);
2711
+ this.insert(commentToken, start.line + 1);
2712
+ }
2713
+ }
2714
+ const lineNumber = this.tsCompilerAST.getLineAndCharacterOfPosition(node.getStart()).line + 1;
2715
+ const token = new ASTMatchedToken(node).withCollapsionBlock(tokenContext.collapsionBlock).withLineNumber(lineNumber).withParents(tokenContext.parents);
2716
+ this.insert(token, lineNumber);
2717
+ ts5.forEachChild(node, (child) => visit(child, currentAncestors));
2718
+ };
2719
+ visit(this.tsCompilerAST, []);
2720
+ this.ast = this.tsCompilerAST;
2721
+ }
2722
+ createAST() {
2723
+ return new AST(
2724
+ this.ast,
2725
+ this.linesToTokens,
2726
+ this.nodeIdToCollapsionBlock.values().toArray(),
2727
+ Array.from(this.collapsableCollapsionBlocks)
2728
+ );
2729
+ }
2730
+ // Used only by tests, does not update focused coordinates
2731
+ buildAST() {
2732
+ this.parse();
2733
+ this.focused?.createSelectionBuilderFrom(this.astTokenResolver);
2734
+ this.analyse();
2735
+ return this.createAST();
2736
+ }
2737
+ };
2738
+
2739
+ // server/linker/ASTs.js
2740
+ var ASTs = class {
2741
+ constructor(absoluteFilePathToASTMap = /* @__PURE__ */ new Map(), fileReader2) {
2742
+ this.absoluteFilePathToASTMap = absoluteFilePathToASTMap;
2743
+ this.fileReader = fileReader2;
2744
+ }
2745
+ get(absoluteFilePath) {
2746
+ const key = `${absoluteFilePath}:${this.fileReader.timeStamp(absoluteFilePath)}`;
2747
+ let result = this.absoluteFilePathToASTMap.get(key);
2748
+ if (!result) {
2749
+ const content = this.fileReader.fileContent(absoluteFilePath);
2750
+ const astBuilder = new ASTBuilder(absoluteFilePath, content);
2751
+ astBuilder.parse();
2752
+ astBuilder.analyse();
2753
+ result = astBuilder.createAST();
2754
+ this.absoluteFilePathToASTMap.set(key, result);
2755
+ }
2756
+ return result;
2757
+ }
2758
+ };
2759
+
2760
+ // server/linker/ASTDependencies.js
2761
+ import path5 from "path";
2762
+ import { cruise } from "dependency-cruiser";
2763
+ var ASTDependencies = class {
2764
+ constructor(environment2, fileCommandLine2, dirCommandLine2) {
2765
+ this.environment = environment2;
2766
+ this.fileCommandLine = fileCommandLine2;
2767
+ this.dirCommandLine = dirCommandLine2;
2768
+ this.dependenciesMap = /* @__PURE__ */ new Map();
2769
+ }
2770
+ // TODO: re-run vs. what to do when files change/added/deleted
2771
+ // TODO: Use try/cach around cruise()
2772
+ async init() {
2773
+ const rootPath = this.environment.absoluteProjectRoot;
2774
+ const ARRAY_OF_FILES_AND_DIRS_TO_CRUISE = ["."];
2775
+ const cruiseOptions = {
2776
+ // includeOnly: 'src',
2777
+ exclude: [...CONFIG_FILES2, ...IGNORED_DIRS],
2778
+ baseDir: rootPath
2779
+ };
2780
+ this.dependenciesMap = /* @__PURE__ */ new Map();
2781
+ const cruiseResult = await cruise(ARRAY_OF_FILES_AND_DIRS_TO_CRUISE, cruiseOptions);
2782
+ await cruiseResult.output.modules.forEach((module) => {
2783
+ this.dependenciesMap.set(
2784
+ path5.join(rootPath, module.source),
2785
+ module.dependencies.map((dep) => ({
2786
+ absolutePath: path5.join(rootPath, dep.resolved),
2787
+ importString: dep.module
2788
+ }))
2789
+ );
2790
+ });
2791
+ }
2792
+ // TODO: Test this well
2793
+ importedFileAbsolutePath(sourceFileAbsolutePath, importString) {
2794
+ return this.dependenciesMap.get(sourceFileAbsolutePath).find((eachDependency) => eachDependency.importString === importString).absolutePath;
2795
+ }
2796
+ // TODO: Test this well
2797
+ ofAbsolutePath(absolutePath) {
2798
+ return Array.from(this.dependenciesMap.keys()).filter(
2799
+ (eachFilePath) => this.dependenciesMap.get(eachFilePath).map((eachDependency) => eachDependency.absolutePath).includes(absolutePath)
2800
+ );
2801
+ }
2802
+ };
2803
+
2804
+ // server/ws/MessageHandler.js
2805
+ import path6 from "path";
2806
+
2807
+ // server/storage/StoredNotes.js
2808
+ var StoredNotes = class {
2809
+ constructor(storedList = [], requestList = []) {
2810
+ this.storedList = storedList;
2811
+ this.requestList = requestList;
2812
+ this.responseList = [];
2813
+ this.newNoteIndex = null;
2814
+ }
2815
+ get newNote() {
2816
+ return this.responseList[this.newNoteIndex];
2817
+ }
2818
+ setNewNote(newNote) {
2819
+ this.responseList[this.newNoteIndex] = newNote;
2820
+ }
2821
+ updateStoredNote(storedNoteJSON, eachRequestNoteJSON) {
2822
+ const result = StoredNote.fromJSON(storedNoteJSON);
2823
+ result.inputValue = eachRequestNoteJSON.inputValue;
2824
+ result.inputPosition = eachRequestNoteJSON.inputPosition;
2825
+ result.diff = eachRequestNoteJSON.diff;
2826
+ result.subNotes = eachRequestNoteJSON.subNotes?.map((eachRequestSubNoteJSON, i) => {
2827
+ if (eachRequestSubNoteJSON.idx === storedNoteJSON.subNotes[i]?.idx) {
2828
+ return this.updateStoredNote(storedNoteJSON.subNotes[i], eachRequestSubNoteJSON);
2829
+ }
2830
+ return StoredNote.fromJSON(eachRequestSubNoteJSON);
2831
+ });
2832
+ return result;
2833
+ }
2834
+ evaluateRequestList() {
2835
+ this.responseList = this.requestList.map((eachRequestNoteJSON, i) => {
2836
+ const storedNoteJSON = this.storedList.find(
2837
+ (eachStoredNoteJSON) => eachStoredNoteJSON.idx === eachRequestNoteJSON.idx
2838
+ );
2839
+ if (storedNoteJSON) {
2840
+ return this.updateStoredNote(storedNoteJSON, eachRequestNoteJSON);
2841
+ }
2842
+ this.newNoteIndex = i;
2843
+ return StoredNote.fromJSON(eachRequestNoteJSON);
2844
+ });
2845
+ }
2846
+ updateNote(newStoredNoteJSON) {
2847
+ return this.storedList.map((eachStoredNoteJSON) => {
2848
+ let isSubNoteFocused = false;
2849
+ const updatedSubNotes = eachStoredNoteJSON.subNotes.map((eachStoredSubNoteJSON) => {
2850
+ if (eachStoredSubNoteJSON.idx === newStoredNoteJSON.idx) {
2851
+ isSubNoteFocused = true;
2852
+ return newStoredNoteJSON;
2853
+ }
2854
+ return eachStoredSubNoteJSON;
2855
+ });
2856
+ if (isSubNoteFocused) {
2857
+ return {
2858
+ ...eachStoredNoteJSON,
2859
+ subNotes: updatedSubNotes
2860
+ };
2861
+ }
2862
+ if (eachStoredNoteJSON.idx !== newStoredNoteJSON.idx) {
2863
+ return eachStoredNoteJSON;
2864
+ }
2865
+ return newStoredNoteJSON;
2866
+ });
2867
+ }
2868
+ };
2869
+
2870
+ // server/controllers/FilesExplorerController.js
2871
+ var FilesExplorerController = class {
2872
+ constructor(environment2, dirCommandLine2) {
2873
+ this.environment = environment2;
2874
+ this.dirCommandLine = dirCommandLine2;
2875
+ }
2876
+ search(query, isRegex, isCaseSensitive) {
2877
+ const files = this.dirCommandLine.search(query, isRegex, isCaseSensitive);
2878
+ return files;
2879
+ }
2880
+ gitModifiedFiles() {
2881
+ return this.environment.gitModifiedFiles();
2882
+ }
2883
+ recentlyModifiedFiles() {
2884
+ return this.environment.recentlyModifiedFiles();
2885
+ }
2886
+ dirContent(absolutePath) {
2887
+ return this.dirCommandLine.dirContent(absolutePath);
2888
+ }
2889
+ };
2890
+
2891
+ // server/linker/ASTFindUsages.js
2892
+ import ts6 from "typescript";
2893
+ var ASTFindUsages = class _ASTFindUsages {
2894
+ constructor(absoluteFilePath, aSTs2, dependencies2) {
2895
+ this.absoluteFilePath = absoluteFilePath;
2896
+ this.aSTs = aSTs2;
2897
+ this.dependencies = dependencies2;
2898
+ this.queryToken = null;
2899
+ }
2900
+ // TODO: Make it immutable
2901
+ withQueryToken(queryToken) {
2902
+ this.queryToken = queryToken;
2903
+ return this;
2904
+ }
2905
+ get ast() {
2906
+ return this.aSTs.get(this.absoluteFilePath);
2907
+ }
2908
+ createFor(absoluteFilePath) {
2909
+ return new _ASTFindUsages(absoluteFilePath, this.aSTs, this.dependencies);
2910
+ }
2911
+ // TODO: Pass tokens instead of strings
2912
+ // TODO: when imported file not identified?
2913
+ // TODO: Improve this
2914
+ // TODO: Improve package from node_modules?
2915
+ // TODO: Test this well
2916
+ referencesTo(importToken) {
2917
+ const firstOrderReferences = this.createFor(importToken.absoluteFilePath).withQueryToken(importToken).references();
2918
+ const transitiveFindUsages = this.dependencies.ofAbsolutePath(importToken.absoluteFilePath).filter((eachAbsolutePath) => eachAbsolutePath !== this.absoluteFilePath).map((eachAbsolutePath) => this.createFor(eachAbsolutePath));
2919
+ let transitiveReferences = /* @__PURE__ */ new Map();
2920
+ for (let eachFindUsages of transitiveFindUsages) {
2921
+ transitiveReferences = [
2922
+ ...transitiveReferences,
2923
+ ...eachFindUsages.withQueryToken(importToken).references()
2924
+ ];
2925
+ }
2926
+ return new Map([...firstOrderReferences, ...transitiveReferences]);
2927
+ }
2928
+ // TODO: Improve this
2929
+ references() {
2930
+ const isImportLookup = this.queryToken instanceof ASTImportToken;
2931
+ const identifierToken = isImportLookup ? this.queryToken.identifierToken : this.queryToken;
2932
+ const identifierName = identifierToken.text;
2933
+ let result = /* @__PURE__ */ new Map();
2934
+ let currentFileReferences = [];
2935
+ let importReferenceFound = isImportLookup && this.absoluteFilePath === this.queryToken.absoluteFilePath;
2936
+ const findReferences = (node) => {
2937
+ const foundIdentifierToken = this.ast.findIdentifierTokenIn(node, identifierName);
2938
+ if (foundIdentifierToken) {
2939
+ currentFileReferences.push(foundIdentifierToken);
2940
+ }
2941
+ let importToken = this.ast.findImportTokenIn(node);
2942
+ if (importToken && importToken.isImportingIdentifier(identifierToken)) {
2943
+ const absoluteFilePath = this.dependencies.importedFileAbsolutePath(
2944
+ this.absoluteFilePath,
2945
+ importToken.importString
2946
+ );
2947
+ if (!isImportLookup) {
2948
+ importToken = importToken.withAbsoluteFilePath(absoluteFilePath).withQueryToken(identifierToken);
2949
+ result = new Map([...result, ...this.referencesTo(importToken)]);
2950
+ } else if (this.queryToken.isImporting(absoluteFilePath, identifierToken)) {
2951
+ importReferenceFound = true;
2952
+ }
2953
+ }
2954
+ ts6.forEachChild(node, (child) => findReferences(child));
2955
+ };
2956
+ findReferences(this.ast.ast);
2957
+ result.set(this.absoluteFilePath, currentFileReferences);
2958
+ if (isImportLookup && !importReferenceFound) {
2959
+ return /* @__PURE__ */ new Map();
2960
+ }
2961
+ return result;
2962
+ }
2963
+ };
2964
+
2965
+ // server/controllers/UsagesController.js
2966
+ var UsagesController = class {
2967
+ constructor(requestToken, absolutePath, dependencies2, aSTs2, fileReader2, dirCommandLine2) {
2968
+ this.requestToken = requestToken;
2969
+ this.absolutePath = absolutePath;
2970
+ this.dependencies = dependencies2;
2971
+ this.aSTs = aSTs2;
2972
+ this.fileReader = fileReader2;
2973
+ this.dirCommandLine = dirCommandLine2;
2974
+ }
2975
+ findASTUsages() {
2976
+ const findUsages = new ASTFindUsages(this.absolutePath, this.aSTs, this.dependencies);
2977
+ const references = findUsages.withQueryToken(this.requestToken).references();
2978
+ return Array.from(references, ([file, tokens]) => [
2979
+ file,
2980
+ tokens.map((eachToken) => ({
2981
+ fileName: file,
2982
+ line: this.fileReader.lineAt(file, eachToken.lineNumber),
2983
+ token: eachToken.toResponseJSON()
2984
+ }))
2985
+ ]);
2986
+ }
2987
+ findTextUsages() {
2988
+ const requestTokenText = this.requestToken.text;
2989
+ const results = /* @__PURE__ */ new Map();
2990
+ const files = this.dirCommandLine.findAllFilesPathsRecursively();
2991
+ for (const file of files) {
2992
+ if (ANNOTABLE_EXTENSIONS.test(file)) {
2993
+ const fileRead = this.fileReader.readCurrent(file);
2994
+ const lines = fileRead.lines;
2995
+ let start = 0;
2996
+ const fileResults = [];
2997
+ lines.forEach((line, idx) => {
2998
+ const lineLeadingSpaceCount = line.length - line.trimStart().length;
2999
+ let col = line.indexOf(requestTokenText);
3000
+ while (col !== -1) {
3001
+ fileResults.push({
3002
+ fileName: file,
3003
+ lineNumber: idx + 1,
3004
+ lineText: line.trim(),
3005
+ lineStart: col - lineLeadingSpaceCount,
3006
+ lineEnd: col - lineLeadingSpaceCount + requestTokenText.length,
3007
+ lineLeadingSpaceCount,
3008
+ start
3009
+ });
3010
+ col = line.indexOf(requestTokenText, col + requestTokenText.length);
3011
+ }
3012
+ start += line.length + 1;
3013
+ });
3014
+ if (fileResults.length > 0) {
3015
+ results.set(file, fileResults);
3016
+ }
3017
+ }
3018
+ }
3019
+ return Array.from(results);
3020
+ }
3021
+ };
3022
+
3023
+ // server/parser/token/ASTUnmatchedText.js
3024
+ var ASTUnmatchedText = class _ASTUnmatchedText extends ASTToken {
3025
+ constructor(text, previousCharacter, view, rawToken, collapsionBlock, isHidden = false, isManuallyExpanded = false, lineNumber = 0, coordinates = null) {
3026
+ super(rawToken, collapsionBlock, isHidden, isManuallyExpanded);
3027
+ this.className = "ASTUnmatchedText";
3028
+ this.text = text;
3029
+ this.previousCharacter = previousCharacter;
3030
+ this.isNewLine = isNewLine(text);
3031
+ this.isSecondNewLine = this.isNewLine && isNewLine(this.previousCharacter);
3032
+ this.view = view;
3033
+ this.lineNumber = lineNumber;
3034
+ this.coordinates = coordinates;
3035
+ }
3036
+ hidden() {
3037
+ return new _ASTUnmatchedText(
3038
+ this.text,
3039
+ this.previousCharacter,
3040
+ this.view,
3041
+ this.rawToken,
3042
+ this.collapsionBlock,
3043
+ true,
3044
+ this.isManuallyExpanded,
3045
+ this.lineNumber,
3046
+ this.coordinates
3047
+ );
3048
+ }
3049
+ visible() {
3050
+ return new _ASTUnmatchedText(
3051
+ this.text,
3052
+ this.previousCharacter,
3053
+ this.view,
3054
+ this.rawToken,
3055
+ this.collapsionBlock,
3056
+ false,
3057
+ this.isManuallyExpanded,
3058
+ this.lineNumber,
3059
+ this.coordinates
3060
+ );
3061
+ }
3062
+ manuallyExpanded() {
3063
+ return new _ASTUnmatchedText(
3064
+ this.text,
3065
+ this.previousCharacter,
3066
+ this.view,
3067
+ this.rawToken,
3068
+ this.collapsionBlock,
3069
+ this.isHidden,
3070
+ true,
3071
+ this.lineNumber,
3072
+ this.coordinates
3073
+ );
3074
+ }
3075
+ static highlightableChainItem(text, rawToken, collapsionBlock, lineNumber, coordinates) {
3076
+ return new _ASTUnmatchedText(
3077
+ text,
3078
+ null,
3079
+ null,
3080
+ rawToken,
3081
+ collapsionBlock,
3082
+ false,
3083
+ false,
3084
+ lineNumber,
3085
+ coordinates
3086
+ );
3087
+ }
3088
+ static nonParsableChainItem(text, lineNumber, coordinates) {
3089
+ return new _ASTUnmatchedText(
3090
+ text,
3091
+ null,
3092
+ null,
3093
+ null,
3094
+ null,
3095
+ false,
3096
+ false,
3097
+ lineNumber,
3098
+ coordinates
3099
+ );
3100
+ }
3101
+ static newLine(previousCharacter, rawToken, collapsionBlock, lineNumber) {
3102
+ return new _ASTUnmatchedText(
3103
+ NEW_LINE_CHARACTER,
3104
+ previousCharacter,
3105
+ null,
3106
+ rawToken,
3107
+ collapsionBlock,
3108
+ false,
3109
+ false,
3110
+ lineNumber
3111
+ );
3112
+ }
3113
+ };
3114
+
3115
+ // server/parser/chain/ASTChain.js
3116
+ var ASTChain = class _ASTChain {
3117
+ constructor(chain, tokensToExpand = [], rawCollapsedBlocks = []) {
3118
+ this.chain = chain;
3119
+ this.tokensToExpand = tokensToExpand;
3120
+ this.rawCollapsedBlocks = rawCollapsedBlocks;
3121
+ this.collapsedBlocks = this.rawCollapsedBlocks.filter(
3122
+ (eachCollapsionBlock) => (
3123
+ // !eachCollapsionBlock.findHeadIn(this.tokensToExpand) &&
3124
+ !this.tokensToExpand.find(
3125
+ (eachToken) => new ASTTokensRange(eachCollapsionBlock, eachCollapsionBlock).containsToken(eachToken)
3126
+ )
3127
+ )
3128
+ );
3129
+ }
3130
+ withExpanded(expandedToken) {
3131
+ return new _ASTChain(
3132
+ this.chain,
3133
+ [...this.tokensToExpand, expandedToken],
3134
+ this.rawCollapsedBlocks
3135
+ );
3136
+ }
3137
+ expand(expandedTokens) {
3138
+ return new _ASTChain(this.chain, expandedTokens, this.rawCollapsedBlocks);
3139
+ }
3140
+ matchingCollapsionBlock(token) {
3141
+ return this.collapsedBlocks.filter((eachCollapsionBlock) => eachCollapsionBlock.range().containsToken(token)).filter((eachCollapsionBlock) => !eachCollapsionBlock.isExpanded).sort((a, b) => a.rawToken.start - b.rawToken.start).at(0);
3142
+ }
3143
+ // TODO: Add tests for cases here
3144
+ tokens() {
3145
+ const result = [];
3146
+ for (const eachToken of this.chain) {
3147
+ const collapsionBlock = this.matchingCollapsionBlock(eachToken);
3148
+ if (collapsionBlock && !collapsionBlock.isHead(eachToken)) {
3149
+ if (collapsionBlock.isOneLine()) {
3150
+ result.push(eachToken);
3151
+ }
3152
+ continue;
3153
+ }
3154
+ if (collapsionBlock && collapsionBlock.isHead(eachToken)) {
3155
+ if (collapsionBlock.isOneLine()) {
3156
+ result.push(eachToken.manuallyExpanded());
3157
+ } else {
3158
+ result.push(eachToken.visible());
3159
+ }
3160
+ continue;
3161
+ }
3162
+ if (eachToken.isHidden || this.tokensToExpand.find((eachTokenToExpand) => eachTokenToExpand.equals(eachToken))) {
3163
+ result.push(eachToken.manuallyExpanded());
3164
+ continue;
3165
+ }
3166
+ result.push(eachToken);
3167
+ }
3168
+ return result;
3169
+ }
3170
+ };
3171
+
3172
+ // server/parser/chain/ASTChainBuilder.js
3173
+ import ts7 from "typescript";
3174
+
3175
+ // server/parser/chain/Chars.js
3176
+ var TRIVIA_CHARS = [
3177
+ "'",
3178
+ '"',
3179
+ "(",
3180
+ ")",
3181
+ "{",
3182
+ "}",
3183
+ ">",
3184
+ "<",
3185
+ "=",
3186
+ ";",
3187
+ ":",
3188
+ ",",
3189
+ ".",
3190
+ "[",
3191
+ "]",
3192
+ "+",
3193
+ "-",
3194
+ "*",
3195
+ "/",
3196
+ "%",
3197
+ "&",
3198
+ "|",
3199
+ "^",
3200
+ "!",
3201
+ "?",
3202
+ "~"
3203
+ ];
3204
+
3205
+ // server/parser/chain/ASTChainBuilder.js
3206
+ var ASTChainBuilder = class {
3207
+ constructor(ast, sourceFile, startLineNumber = 1) {
3208
+ this.ast = ast;
3209
+ this.sourceFile = sourceFile;
3210
+ this.content = sourceFile.content;
3211
+ this.lines = sourceFile.lines;
3212
+ this.linesStarts = sourceFile.linesStarts;
3213
+ this.programTokenInstance = this.findProgramToken();
3214
+ this.start = (this.linesStarts.get(startLineNumber) ?? 0) - 1;
3215
+ this.lineNumber = startLineNumber - 1;
3216
+ this.startToken = this.programTokenInstance;
3217
+ this.lineStart = 0;
3218
+ this.cursor = null;
3219
+ }
3220
+ findProgramToken() {
3221
+ if (!this.ast) return null;
3222
+ let programToken = null;
3223
+ const sortedLineNumbers = this.ast.linesToTokens.keys().toArray().sort((a, b) => Number(a) - Number(b));
3224
+ for (const lineNumber of sortedLineNumbers) {
3225
+ const tokens = this.ast.linesToTokens.get(lineNumber);
3226
+ for (const token of tokens) {
3227
+ if (token.rawToken.kind === ts7.SyntaxKind.SourceFile) {
3228
+ programToken = token;
3229
+ break;
3230
+ }
3231
+ }
3232
+ if (programToken) break;
3233
+ }
3234
+ return programToken;
3235
+ }
3236
+ setCursor(token) {
3237
+ this.cursor = token;
3238
+ }
3239
+ create() {
3240
+ const chain = [];
3241
+ if (this.lineNumber > 0) {
3242
+ this.startToken = this.ast?.resolver.token(this.lineNumber + 1, 0) || this.programTokenInstance;
3243
+ chain.push(this.createNewLineChainItem(true));
3244
+ }
3245
+ this.lines.forEach((eachLine, i) => {
3246
+ this.moveAfterLine();
3247
+ const lineTokens = eachLine.match(/(\s+|[\w$]+|[^\w\s])/g) || [];
3248
+ lineTokens.forEach((eachToken) => {
3249
+ this.setCursor(eachToken);
3250
+ chain.push(this.createChainItem());
3251
+ this.moveAfterToken();
3252
+ });
3253
+ this.setCursor(NEW_LINE_CHARACTER);
3254
+ if (i < this.lines.length - 1) {
3255
+ chain.push(this.createNewLineChainItem());
3256
+ }
3257
+ });
3258
+ return new ASTChain(chain, [], this.ast?.collapsableCollapsionBlocks);
3259
+ }
3260
+ createChainItem() {
3261
+ const coordinates = { start: this.start, end: this.start + this.cursor.length };
3262
+ if (!this.startToken) {
3263
+ return ASTUnmatchedText.nonParsableChainItem(this.cursor, this.lineNumber, coordinates);
3264
+ }
3265
+ if (this.startToken instanceof ASTCommentToken) {
3266
+ return ASTCommentToken.chainItem(
3267
+ this.startToken.rawToken,
3268
+ this.startToken.collapsionBlock,
3269
+ this.cursor,
3270
+ this.lineNumber
3271
+ );
3272
+ }
3273
+ if (TRIVIA_CHARS.includes(this.cursor) || /^\s+$/.test(this.cursor)) {
3274
+ return ASTUnmatchedText.highlightableChainItem(
3275
+ this.cursor,
3276
+ this.startToken.rawToken,
3277
+ this.startToken.collapsionBlock,
3278
+ this.lineNumber,
3279
+ coordinates
3280
+ );
3281
+ }
3282
+ if (this.startToken.isViewable()) {
3283
+ return this.startToken.withText(this.cursor);
3284
+ }
3285
+ return ASTUnmatchedText.highlightableChainItem(
3286
+ this.cursor,
3287
+ this.startToken.rawToken,
3288
+ this.startToken.collapsionBlock,
3289
+ this.lineNumber,
3290
+ coordinates
3291
+ );
3292
+ }
3293
+ createNewLineChainItem(ignorePreviousCharacter = false) {
3294
+ const previousCharacter = ignorePreviousCharacter ? null : this.content[this.start - 1];
3295
+ let token = null;
3296
+ if (this.startToken) {
3297
+ const endLine = this.ast?.ast.getLineAndCharacterOfPosition(this.startToken.rawToken.getEnd()).line + 1;
3298
+ const isTokenEnding = endLine === this.lineNumber;
3299
+ token = isTokenEnding ? this.ast?.resolver.newLineToken(this.startToken) || this.programTokenInstance : this.startToken;
3300
+ }
3301
+ return ASTUnmatchedText.newLine(
3302
+ previousCharacter,
3303
+ token?.rawToken,
3304
+ token?.collapsionBlock,
3305
+ this.lineNumber + 1
3306
+ );
3307
+ }
3308
+ moveAfterToken() {
3309
+ this.start += this.cursor.length;
3310
+ this.lineStart += this.cursor.length;
3311
+ this.startToken = this.ast?.resolver.token(this.lineNumber, this.lineStart) || this.programTokenInstance;
3312
+ }
3313
+ moveAfterLine() {
3314
+ this.lineNumber++;
3315
+ this.start += 1;
3316
+ this.startToken = this.ast?.resolver.token(this.lineNumber, 0) || this.programTokenInstance;
3317
+ this.lineStart = 0;
3318
+ }
3319
+ };
3320
+
3321
+ // server/controllers/parse/ParseFileWorkflow.js
3322
+ var ParseFileWorkflow = class {
3323
+ constructor(filePath, focusedNoteId, sessionId, storage2, fileReader2) {
3324
+ this.filePath = filePath;
3325
+ this.focusedNoteId = focusedNoteId;
3326
+ this.sessionId = sessionId;
3327
+ this.storage = storage2;
3328
+ this.fileReader = fileReader2;
3329
+ this.focused = null;
3330
+ this.ast = null;
3331
+ this.sourceFile = null;
3332
+ this.notes = null;
3333
+ this.storedRemovedNotes = [];
3334
+ }
3335
+ get shouldRecalculateFocusedNote() {
3336
+ return this.focused?.shouldRecalculateSelection;
3337
+ }
3338
+ run() {
3339
+ this.sourceFile = this.fileReader.read(this.filePath);
3340
+ this.loadNotes();
3341
+ this.loadFocusedNote();
3342
+ this.focused?.checkIfUpToDate();
3343
+ if (this.focused?.isOutdated) {
3344
+ this.sourceFile = this.fileReader.readRevision(
3345
+ this.filePath,
3346
+ this.focused.linesWindow.revision
3347
+ );
3348
+ }
3349
+ this.buildAST();
3350
+ if (this.shouldRecalculateFocusedNote) {
3351
+ this.focused.recalculateSelectionAndLines(
3352
+ this.ast.resolver,
3353
+ this.fileReader.calculateRevision(this.filePath)
3354
+ );
3355
+ }
3356
+ this.persistFocusedNote();
3357
+ }
3358
+ loadNotes() {
3359
+ const { list: storedNotes, removedList: storedRemovedNotes } = this.storage.getNotes(
3360
+ null,
3361
+ this.sessionId
3362
+ );
3363
+ this.notes = new StoredNotes(storedNotes);
3364
+ this.storedRemovedNotes = storedRemovedNotes;
3365
+ }
3366
+ loadFocusedNote() {
3367
+ if (!this.focusedNoteId) return;
3368
+ const noteJSON = this.storage.getNoteByIdx(this.focusedNoteId, this.sessionId);
3369
+ this.focused = StoredNote.fromJSON(noteJSON, this.sourceFile.lines);
3370
+ }
3371
+ // TODO: Reuse ASTs.get() to avoid reparsing
3372
+ buildAST() {
3373
+ const astBuilder = new ASTBuilder(this.filePath, this.sourceFile.content, this.focused);
3374
+ astBuilder.parse();
3375
+ if (this.shouldRecalculateFocusedNote) {
3376
+ const astResolver = new ASTTokenResolver(astBuilder.tsCompilerAST);
3377
+ this.focused.createSelectionBuilderFrom(astResolver);
3378
+ }
3379
+ astBuilder.analyse();
3380
+ this.ast = astBuilder.createAST();
3381
+ }
3382
+ // TODO: Why doing this separately (not inside previous if block)?
3383
+ persistFocusedNote() {
3384
+ if (!this.focusedNoteId) return;
3385
+ const toStoreList = this.notes.updateNote(this.focused.toStorageJSON());
3386
+ this.storage.setNotes(toStoreList, this.storedRemovedNotes, this.sessionId);
3387
+ }
3388
+ };
3389
+
3390
+ // server/controllers/parse/ParseFileController.js
3391
+ var ParseFileController = class {
3392
+ constructor(filePath, focusedNoteId, sessionId, storage2, fileReader2) {
3393
+ this.filePath = filePath;
3394
+ this.focusedNoteId = focusedNoteId;
3395
+ this.sessionId = sessionId;
3396
+ this.storage = storage2;
3397
+ this.fileReader = fileReader2;
3398
+ this.focused = null;
3399
+ this.ast = null;
3400
+ this.sourceFile = null;
3401
+ }
3402
+ parse() {
3403
+ const workflow = new ParseFileWorkflow(
3404
+ this.filePath,
3405
+ this.focusedNoteId,
3406
+ this.sessionId,
3407
+ this.storage,
3408
+ this.fileReader
3409
+ );
3410
+ workflow.run();
3411
+ this.focused = workflow.focused;
3412
+ this.ast = workflow.ast;
3413
+ this.sourceFile = workflow.sourceFile;
3414
+ }
3415
+ createNotesContextTokens(_notes) {
3416
+ const notes = _notes || this.storage.getNotes(this.filePath, this.sessionId).list;
3417
+ return notes.filter((eachNote) => eachNote.type !== NOTES_TYPES.OUTDATED_NOTE).map(
3418
+ (eachNote) => eachNote.file === this.filePath ? ASTNote_default.fromJSON(eachNote).takeContextTokensFrom(this.ast) : ASTNote_default.fromJSON(eachNote)
3419
+ );
3420
+ }
3421
+ createFocusedContextTokens() {
3422
+ return this.focused?.note.takeContextTokensFrom(this.ast) ?? null;
3423
+ }
3424
+ tokenizeTextRange(startLine, endLine) {
3425
+ const sourceFile = this.fileReader.readFileRangeFromSourceFile(
3426
+ this.sourceFile,
3427
+ startLine,
3428
+ endLine
3429
+ );
3430
+ const chain = new ASTChainBuilder(null, sourceFile, startLine).create();
3431
+ return chain.tokens();
3432
+ }
3433
+ tokenizeRange(startLine, endLine) {
3434
+ const sourceFile = this.fileReader.readFileRangeFromSourceFile(
3435
+ this.sourceFile,
3436
+ startLine,
3437
+ endLine
3438
+ );
3439
+ const chain = new ASTChainBuilder(this.ast, sourceFile, startLine).create();
3440
+ return chain.tokens();
3441
+ }
3442
+ expandToken(expandedToken, startLine, endLine) {
3443
+ const sourceFile = this.fileReader.readFileRange(this.filePath, startLine, endLine);
3444
+ const chain = new ASTChainBuilder(this.ast, sourceFile, startLine).create();
3445
+ const expandedMatchedToken = ASTMatchedToken.fromJSON(expandedToken);
3446
+ return chain.withExpanded(expandedMatchedToken).tokens();
3447
+ }
3448
+ };
3449
+
3450
+ // server/controllers/parse/ParseFileCache.js
3451
+ var ParseFileCache = class _ParseFileCache {
3452
+ constructor(environment2, cache2, storage2, fileReader2, controllerAsync, fileViewId, filePath, sessionId, focusedNoteId) {
3453
+ this.environment = environment2;
3454
+ this.cache = cache2;
3455
+ this.storage = storage2;
3456
+ this.fileReader = fileReader2;
3457
+ this.controllerAsync = controllerAsync;
3458
+ this.fileViewId = fileViewId;
3459
+ this.filePath = filePath;
3460
+ this.sessionId = sessionId;
3461
+ this.focusedNoteId = focusedNoteId;
3462
+ }
3463
+ // TODO: Make this session-agnostic (now session notes are cached inside the controller between sessions)
3464
+ createCachedFileKey() {
3465
+ return `${this.environment.commitHash || ""}:${this.filePath}:${this.fileReader.timeStamp(this.filePath) || ""}:${this.focusedNoteId || ""}:${this.sessionId}`;
3466
+ }
3467
+ onDoneParsing(cachedFile, fileViewId) {
3468
+ for (const [_, requests] of cachedFile.requestedTextRanges.entries()) {
3469
+ requests.forEach((message) => this.controllerAsync.sendTextResponse(message, cachedFile));
3470
+ }
3471
+ for (const [_, requests] of cachedFile.requestedRanges.entries()) {
3472
+ requests.forEach((message) => this.controllerAsync.sendTokensResponse(message, cachedFile));
3473
+ }
3474
+ const requestsCache = this.cache.get(fileViewId);
3475
+ if (!requestsCache) {
3476
+ return;
3477
+ }
3478
+ if (requestsCache.fileMetadataRequest) {
3479
+ this.controllerAsync.sendFileMetadataResponse(requestsCache.fileMetadataRequest, cachedFile);
3480
+ }
3481
+ if (requestsCache.notesRequest) {
3482
+ this.controllerAsync.sendNotesResponse(requestsCache.notesRequest, cachedFile);
3483
+ }
3484
+ if (requestsCache.focusedNoteRequest) {
3485
+ this.controllerAsync.sendFocusedNoteResponse(requestsCache.focusedNoteRequest, cachedFile);
3486
+ }
3487
+ if (requestsCache.temporalNotesRequest) {
3488
+ this.controllerAsync.sendTemporalNotesResponse(
3489
+ requestsCache.temporalNotesRequest,
3490
+ cachedFile
3491
+ );
3492
+ }
3493
+ }
3494
+ getOrStartParsing() {
3495
+ const key = this.createCachedFileKey();
3496
+ let cachedFile = this.cache.get(key);
3497
+ if (cachedFile) {
3498
+ return cachedFile;
3499
+ }
3500
+ cachedFile = {
3501
+ controller: null,
3502
+ isParsing: false,
3503
+ isParsed: false,
3504
+ requestedRanges: /* @__PURE__ */ new Map(),
3505
+ resolvedRanges: /* @__PURE__ */ new Map(),
3506
+ requestedTextRanges: /* @__PURE__ */ new Map(),
3507
+ resolvedTextRanges: /* @__PURE__ */ new Map()
3508
+ };
3509
+ this.cache.set(key, cachedFile);
3510
+ cachedFile.isParsing = true;
3511
+ cachedFile.controller = new ParseFileController(
3512
+ this.filePath,
3513
+ this.focusedNoteId,
3514
+ this.sessionId,
3515
+ this.storage,
3516
+ this.fileReader
3517
+ );
3518
+ try {
3519
+ cachedFile.controller.parse();
3520
+ } catch (e) {
3521
+ console.error("Error parsing file", this.filePath, e);
3522
+ this.cache.delete(key);
3523
+ return cachedFile;
3524
+ }
3525
+ cachedFile.isParsing = false;
3526
+ cachedFile.isParsed = true;
3527
+ this.onDoneParsing(cachedFile, this.fileViewId);
3528
+ cachedFile.requestedRanges.clear();
3529
+ return cachedFile;
3530
+ }
3531
+ // requests cache
3532
+ requestsCache() {
3533
+ let requestsCache = this.cache.get(this.fileViewId);
3534
+ if (!requestsCache) {
3535
+ requestsCache = {
3536
+ notesRequest: null,
3537
+ focusedNoteRequest: null,
3538
+ temporalNotesRequest: null,
3539
+ fileMetadataRequest: null
3540
+ };
3541
+ this.cache.set(this.fileViewId, requestsCache);
3542
+ }
3543
+ return requestsCache;
3544
+ }
3545
+ setFileMetadataRequest(message) {
3546
+ const requestsCache = this.requestsCache();
3547
+ requestsCache.fileMetadataRequest = message;
3548
+ this.cache.set(this.fileViewId, requestsCache);
3549
+ }
3550
+ setNotesRequest(message) {
3551
+ const requestsCache = this.requestsCache();
3552
+ requestsCache.notesRequest = message;
3553
+ this.cache.set(this.fileViewId, requestsCache);
3554
+ }
3555
+ setFocusedNoteRequest(message) {
3556
+ const requestsCache = this.requestsCache();
3557
+ requestsCache.focusedNoteRequest = message;
3558
+ this.cache.set(this.fileViewId, requestsCache);
3559
+ }
3560
+ setTemporalNotesRequest(message) {
3561
+ const requestsCache = this.requestsCache();
3562
+ requestsCache.temporalNotesRequest = message;
3563
+ this.cache.set(this.fileViewId, requestsCache);
3564
+ }
3565
+ // factories
3566
+ static from(controllerAsync, fileViewId, filePath, sessionId, focusedNoteId) {
3567
+ return new _ParseFileCache(
3568
+ controllerAsync.environment,
3569
+ controllerAsync.cache,
3570
+ controllerAsync.storage,
3571
+ controllerAsync.fileReader,
3572
+ controllerAsync,
3573
+ fileViewId,
3574
+ filePath,
3575
+ sessionId,
3576
+ focusedNoteId
3577
+ );
3578
+ }
3579
+ };
3580
+
3581
+ // server/controllers/parse/ParseFileControllerAsync.js
3582
+ function getRangeKey(startLine, endLine) {
3583
+ return `${startLine}:${endLine}`;
3584
+ }
3585
+ var ParseFileControllerAsync = class {
3586
+ constructor(environment2, send, cache2, storage2, fileReader2) {
3587
+ this.environment = environment2;
3588
+ this.send = send;
3589
+ this.cache = cache2;
3590
+ this.storage = storage2;
3591
+ this.fileReader = fileReader2;
3592
+ }
3593
+ //
3594
+ sendFileMetadataResponse(message, cacheEntry) {
3595
+ const response = {
3596
+ focusedNoteType: cacheEntry.controller.focused?.type || null,
3597
+ linesCount: cacheEntry.controller.sourceFile.linesCount,
3598
+ tokens: cacheEntry.controller.sourceFile.tokensPlaceholders,
3599
+ longestLineLength: cacheEntry.controller.sourceFile.longestLineLength
3600
+ };
3601
+ this.send({ TYPE: MessageTypes_default.GET_FILE_METADATA, id: message.id, data: response });
3602
+ }
3603
+ sendNotesResponse(message, cacheEntry) {
3604
+ const notesWithContext = cacheEntry.controller.createNotesContextTokens();
3605
+ this.send({
3606
+ TYPE: MessageTypes_default.FETCH_NOTES_HIGHLIGHTS,
3607
+ id: message.id,
3608
+ data: {
3609
+ notes: notesWithContext.map((note) => note.toResponseJSON())
3610
+ }
3611
+ });
3612
+ }
3613
+ sendFocusedNoteResponse(message, cacheEntry) {
3614
+ const noteWithContext = cacheEntry.controller.createFocusedContextTokens();
3615
+ this.send({
3616
+ TYPE: MessageTypes_default.FETCH_FOCUSED_NOTE_HIGHLIGHTS,
3617
+ id: message.id,
3618
+ data: {
3619
+ focusedNote: noteWithContext.toResponseJSON()
3620
+ }
3621
+ });
3622
+ }
3623
+ sendTemporalNotesResponse(message, cacheEntry) {
3624
+ const notesWithContext = cacheEntry.controller.createNotesContextTokens(message.temporalNotes);
3625
+ this.send({
3626
+ TYPE: MessageTypes_default.FETCH_TEMPORAL_NOTES_HIGHLIGHTS,
3627
+ id: message.id,
3628
+ data: {
3629
+ notes: notesWithContext.map((note) => note.toResponseJSON())
3630
+ }
3631
+ });
3632
+ }
3633
+ sendTextResponse(message, cacheEntry) {
3634
+ const { startLine, endLine } = message;
3635
+ const rangeKey = getRangeKey(startLine, endLine);
3636
+ let filteredTokens = cacheEntry.resolvedTextRanges.get(rangeKey);
3637
+ if (!filteredTokens) {
3638
+ filteredTokens = cacheEntry.controller.tokenizeTextRange(startLine, endLine);
3639
+ cacheEntry.resolvedTextRanges.set(rangeKey, filteredTokens);
3640
+ }
3641
+ this.send({
3642
+ TYPE: MessageTypes_default.FETCH_TEXT,
3643
+ id: message.id,
3644
+ data: {
3645
+ tokens: filteredTokens.map((token) => token.toResponseJSON())
3646
+ }
3647
+ });
3648
+ }
3649
+ sendTokensResponse(message, cacheEntry) {
3650
+ const { startLine, endLine } = message;
3651
+ const rangeKey = getRangeKey(startLine, endLine);
3652
+ let filteredTokens = cacheEntry.resolvedRanges.get(rangeKey);
3653
+ if (!filteredTokens) {
3654
+ filteredTokens = cacheEntry.controller.tokenizeRange(startLine, endLine);
3655
+ cacheEntry.resolvedRanges.set(rangeKey, filteredTokens);
3656
+ }
3657
+ this.send({
3658
+ TYPE: MessageTypes_default.FETCH_TOKENS,
3659
+ id: message.id,
3660
+ data: {
3661
+ tokens: filteredTokens.map((token) => token.toResponseJSON())
3662
+ }
3663
+ });
3664
+ }
3665
+ // Message handlers
3666
+ onFetchFileMetadataRequest(message) {
3667
+ const { filePath, fileViewId, sessionId, focusedNoteId } = message;
3668
+ const cache2 = ParseFileCache.from(this, fileViewId, filePath, sessionId, focusedNoteId);
3669
+ const cacheEntry = cache2.getOrStartParsing();
3670
+ if (cacheEntry.isParsed) {
3671
+ this.sendFileMetadataResponse(message, cacheEntry);
3672
+ } else {
3673
+ cache2.setFileMetadataRequest(message);
3674
+ }
3675
+ }
3676
+ onFetchTextRequest(message) {
3677
+ const { startLine, endLine, filePath, focusedNoteId, fileViewId, sessionId } = message;
3678
+ const cacheEntry = ParseFileCache.from(
3679
+ this,
3680
+ fileViewId,
3681
+ filePath,
3682
+ sessionId,
3683
+ focusedNoteId
3684
+ ).getOrStartParsing();
3685
+ const rangeKey = getRangeKey(startLine, endLine);
3686
+ if (cacheEntry.isParsed) {
3687
+ this.sendTextResponse(message, cacheEntry);
3688
+ } else {
3689
+ if (!cacheEntry.requestedTextRanges.has(rangeKey)) {
3690
+ cacheEntry.requestedTextRanges.set(rangeKey, []);
3691
+ }
3692
+ cacheEntry.requestedTextRanges.get(rangeKey).push(message);
3693
+ }
3694
+ }
3695
+ onFetchTokensRequest(message) {
3696
+ const { startLine, endLine, filePath, focusedNoteId, fileViewId, sessionId } = message;
3697
+ const cacheEntry = ParseFileCache.from(
3698
+ this,
3699
+ fileViewId,
3700
+ filePath,
3701
+ sessionId,
3702
+ focusedNoteId
3703
+ ).getOrStartParsing();
3704
+ const rangeKey = getRangeKey(startLine, endLine);
3705
+ if (cacheEntry.isParsed) {
3706
+ this.sendTokensResponse(message, cacheEntry);
3707
+ } else {
3708
+ if (!cacheEntry.requestedRanges.has(rangeKey)) {
3709
+ cacheEntry.requestedRanges.set(rangeKey, []);
3710
+ }
3711
+ cacheEntry.requestedRanges.get(rangeKey).push(message);
3712
+ }
3713
+ }
3714
+ onNotesRequest(message) {
3715
+ const { filePath, fileViewId, sessionId, focusedNoteId } = message;
3716
+ const cache2 = ParseFileCache.from(this, fileViewId, filePath, sessionId, focusedNoteId);
3717
+ const cacheEntry = cache2.getOrStartParsing();
3718
+ if (cacheEntry.isParsed) {
3719
+ this.sendNotesResponse(message, cacheEntry);
3720
+ } else {
3721
+ cache2.setNotesRequest(message);
3722
+ }
3723
+ }
3724
+ onFocusedNoteRequest(message) {
3725
+ const { filePath, fileViewId, focusedNoteId, sessionId } = message;
3726
+ const cache2 = ParseFileCache.from(this, fileViewId, filePath, sessionId, focusedNoteId);
3727
+ const cacheEntry = cache2.getOrStartParsing();
3728
+ if (cacheEntry.isParsed) {
3729
+ this.sendFocusedNoteResponse(message, cacheEntry);
3730
+ } else {
3731
+ cache2.setFocusedNoteRequest(message);
3732
+ }
3733
+ }
3734
+ onTemporalNotesRequest(message) {
3735
+ const { filePath, fileViewId, sessionId, focusedNoteId } = message;
3736
+ const cache2 = ParseFileCache.from(this, fileViewId, filePath, sessionId, focusedNoteId);
3737
+ const cacheEntry = cache2.getOrStartParsing();
3738
+ if (cacheEntry.isParsed) {
3739
+ this.sendTemporalNotesResponse(message, cacheEntry);
3740
+ } else {
3741
+ cache2.setTemporalNotesRequest(message);
3742
+ }
3743
+ }
3744
+ onExpandTokenRequest(message) {
3745
+ const { fileViewId, filePath, expandedToken, startLine, endLine, sessionId, focusedNoteId } = message;
3746
+ const cachedEntry = ParseFileCache.from(
3747
+ this,
3748
+ fileViewId,
3749
+ filePath,
3750
+ sessionId,
3751
+ focusedNoteId
3752
+ ).getOrStartParsing();
3753
+ const tokens = cachedEntry.controller.expandToken(expandedToken, startLine, endLine);
3754
+ this.send({
3755
+ TYPE: MessageTypes_default.EXPAND_TOKEN,
3756
+ id: message.id,
3757
+ data: {
3758
+ tokens: tokens.map((token) => token.toResponseJSON())
3759
+ }
3760
+ });
3761
+ }
3762
+ };
3763
+
3764
+ // server/controllers/DiffController.js
3765
+ import { diffLines } from "diff";
3766
+ var DiffController = class {
3767
+ oldContent = null;
3768
+ newContent = null;
3769
+ changes = null;
3770
+ oldContentLinesCount = null;
3771
+ newContentLinesCount = null;
3772
+ ranges = /* @__PURE__ */ new Map();
3773
+ constructor(fileReader2, absoluteFilePath, comparedToHash, currentHash) {
3774
+ this.fileReader = fileReader2;
3775
+ this.absoluteFilePath = absoluteFilePath;
3776
+ this.comparedToHash = comparedToHash;
3777
+ this.currentHash = currentHash;
3778
+ }
3779
+ readContent() {
3780
+ this.oldContent = this.fileReader.fileContent(this.absoluteFilePath, this.comparedToHash);
3781
+ this.newContent = this.fileReader.fileContent(this.absoluteFilePath, this.currentHash);
3782
+ }
3783
+ calculateDiffs() {
3784
+ this.changes = diffLines(this.oldContent, this.newContent);
3785
+ this.oldContentLinesCount = this.oldContent.split("\n").length;
3786
+ this.newContentLinesCount = this.newContent.split("\n").length;
3787
+ this.putChangedRanges();
3788
+ }
3789
+ createRange(oldLine, newLine) {
3790
+ return {
3791
+ [this.currentHash]: { startLine: newLine, endLine: newLine },
3792
+ [this.comparedToHash]: { startLine: oldLine, endLine: oldLine }
3793
+ };
3794
+ }
3795
+ put(range) {
3796
+ const cur = this.currentHash;
3797
+ const comp = this.comparedToHash;
3798
+ range[cur].linesCount = range[cur].endLine - range[cur].startLine + 1;
3799
+ range[comp].linesCount = range[comp].endLine - range[comp].startLine + 1;
3800
+ for (let line = range[cur].startLine; line <= range[cur].endLine; line++) {
3801
+ this.ranges.set(line, range);
3802
+ }
3803
+ }
3804
+ putChangedRanges() {
3805
+ let currentRange = null;
3806
+ let oldLine = 1;
3807
+ let newLine = 1;
3808
+ for (const change of this.changes) {
3809
+ if (!change.added && !change.removed) {
3810
+ if (currentRange) {
3811
+ this.put(currentRange);
3812
+ currentRange = null;
3813
+ }
3814
+ oldLine += change.count;
3815
+ newLine += change.count;
3816
+ continue;
3817
+ }
3818
+ currentRange ??= this.createRange(oldLine, newLine);
3819
+ if (change.removed) {
3820
+ currentRange[this.comparedToHash].endLine = Math.min(
3821
+ oldLine + change.count,
3822
+ this.oldContentLinesCount
3823
+ );
3824
+ oldLine += change.count;
3825
+ }
3826
+ if (change.added) {
3827
+ currentRange[this.currentHash].endLine = Math.min(
3828
+ newLine + change.count,
3829
+ this.newContentLinesCount
3830
+ );
3831
+ newLine += change.count;
3832
+ }
3833
+ }
3834
+ if (currentRange) {
3835
+ this.put(currentRange);
3836
+ }
3837
+ }
3838
+ };
3839
+
3840
+ // server/controllers/ai/CheckAiServerController.js
3841
+ import { createOpencodeClient } from "@opencode-ai/sdk";
3842
+ var CheckAiServerController = class {
3843
+ constructor(agentId = "local_opencode") {
3844
+ this.agentId = agentId;
3845
+ this.baseUrl = LOCAL_OPENCODE_BASE_URL;
3846
+ this.timeout = DEFAULT_OPENCODE_TIMEOUT;
3847
+ }
3848
+ async check() {
3849
+ try {
3850
+ return await this.checkOpenCode();
3851
+ } catch (error) {
3852
+ return {
3853
+ isConnected: false,
3854
+ message: error?.message || "AI server unreachable"
3855
+ };
3856
+ }
3857
+ }
3858
+ async checkOpenCode() {
3859
+ const client = createOpencodeClient({
3860
+ baseUrl: this.baseUrl,
3861
+ responseStyle: "data",
3862
+ throwOnError: true
3863
+ });
3864
+ const session = await client.session.create({
3865
+ body: {
3866
+ title: `${APP_NAME} health check`
3867
+ }
3868
+ });
3869
+ try {
3870
+ await client.session.delete({ path: { id: session.id } });
3871
+ } catch {
3872
+ }
3873
+ return {
3874
+ isConnected: true,
3875
+ message: "OpenCode server reachable"
3876
+ };
3877
+ }
3878
+ };
3879
+
3880
+ // server/controllers/ai/PromptController.js
3881
+ import { createOpencodeClient as createOpencodeClient2 } from "@opencode-ai/sdk";
3882
+
3883
+ // server/ai/PromptResponse.js
3884
+ import { createPatch as createPatch2, parsePatch } from "diff";
3885
+ var PromptResponse = class {
3886
+ constructor(response = {}) {
3887
+ this.response = response;
3888
+ }
3889
+ toJSON() {
3890
+ return {
3891
+ summary: this.response.summary,
3892
+ diffs: this.normalizeDiffs(),
3893
+ annotations: this.normalizeAnnotations()
3894
+ };
3895
+ }
3896
+ normalizeDiffs() {
3897
+ return this.response.diffs?.filter((eachDiff) => eachDiff.description && eachDiff?.diff && eachDiff.updatedCode).map((entry) => {
3898
+ const filePath = entry.filePath || this.request.filePath || "code";
3899
+ const originalCode = this.request.code;
3900
+ const diff = createPatch2(filePath, originalCode, entry.updatedCode, "current", "ai");
3901
+ return { filePath, title: entry.description, diff };
3902
+ });
3903
+ }
3904
+ normalizeAnnotations() {
3905
+ return this.response.annotations?.filter((eachAnnotation) => eachAnnotation?.title).map((annotation) => ({
3906
+ filePath: annotation.filePath || this.request.filePath,
3907
+ title: annotation.title,
3908
+ startLine: Number(annotation.startLine || 1),
3909
+ endLine: Number(annotation.endLine || annotation.startLine || 1)
3910
+ }));
3911
+ }
3912
+ };
3913
+
3914
+ // server/controllers/ai/PromptController.js
3915
+ var PromptController = class {
3916
+ constructor(storage2) {
3917
+ this.storage = storage2;
3918
+ }
3919
+ async evaluate() {
3920
+ throw new Error("PromptController#evaluate must be implemented by subclasses");
3921
+ }
3922
+ systemPrompt() {
3923
+ return [
3924
+ "You are a senior software engineer.",
3925
+ "You help generate safe, production-quality code changes or code annotations that explain.",
3926
+ "Rules:",
3927
+ "- Prefer minimal targeted changes",
3928
+ "- Preserve formatting and conventions",
3929
+ "- Do not remove unrelated code",
3930
+ "- Never include markdown",
3931
+ "- Never include code fences",
3932
+ "- Never explain outside schema",
3933
+ "- Return structured output only",
3934
+ "- If code is unchanged return empty diffs",
3935
+ "Annotations should describe important code changes."
3936
+ ].join("\n");
3937
+ }
3938
+ static create(storage2, agentId) {
3939
+ return new OpenCodePromptController(storage2);
3940
+ }
3941
+ };
3942
+ var OpenCodePromptController = class extends PromptController {
3943
+ constructor(storage2) {
3944
+ super(storage2);
3945
+ this.baseUrl = LOCAL_OPENCODE_BASE_URL;
3946
+ this.timeout = DEFAULT_OPENCODE_TIMEOUT;
3947
+ }
3948
+ async evaluate(request) {
3949
+ const client = createOpencodeClient2({
3950
+ baseUrl: this.baseUrl,
3951
+ responseStyle: "data",
3952
+ throwOnError: true
3953
+ });
3954
+ const session = await client.session.create({
3955
+ body: {
3956
+ title: `${APP_NAME} prompt`
3957
+ }
3958
+ });
3959
+ const sessionId = session.id;
3960
+ const result = await client.session.prompt({
3961
+ path: { id: sessionId },
3962
+ body: {
3963
+ system: this.systemPrompt(),
3964
+ parts: [{ type: "text", text: JSON.stringify(request, null, 2) }],
3965
+ format: {
3966
+ type: "json_schema",
3967
+ schema: openCodePromptResponse
3968
+ }
3969
+ }
3970
+ });
3971
+ client.session.delete({ path: { id: sessionId } });
3972
+ if (result.info.error) {
3973
+ throw new Error(`OpenCode error: ${JSON.stringify(result.info.error)}`);
3974
+ }
3975
+ return new PromptResponse(result.info.structured).toJSON();
3976
+ }
3977
+ };
3978
+ var openCodePromptResponse = {
3979
+ type: "object",
3980
+ properties: {
3981
+ summary: { type: "string", default: "" },
3982
+ diffs: {
3983
+ type: "array",
3984
+ items: {
3985
+ type: "object",
3986
+ properties: {
3987
+ filePath: { type: ["string", "null"] },
3988
+ description: { type: "string", default: "" },
3989
+ diff: { type: ["string", "null"] }
3990
+ },
3991
+ required: ["description"],
3992
+ additionalProperties: true
3993
+ },
3994
+ default: []
3995
+ },
3996
+ annotations: {
3997
+ type: "array",
3998
+ items: {
3999
+ type: "object",
4000
+ properties: {
4001
+ filePath: { type: ["string", "null"] },
4002
+ startLine: { type: "number" },
4003
+ endLine: { type: "number" },
4004
+ title: { type: "string" }
4005
+ },
4006
+ required: ["startLine", "endLine", "title"],
4007
+ additionalProperties: true
4008
+ },
4009
+ default: []
4010
+ }
4011
+ },
4012
+ required: ["summary", "diffs", "annotations"],
4013
+ additionalProperties: true
4014
+ };
4015
+
4016
+ // server/controllers/ai/AugmentNoteController.js
4017
+ var AugmentNoteController = class {
4018
+ constructor(storage2, agentId) {
4019
+ this.storage = storage2;
4020
+ this.agentId = agentId;
4021
+ this.promptController = PromptController.create(storage2, agentId);
4022
+ }
4023
+ buildSubNotes(parentNote, summary = parentNote.inputValue, diffs = [], annotations = []) {
4024
+ const result = [];
4025
+ if (summary) {
4026
+ }
4027
+ for (const eachDiff of diffs) {
4028
+ result.push(parentNote.createChildFromDiff(eachDiff));
4029
+ }
4030
+ for (const eachAnnotation of annotations) {
4031
+ result.push(parentNote.createChildFromAnnotation(eachAnnotation));
4032
+ }
4033
+ return result;
4034
+ }
4035
+ // prompt
4036
+ sanitizeCode(input) {
4037
+ if (typeof input !== "string") {
4038
+ return input;
4039
+ }
4040
+ return input.replace(/(?<=API_KEY\s*=\s*).+/gi, "[REDACTED]").replace(/(?<=SECRET\s*=\s*).+/gi, "[REDACTED]").replace(/(?<=TOKEN\s*=\s*).+/gi, "[REDACTED]").replace(/(?<=PASSWORD\s*=\s*).+/gi, "[REDACTED]");
4041
+ }
4042
+ limitInput(input) {
4043
+ if (input.length <= MAX_INPUT_CHARS) {
4044
+ return input;
4045
+ }
4046
+ return input.slice(0, MAX_INPUT_CHARS) + "\n\n[TRUNCATED]";
4047
+ }
4048
+ // TODO: Fine tune and improve this
4049
+ normalizeRequest(storedNote) {
4050
+ return {
4051
+ filePath: storedNote.note.file,
4052
+ prompt: storedNote.inputValue
4053
+ // currentLines: this.limitInput(this.sanitizeCode(currentLines)),
4054
+ // code: this.limitInput(this.sanitizeCode(request.code)),
4055
+ // language: String(request.language),
4056
+ // selection: this.limitInput(String(request.selection)),
4057
+ // context: this.limitInput(String(request.context)),
4058
+ };
4059
+ }
4060
+ // action
4061
+ async augment(storedNote) {
4062
+ const request = this.normalizeRequest(storedNote);
4063
+ const { summary, diffs, annotations } = await this.promptController.evaluate(
4064
+ request,
4065
+ this.agentId
4066
+ );
4067
+ const subNotes = this.buildSubNotes(storedNote, summary, diffs, annotations);
4068
+ storedNote.subNotes = [...storedNote.subNotes, ...subNotes];
4069
+ return storedNote;
4070
+ }
4071
+ };
4072
+
4073
+ // server/session/SessionNameGenerator.js
4074
+ var SESSION_NAME_ADJECTIVES = [
4075
+ "Bright",
4076
+ "Bold",
4077
+ "Brisk",
4078
+ "Calm",
4079
+ "Cheery",
4080
+ "Clever",
4081
+ "Cosmic",
4082
+ "Cozy",
4083
+ "Curious",
4084
+ "Eager",
4085
+ "Gentle",
4086
+ "Glowing",
4087
+ "Jazzy",
4088
+ "Lucky",
4089
+ "Mellow",
4090
+ "Merry",
4091
+ "Nimble",
4092
+ "Peppy",
4093
+ "Plucky",
4094
+ "Quirky",
4095
+ "Sunny",
4096
+ "Swift",
4097
+ "Zesty"
4098
+ ];
4099
+ var SESSION_NAME_NOUNS = [
4100
+ "Badger",
4101
+ "Comet",
4102
+ "Dolphin",
4103
+ "Falcon",
4104
+ "Fern",
4105
+ "Firefly",
4106
+ "Fox",
4107
+ "Harbor",
4108
+ "Koala",
4109
+ "Lantern",
4110
+ "Lynx",
4111
+ "Meadow",
4112
+ "Otter",
4113
+ "Panda",
4114
+ "Pebble",
4115
+ "Pine",
4116
+ "Robin",
4117
+ "Sparrow",
4118
+ "Star",
4119
+ "Tiger",
4120
+ "Trail",
4121
+ "Willow",
4122
+ "Wren",
4123
+ "Yeti"
4124
+ ];
4125
+ var SESSION_NAME_COMBINATIONS_COUNT = SESSION_NAME_ADJECTIVES.length * SESSION_NAME_NOUNS.length;
4126
+ var SessionNameGenerator = class {
4127
+ constructor(existingNames = /* @__PURE__ */ new Set()) {
4128
+ this.existingNames = existingNames;
4129
+ }
4130
+ generate() {
4131
+ if (this.existingNames.size >= SESSION_NAME_COMBINATIONS_COUNT) {
4132
+ return `Session ${Date.now()}`;
4133
+ }
4134
+ let attempts = 0;
4135
+ while (attempts < SESSION_NAME_COMBINATIONS_COUNT) {
4136
+ const adjectiveIndex = Math.floor(Math.random() * SESSION_NAME_ADJECTIVES.length);
4137
+ const nounIndex = Math.floor(Math.random() * SESSION_NAME_NOUNS.length);
4138
+ const nextName = `${SESSION_NAME_ADJECTIVES[adjectiveIndex]} ${SESSION_NAME_NOUNS[nounIndex]}`;
4139
+ if (!this.existingNames.has(nextName)) {
4140
+ return nextName;
4141
+ }
4142
+ attempts += 1;
4143
+ }
4144
+ return `Session ${Date.now()}`;
4145
+ }
4146
+ };
4147
+
4148
+ // server/ws/MessageHandler.js
4149
+ var MessageHandlerFactory = class {
4150
+ constructor(environment2, dependencies2, cache2, fileCommandLine2, dirCommandLine2, aSTs2, storage2, fileReader2, myPackage2) {
4151
+ this.environment = environment2;
4152
+ this.dependencies = dependencies2;
4153
+ this.cache = cache2;
4154
+ this.fileCommandLine = fileCommandLine2;
4155
+ this.dirCommandLine = dirCommandLine2;
4156
+ this.aSTs = aSTs2;
4157
+ this.storage = storage2;
4158
+ this.fileReader = fileReader2;
4159
+ this.myPackage = myPackage2;
4160
+ }
4161
+ create(message, ws) {
4162
+ return new MessageHandler(
4163
+ message,
4164
+ ws,
4165
+ this.environment,
4166
+ this.dependencies,
4167
+ this.cache,
4168
+ this.fileCommandLine,
4169
+ this.dirCommandLine,
4170
+ this.aSTs,
4171
+ this.storage,
4172
+ this.fileReader,
4173
+ this.myPackage
4174
+ );
4175
+ }
4176
+ };
4177
+ var MessageHandler = class {
4178
+ constructor(message, ws, environment2, dependencies2, cache2, fileCommandLine2, dirCommandLine2, aSTs2, storage2, fileReader2, myPackage2) {
4179
+ this.message = message;
4180
+ this.ws = ws;
4181
+ this.environment = environment2;
4182
+ this.dependencies = dependencies2;
4183
+ this.cache = cache2;
4184
+ this.fileCommandLine = fileCommandLine2;
4185
+ this.dirCommandLine = dirCommandLine2;
4186
+ this.aSTs = aSTs2;
4187
+ this.storage = storage2;
4188
+ this.fileReader = fileReader2;
4189
+ this.myPackage = myPackage2;
4190
+ }
4191
+ send(response) {
4192
+ this.storage.registerDebugEvent("response/be->fe", response);
4193
+ this.ws.send(JSON.stringify(response));
4194
+ }
4195
+ createSession() {
4196
+ const existingSessionNames = new Set(
4197
+ (this.storage.getSessions?.() || []).map((session2) => session2.name)
4198
+ );
4199
+ const sessionNameGenerator = new SessionNameGenerator(existingSessionNames);
4200
+ const defaultSessionName = sessionNameGenerator.generate();
4201
+ const session = this.storage.createSession(defaultSessionName);
4202
+ this.send({ TYPE: MessageTypes_default.CREATE_SESSION, id: this.message.id, data: { session } });
4203
+ }
4204
+ // TODO: infinite scroll, pagination, filtering...
4205
+ getSessions() {
4206
+ this.send({
4207
+ TYPE: MessageTypes_default.GET_SESSIONS,
4208
+ id: this.message.id,
4209
+ data: this.storage.getSessions().slice(0, 10)
4210
+ });
4211
+ }
4212
+ renameSession() {
4213
+ const session = this.storage.renameSession(this.message.sessionId, this.message.name);
4214
+ this.send({ TYPE: MessageTypes_default.RENAME_SESSION, id: this.message.id, data: { session } });
4215
+ }
4216
+ exportSession() {
4217
+ const sessionId = this.message.sessionId;
4218
+ const session = this.storage.getSessionById(sessionId);
4219
+ const { list, removedList } = this.storage.getNotes(null, sessionId);
4220
+ this.send({
4221
+ TYPE: MessageTypes_default.EXPORT_SESSION,
4222
+ id: this.message.id,
4223
+ data: { session, notes: list, removedNotes: removedList }
4224
+ });
4225
+ }
4226
+ importSession() {
4227
+ const { session: sessionData, notes, removedNotes } = this.message;
4228
+ const newSession = this.storage.createSession(sessionData.name);
4229
+ this.storage.setNotes(notes || [], removedNotes || [], newSession.id);
4230
+ this.send({
4231
+ TYPE: MessageTypes_default.IMPORT_SESSION,
4232
+ id: this.message.id,
4233
+ data: { session: newSession }
4234
+ });
4235
+ }
4236
+ checkUpdate() {
4237
+ const response = new FsMetadataController(
4238
+ this.environment,
4239
+ this.fileCommandLine,
4240
+ this.storage,
4241
+ this.myPackage
4242
+ ).getCurrentAndLatestVersions();
4243
+ this.send({ TYPE: MessageTypes_default.CHECK_UPDATE, id: this.message.id, data: response });
4244
+ }
4245
+ findTokenUsages() {
4246
+ const controller = new UsagesController(
4247
+ ASTMatchedToken.fromJSON(this.message.token),
4248
+ this.message.filePath,
4249
+ this.dependencies,
4250
+ this.aSTs,
4251
+ this.fileReader,
4252
+ this.dirCommandLine
4253
+ );
4254
+ const data = {
4255
+ astUsages: controller.findASTUsages(),
4256
+ textUsages: controller.findTextUsages()
4257
+ };
4258
+ this.send({ TYPE: MessageTypes_default.FIND_TOKEN_USAGES, id: this.message.id, data });
4259
+ }
4260
+ projectMetadata() {
4261
+ const response = new FsMetadataController(
4262
+ this.environment,
4263
+ this.fileCommandLine,
4264
+ this.storage,
4265
+ this.myPackage
4266
+ ).getProjectMetadata();
4267
+ this.send({ TYPE: MessageTypes_default.PROJECT_METADATA, id: this.message.id, data: response });
4268
+ }
4269
+ root() {
4270
+ const response = path6.resolve(this.environment.absoluteProjectRoot);
4271
+ this.send({ TYPE: MessageTypes_default.ROOT, id: this.message.id, data: response });
4272
+ }
4273
+ getNotes() {
4274
+ const { list, removedList } = this.storage.getNotes(null, this.message.sessionId);
4275
+ this.send({
4276
+ TYPE: MessageTypes_default.GET_NOTES,
4277
+ id: this.message.id,
4278
+ data: {
4279
+ list: list.map((eachNote) => StoredNote.fromJSON(eachNote).toResponseJSON()),
4280
+ removedList
4281
+ }
4282
+ });
4283
+ }
4284
+ debugTimeline() {
4285
+ this.send({
4286
+ TYPE: MessageTypes_default.DEBUG_TIMELINE,
4287
+ id: this.message.id,
4288
+ data: this.storage.debugTimeline()
4289
+ });
4290
+ }
4291
+ getDebugEventById(id) {
4292
+ this.send({
4293
+ TYPE: MessageTypes_default.GET_DEBUG_EVENT_BY_ID,
4294
+ id: this.message.id,
4295
+ data: this.storage.getDebugEventById(id)
4296
+ });
4297
+ }
4298
+ async promptAi() {
4299
+ try {
4300
+ const promptController = PromptController.create(this.storage, this.message.agentId);
4301
+ const data = await promptController.evaluate(
4302
+ this.message.promptRequest,
4303
+ this.message.agentId
4304
+ );
4305
+ this.send({ TYPE: MessageTypes_default.PROMPT_AI, id: this.message.id, data });
4306
+ } catch (error) {
4307
+ this.send({
4308
+ TYPE: MessageTypes_default.PROMPT_AI,
4309
+ id: this.message.id,
4310
+ error: error.message,
4311
+ callStack: error.stack
4312
+ });
4313
+ }
4314
+ }
4315
+ async checkAiServer() {
4316
+ const data = await new CheckAiServerController(this.message.agentId).check();
4317
+ this.send({ TYPE: MessageTypes_default.CHECK_AI_SERVER, id: this.message.id, data });
4318
+ }
4319
+ async setNotes() {
4320
+ const requestList = this.message.list;
4321
+ const requestRemovedList = this.message.removedList;
4322
+ const sessionId = this.message.sessionId;
4323
+ const { list: storedList } = this.storage.getNotes(null, sessionId);
4324
+ const storedNotes = new StoredNotes(storedList, requestList);
4325
+ storedNotes.evaluateRequestList();
4326
+ if (this.message.agentId) {
4327
+ const augmentController = new AugmentNoteController(this.storage, this.message.agentId);
4328
+ const augmentedNewNote = await augmentController.augment(storedNotes.newNote);
4329
+ storedNotes.setNewNote(augmentedNewNote);
4330
+ }
4331
+ this.storage.setNotes(
4332
+ storedNotes.responseList.map((eachNote) => eachNote.toStorageJSON()),
4333
+ requestRemovedList,
4334
+ sessionId
4335
+ );
4336
+ this.send({ TYPE: MessageTypes_default.SET_NOTES, id: this.message.id });
4337
+ }
4338
+ searchFiles() {
4339
+ const query = this.message.query;
4340
+ const isRegex = this.message.isRegex;
4341
+ const isCaseSensitive = this.message.isCaseSensitive;
4342
+ const response = new FilesExplorerController(this.environment, this.dirCommandLine).search(
4343
+ query,
4344
+ isRegex,
4345
+ isCaseSensitive
4346
+ );
4347
+ this.send({ TYPE: MessageTypes_default.SEARCH_FILES, id: this.message.id, data: response });
4348
+ }
4349
+ recentlyModifiedFiles() {
4350
+ const recentFiles = new FilesExplorerController(
4351
+ this.environment,
4352
+ this.dirCommandLine
4353
+ ).recentlyModifiedFiles();
4354
+ this.send({
4355
+ TYPE: MessageTypes_default.RECENTLY_MODIFIED_FILES,
4356
+ id: this.message.id,
4357
+ data: recentFiles
4358
+ });
4359
+ }
4360
+ gitModifiedFiles() {
4361
+ const gitModifiedFiles = new FilesExplorerController(
4362
+ this.environment,
4363
+ this.dirCommandLine
4364
+ ).gitModifiedFiles();
4365
+ this.send({
4366
+ TYPE: MessageTypes_default.MODIFIED_FILES,
4367
+ id: this.message.id,
4368
+ data: gitModifiedFiles
4369
+ });
4370
+ }
4371
+ dirContent() {
4372
+ const absolutePath = path6.join(this.environment.absoluteProjectRoot, this.message.path);
4373
+ const content = new FilesExplorerController(this.environment, this.dirCommandLine).dirContent(
4374
+ absolutePath
4375
+ );
4376
+ this.send({
4377
+ TYPE: MessageTypes_default.DIR_CONTENT,
4378
+ id: this.message.id,
4379
+ data: content
4380
+ });
4381
+ }
4382
+ setStorageFilePath() {
4383
+ this.storage.setStorageStrategy(this.message.storageStrategy);
4384
+ if (this.message.storageStrategy === StorageStrategy.FilePath) {
4385
+ this.storage.initStoragePath(this.message.storageFilePath);
4386
+ }
4387
+ this.send({
4388
+ TYPE: MessageTypes_default.SET_STORAGE_FILE_PATH,
4389
+ id: this.message.id,
4390
+ data: this.storage.storagePath
4391
+ });
4392
+ }
4393
+ setDarkMode() {
4394
+ const isDarkMode = this.message.isDarkMode;
4395
+ this.storage.setIsDarkMode(isDarkMode);
4396
+ this.send({
4397
+ TYPE: MessageTypes_default.SET_DARK_MODE,
4398
+ id: this.message.id,
4399
+ data: { value: isDarkMode }
4400
+ });
4401
+ }
4402
+ //
4403
+ getFileMetadata() {
4404
+ const environment2 = this.environment.withCommitHash(this.message.commitHash);
4405
+ new ParseFileControllerAsync(
4406
+ environment2,
4407
+ this.send.bind(this),
4408
+ this.cache,
4409
+ this.storage,
4410
+ this.fileReader.withEnvironment(environment2)
4411
+ ).onFetchFileMetadataRequest(this.message);
4412
+ }
4413
+ fetchText() {
4414
+ const environment2 = this.environment.withCommitHash(this.message.commitHash);
4415
+ new ParseFileControllerAsync(
4416
+ environment2,
4417
+ this.send.bind(this),
4418
+ this.cache,
4419
+ this.storage,
4420
+ this.fileReader.withEnvironment(environment2)
4421
+ ).onFetchTextRequest(this.message);
4422
+ }
4423
+ fetchTokens() {
4424
+ const environment2 = this.environment.withCommitHash(this.message.commitHash);
4425
+ new ParseFileControllerAsync(
4426
+ environment2,
4427
+ this.send.bind(this),
4428
+ this.cache,
4429
+ this.storage,
4430
+ this.fileReader.withEnvironment(environment2)
4431
+ ).onFetchTokensRequest(this.message);
4432
+ }
4433
+ fetchNotesHighlights() {
4434
+ const environment2 = this.environment.withCommitHash(this.message.commitHash);
4435
+ new ParseFileControllerAsync(
4436
+ environment2,
4437
+ this.send.bind(this),
4438
+ this.cache,
4439
+ this.storage,
4440
+ this.fileReader.withEnvironment(environment2)
4441
+ ).onNotesRequest(this.message);
4442
+ }
4443
+ fetchFocusedNoteHighlights() {
4444
+ const environment2 = this.environment.withCommitHash(this.message.commitHash);
4445
+ new ParseFileControllerAsync(
4446
+ environment2,
4447
+ this.send.bind(this),
4448
+ this.cache,
4449
+ this.storage,
4450
+ this.fileReader.withEnvironment(environment2)
4451
+ ).onFocusedNoteRequest(this.message);
4452
+ }
4453
+ fetchTemporalNotesHighlights() {
4454
+ const environment2 = this.environment.withCommitHash(this.message.commitHash);
4455
+ new ParseFileControllerAsync(
4456
+ environment2,
4457
+ this.send.bind(this),
4458
+ this.cache,
4459
+ this.storage,
4460
+ this.fileReader.withEnvironment(environment2)
4461
+ ).onTemporalNotesRequest(this.message);
4462
+ }
4463
+ expandToken() {
4464
+ const environment2 = this.environment.withCommitHash(this.message.commitHash);
4465
+ new ParseFileControllerAsync(
4466
+ environment2,
4467
+ this.send.bind(this),
4468
+ this.cache,
4469
+ this.storage,
4470
+ this.fileReader.withEnvironment(environment2)
4471
+ ).onExpandTokenRequest(this.message);
4472
+ }
4473
+ //
4474
+ getCommits() {
4475
+ const data = this.environment.commitMessages(this.message.commitsHashes);
4476
+ this.send({ TYPE: MessageTypes_default.GET_COMMITS, id: this.message.id, data });
4477
+ }
4478
+ getCommitNote() {
4479
+ const { commitHash, comparedToCommitHash } = this.message;
4480
+ const currentCommit = this.environment.commitMessages([commitHash])[0];
4481
+ const changedFiles = this.environment.changedFilesRelativePaths(
4482
+ commitHash,
4483
+ comparedToCommitHash
4484
+ );
4485
+ const data = {
4486
+ inputValue: currentCommit.message,
4487
+ subNotes: changedFiles.map((filePath) => ({
4488
+ inputValue: filePath,
4489
+ file: path6.join(this.environment.absoluteProjectRoot, filePath)
4490
+ }))
4491
+ };
4492
+ this.send({ TYPE: MessageTypes_default.GET_COMMIT_NOTE, id: this.message.id, data });
4493
+ }
4494
+ //
4495
+ diffs() {
4496
+ const controller = new DiffController(
4497
+ this.fileReader,
4498
+ this.message.filePath,
4499
+ this.message.comparedToCommitHash,
4500
+ this.message.currentCommitHash
4501
+ );
4502
+ controller.readContent();
4503
+ controller.calculateDiffs();
4504
+ const data = Array.from(controller.ranges);
4505
+ this.send({ TYPE: MessageTypes_default.DIFF_RANGES, id: this.message.id, data });
4506
+ }
4507
+ getRangeInCommit() {
4508
+ const targetEnvironment = this.environment.withCommitHash(this.message.commitHash);
4509
+ new ParseFileControllerAsync(
4510
+ targetEnvironment,
4511
+ // TODO: Improve this
4512
+ (response) => {
4513
+ this.send({
4514
+ ...response,
4515
+ TYPE: MessageTypes_default.GET_RANGE_IN_COMMIT
4516
+ });
4517
+ },
4518
+ this.cache,
4519
+ this.storage,
4520
+ this.fileReader.withEnvironment(targetEnvironment)
4521
+ ).onFetchTokensRequest(this.message);
4522
+ }
4523
+ };
4524
+
4525
+ // server/index.js
4526
+ import { fileURLToPath } from "url";
4527
+ import { dirname } from "path";
4528
+
4529
+ // server/os/Package.js
4530
+ import path7 from "path";
4531
+ import fs5 from "fs";
4532
+ import { execSync as execSync4 } from "child_process";
4533
+ var Package = class {
4534
+ constructor(environment2, packageDirName2) {
4535
+ this.environment = environment2;
4536
+ this.packageDirName = packageDirName2;
4537
+ }
4538
+ currentVersion() {
4539
+ const pkgPath = path7.join(this.packageDirName, "package.json");
4540
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
4541
+ const currentVersion = pkg.version;
4542
+ return currentVersion;
4543
+ }
4544
+ latestVersion() {
4545
+ if (process.env.NODE_ENV === "development") {
4546
+ return "1";
4547
+ }
4548
+ try {
4549
+ const latestVersion = execSync4(`npm view ${APP_NAME} version`, {
4550
+ encoding: "utf8"
4551
+ }).trim();
4552
+ return latestVersion;
4553
+ } catch (error) {
4554
+ return this.currentVersion();
4555
+ }
4556
+ }
4557
+ };
4558
+
4559
+ // server/storage/Cache.js
4560
+ import { LRUCache } from "lru-cache";
4561
+ var DEFAULT_MAX_SIZE_MB = 200;
4562
+ var DEFAULT_MAX_ITEMS_COUNT = 1e3;
4563
+ var BYTES_PER_MB = 1024 * 1024;
4564
+ var FALLBACK_ITEM_SIZE = 1;
4565
+ var Cache = class {
4566
+ constructor(options = {}) {
4567
+ const maxSizeBytes = (options.maxSizeMB || DEFAULT_MAX_SIZE_MB) * BYTES_PER_MB;
4568
+ this.maxMemoryMB = options.maxSizeMB || DEFAULT_MAX_SIZE_MB;
4569
+ this.lruCache = new LRUCache({
4570
+ max: options.max || DEFAULT_MAX_ITEMS_COUNT,
4571
+ maxSize: maxSizeBytes,
4572
+ // TODO: Why needing this?
4573
+ sizeCalculation: (value) => {
4574
+ try {
4575
+ return JSON.stringify(value).length;
4576
+ } catch {
4577
+ return FALLBACK_ITEM_SIZE;
4578
+ }
4579
+ },
4580
+ updateAgeOnGet: true,
4581
+ updateAgeOnHas: false,
4582
+ allowStale: false
4583
+ // TODO: should we add this?
4584
+ // ttl: 1000 * 60 * 5,
4585
+ });
4586
+ }
4587
+ //
4588
+ set(key, value) {
4589
+ this.lruCache.set(key, value);
4590
+ }
4591
+ get(key) {
4592
+ return this.lruCache.get(key);
4593
+ }
4594
+ has(key) {
4595
+ return this.lruCache.has(key);
4596
+ }
4597
+ delete(key) {
4598
+ this.lruCache.delete(key);
4599
+ }
4600
+ clear() {
4601
+ this.lruCache.clear();
4602
+ }
4603
+ // stats
4604
+ getStats() {
4605
+ const heapUsed = process.memoryUsage().heapUsed;
4606
+ const memoryUsageMB = Math.round(heapUsed / BYTES_PER_MB * 100) / 100;
4607
+ return {
4608
+ size: this.lruCache.size,
4609
+ memoryUsageMB,
4610
+ maxMemoryMB: this.maxMemoryMB,
4611
+ calculatedSizeMB: Math.round(this.lruCache.calculatedSize / BYTES_PER_MB)
4612
+ };
4613
+ }
4614
+ };
4615
+ var Cache_default = Cache;
4616
+
4617
+ // server/index.js
4618
+ console.log(`[${APP_NAME}]: [init...]`);
4619
+ var args = process.argv.slice(2);
4620
+ var packageDirName;
4621
+ if (process.env.NODE_ENV === "development") {
4622
+ const __filename = fileURLToPath(import.meta.url);
4623
+ packageDirName = dirname(__filename);
4624
+ packageDirName = dirname(packageDirName);
4625
+ } else {
4626
+ packageDirName = __dirname;
4627
+ }
4628
+ var launchDir = process.cwd();
4629
+ var defaultPort = 8080;
4630
+ var environment = Environment.parse(args, launchDir, defaultPort);
4631
+ var myPackage = new Package(environment, packageDirName);
4632
+ var fileReader = new FileReader_default(environment);
4633
+ var storage = new Storage(environment.absoluteProjectRoot);
4634
+ var fileCommandLine = new FileCommandLine();
4635
+ var dirCommandLine = new DirCommandLine(environment);
4636
+ var aSTs = new ASTs(/* @__PURE__ */ new Map(), fileReader);
4637
+ var cache = new Cache_default();
4638
+ if (storage.findStoragePath()) {
4639
+ storage.setStorageStrategy(StorageStrategy.FilePath);
4640
+ }
4641
+ storage.init();
4642
+ var storageFilePath = storage.findStoragePath();
4643
+ if (storageFilePath) {
4644
+ console.log(`[${APP_NAME}]: Storage file path: ${storageFilePath}`);
4645
+ }
4646
+ var dependencies = new ASTDependencies(environment, fileCommandLine, dirCommandLine);
4647
+ dependencies.init();
4648
+ var startWebServer = () => {
4649
+ const app = express();
4650
+ app.use(express.static(path8.join(packageDirName)));
4651
+ app.get("/", (req, res) => {
4652
+ res.sendFile(path8.join(packageDirName, "index.html"));
4653
+ });
4654
+ const result = app.listen(environment.port, () => {
4655
+ console.log(`[${APP_NAME}]: [ON]`);
4656
+ const upgradeInfo = new FsMetadataController(
4657
+ environment,
4658
+ fileCommandLine,
4659
+ storage,
4660
+ myPackage
4661
+ ).getCurrentAndLatestVersions();
4662
+ console.log(`[version]: [${upgradeInfo.currentVersion}]`);
4663
+ if (upgradeInfo.currentVersion !== upgradeInfo.latestVersion) {
4664
+ console.log("\n===========================================");
4665
+ console.log("New version available!\n");
4666
+ console.log("Please upgrade to the latest version for new features and bug fixes.");
4667
+ console.log(`Check our upgrade guide at: ${APP_WEB_URLS.UpgradeGuide}`);
4668
+ console.log("===========================================\n");
4669
+ }
4670
+ console.log(`[${APP_NAME}]: Start here: http://localhost:${environment.port}/`);
4671
+ });
4672
+ return result;
4673
+ };
4674
+ var messageHandlerFactory = new MessageHandlerFactory(
4675
+ environment,
4676
+ dependencies,
4677
+ cache,
4678
+ fileCommandLine,
4679
+ dirCommandLine,
4680
+ aSTs,
4681
+ storage,
4682
+ fileReader,
4683
+ myPackage
4684
+ );
4685
+ var webSocketServer = new WebSocketServer2(messageHandlerFactory, storage, cache);
4686
+ webSocketServer.start(startWebServer);