tabminal 3.0.13 → 3.0.15
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/AGENTS.md +5 -0
- package/package.json +1 -1
- package/public/app.js +2754 -259
- package/public/index.html +137 -35
- package/public/styles.css +380 -0
- package/src/acp-manager.mjs +180 -18
- package/src/fs-routes.mjs +148 -39
- package/src/server.mjs +69 -25
- package/src/terminal-manager.mjs +97 -2
- package/src/terminal-session.mjs +24 -1
package/public/app.js
CHANGED
|
@@ -104,13 +104,26 @@ const editorPane = document.getElementById('editor-pane');
|
|
|
104
104
|
const HEARTBEAT_INTERVAL_MS = 1000;
|
|
105
105
|
const RECONNECT_RETRY_MS = 5000;
|
|
106
106
|
const FILE_TREE_REFRESH_INTERVAL_MS = 3000;
|
|
107
|
+
const FILE_VERSION_CHECK_INTERVAL_MS = 3000;
|
|
108
|
+
const AGENT_TRANSCRIPT_INITIAL_VISIBLE_BLOCKS = 100;
|
|
109
|
+
const AGENT_TRANSCRIPT_WINDOW_STEP = 50;
|
|
110
|
+
const AGENT_TRANSCRIPT_FOLLOW_LATEST_TOLERANCE = 5;
|
|
111
|
+
const WORKSPACE_TAB_TITLE_MAX_LENGTH = 20;
|
|
107
112
|
const MAIN_SERVER_ID = 'main';
|
|
108
113
|
const RUNTIME_BOOT_ID_STORAGE_KEY = 'tabminal_runtime_boot_id';
|
|
109
114
|
const WORKSPACE_DEVICE_ID_STORAGE_KEY = 'tabminal_workspace_device_id';
|
|
110
115
|
const RECENT_AGENT_USAGE_STORAGE_KEY = 'tabminal_recent_agent_usage';
|
|
111
116
|
const FILE_WORKSPACE_TAB_PREFIX = 'file:';
|
|
117
|
+
const MARKDOWN_PREVIEW_WORKSPACE_TAB_PREFIX = 'markdown-preview:';
|
|
112
118
|
const AGENT_WORKSPACE_TAB_PREFIX = 'agent:';
|
|
113
119
|
const TERMINAL_WORKSPACE_TAB_KEY = 'terminal:main';
|
|
120
|
+
const SUPPORTED_MARKDOWN_EXTENSIONS = new Set([
|
|
121
|
+
'md',
|
|
122
|
+
'markdown',
|
|
123
|
+
'mkd',
|
|
124
|
+
'mkdn',
|
|
125
|
+
'mdown'
|
|
126
|
+
]);
|
|
114
127
|
const SUPPORTED_IMAGE_EXTENSIONS = new Set([
|
|
115
128
|
'png',
|
|
116
129
|
'jpg',
|
|
@@ -119,6 +132,20 @@ const SUPPORTED_IMAGE_EXTENSIONS = new Set([
|
|
|
119
132
|
'svg',
|
|
120
133
|
'webp'
|
|
121
134
|
]);
|
|
135
|
+
const SUPPORTED_PDF_EXTENSIONS = new Set([
|
|
136
|
+
'pdf'
|
|
137
|
+
]);
|
|
138
|
+
const PDFJS_VERSION = '5.6.205';
|
|
139
|
+
const PDFJS_MODULE_URL = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${PDFJS_VERSION}/build/pdf.min.mjs`;
|
|
140
|
+
const PDFJS_WORKER_URL = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${PDFJS_VERSION}/build/pdf.worker.min.mjs`;
|
|
141
|
+
const MARKDOWN_IT_MODULE_URL = 'https://cdn.jsdelivr.net/npm/markdown-it@14.1.1/+esm';
|
|
142
|
+
const MARKDOWN_TASK_LISTS_MODULE_URL = 'https://cdn.jsdelivr.net/npm/markdown-it-task-lists@2.1.1/+esm';
|
|
143
|
+
const MARKDOWN_KATEX_MODULE_URL = 'https://cdn.jsdelivr.net/npm/@traptitech/markdown-it-katex@3.6.0/+esm';
|
|
144
|
+
const KATEX_MODULE_URL = 'https://cdn.jsdelivr.net/npm/katex@0.16.25/+esm';
|
|
145
|
+
const HIGHLIGHT_JS_MODULE_URL = 'https://cdn.jsdelivr.net/npm/highlight.js@11.11.1/+esm';
|
|
146
|
+
const MARKDOWN_PREVIEW_GITHUB_CSS_URL = 'https://cdn.jsdelivr.net/npm/github-markdown-css@5.8.1/github-markdown-dark.min.css';
|
|
147
|
+
const MARKDOWN_PREVIEW_HIGHLIGHT_CSS_URL = 'https://cdn.jsdelivr.net/npm/highlight.js@11.11.1/styles/github-dark.css';
|
|
148
|
+
const MARKDOWN_PREVIEW_KATEX_CSS_URL = 'https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/katex.min.css';
|
|
122
149
|
const CLOSE_ICON_SVG = '<svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
|
|
123
150
|
const AGENT_ICON_SVG = '<svg viewBox="0 0 24 24" width="17" height="17" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="7" y="7" width="10" height="10" rx="2"></rect><path d="M9 7V5"></path><path d="M15 7V5"></path><path d="M12 17v2"></path><path d="M5 12H3"></path><path d="M21 12h-2"></path><path d="M9 11h.01"></path><path d="M15 11h.01"></path><path d="M9.5 14c.7.67 1.53 1 2.5 1s1.8-.33 2.5-1"></path></svg>';
|
|
124
151
|
const TERMINAL_TAB_ICON_SVG = '<svg viewBox="0 0 24 24" width="15" height="15" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"></rect><path d="m8 10 3 2-3 2"></path><path d="M13 15h4"></path></svg>';
|
|
@@ -137,6 +164,9 @@ const RENAME_ICON_SVG = '<svg viewBox="0 0 24 24" width="14" height="14" stroke=
|
|
|
137
164
|
const DELETE_ICON_SVG = '<svg viewBox="0 0 24 24" width="14" height="14" stroke="currentColor" stroke-width="1.9" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16"></path><path d="M10 11v6"></path><path d="M14 11v6"></path><path d="M6 7l1 12h10l1-12"></path><path d="M9 7V4h6v3"></path></svg>';
|
|
138
165
|
const NEW_FOLDER_ICON_SVG = '<svg viewBox="0 0 24 24" width="14" height="14" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 7.5A2.5 2.5 0 0 1 6 5h4l2 2h6a2.5 2.5 0 0 1 2.5 2.5V17A2.5 2.5 0 0 1 18 19.5H6A2.5 2.5 0 0 1 3.5 17Z"></path><path d="M12 10.5v5"></path><path d="M9.5 13h5"></path></svg>';
|
|
139
166
|
const NEW_FILE_ICON_SVG = '<svg viewBox="0 0 24 24" width="14" height="14" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M7 3.5h7l4 4V20.5H7A2.5 2.5 0 0 1 4.5 18V6A2.5 2.5 0 0 1 7 3.5Z"></path><path d="M14 3.5V8h4"></path><path d="M12 11v6"></path><path d="M9 14h6"></path></svg>';
|
|
167
|
+
const MARKDOWN_PREVIEW_ICON_SVG = '<svg viewBox="0 0 24 24" width="15" height="15" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M3 5.5h18"></path><path d="M3 9.5h18"></path><path d="M5 5.5V18a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V5.5"></path><path d="M9 13h6"></path><path d="M9 16h4"></path></svg>';
|
|
168
|
+
const MARKDOWN_SPLIT_ENABLE_ICON_SVG = '<svg viewBox="0 0 24 24" width="15" height="15" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="5" width="16" height="14" rx="2"></rect><path d="M12 5v14"></path></svg>';
|
|
169
|
+
const MARKDOWN_SPLIT_DISABLE_ICON_SVG = '<svg viewBox="0 0 24 24" width="15" height="15" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="5" width="16" height="14" rx="2"></rect><path d="M12 5v14"></path><path d="m9.25 8.5 5.5 7"></path></svg>';
|
|
140
170
|
const TERMINAL_FONT_FAMILY = '\'Monaspace Neon\', "SF Mono Terminal", '
|
|
141
171
|
+ '"SFMono-Regular", "SF Mono", "JetBrains Mono", Menlo, Consolas, '
|
|
142
172
|
+ 'monospace';
|
|
@@ -161,12 +191,18 @@ const agentSetupState = {
|
|
|
161
191
|
};
|
|
162
192
|
let primaryServerBootId = '';
|
|
163
193
|
let runtimeReloadScheduled = false;
|
|
194
|
+
let pdfJsLibPromise = null;
|
|
195
|
+
let markdownPreviewBundlePromise = null;
|
|
164
196
|
// #endregion
|
|
165
197
|
|
|
166
198
|
function makeFileWorkspaceTabKey(filePath) {
|
|
167
199
|
return `${FILE_WORKSPACE_TAB_PREFIX}${filePath}`;
|
|
168
200
|
}
|
|
169
201
|
|
|
202
|
+
function makeMarkdownPreviewWorkspaceTabKey(filePath) {
|
|
203
|
+
return `${MARKDOWN_PREVIEW_WORKSPACE_TAB_PREFIX}${filePath}`;
|
|
204
|
+
}
|
|
205
|
+
|
|
170
206
|
function makeAgentTabKey(serverId, tabId) {
|
|
171
207
|
return `${AGENT_WORKSPACE_TAB_PREFIX}${serverId}:${tabId}`;
|
|
172
208
|
}
|
|
@@ -182,7 +218,15 @@ function isTerminalWorkspaceTabKey(key) {
|
|
|
182
218
|
|
|
183
219
|
function isFileWorkspaceTabKey(key) {
|
|
184
220
|
return typeof key === 'string'
|
|
185
|
-
&&
|
|
221
|
+
&& (
|
|
222
|
+
key.startsWith(FILE_WORKSPACE_TAB_PREFIX)
|
|
223
|
+
|| key.startsWith(MARKDOWN_PREVIEW_WORKSPACE_TAB_PREFIX)
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function isMarkdownPreviewWorkspaceTabKey(key) {
|
|
228
|
+
return typeof key === 'string'
|
|
229
|
+
&& key.startsWith(MARKDOWN_PREVIEW_WORKSPACE_TAB_PREFIX);
|
|
186
230
|
}
|
|
187
231
|
|
|
188
232
|
function isCompactWorkspaceMode() {
|
|
@@ -201,10 +245,127 @@ function isSupportedImagePath(filePath) {
|
|
|
201
245
|
return SUPPORTED_IMAGE_EXTENSIONS.has(ext);
|
|
202
246
|
}
|
|
203
247
|
|
|
248
|
+
function isSupportedPdfPath(filePath) {
|
|
249
|
+
if (typeof filePath !== 'string') {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
const dotIndex = filePath.lastIndexOf('.');
|
|
253
|
+
if (dotIndex === -1) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
const ext = filePath.slice(dotIndex + 1).toLowerCase();
|
|
257
|
+
return SUPPORTED_PDF_EXTENSIONS.has(ext);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function isSupportedMarkdownPath(filePath) {
|
|
261
|
+
if (typeof filePath !== 'string') {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
const dotIndex = filePath.lastIndexOf('.');
|
|
265
|
+
if (dotIndex === -1) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
const ext = filePath.slice(dotIndex + 1).toLowerCase();
|
|
269
|
+
return SUPPORTED_MARKDOWN_EXTENSIONS.has(ext);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function ensureExternalStylesheet(id, href) {
|
|
273
|
+
if (!href || document.getElementById(id)) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const link = document.createElement('link');
|
|
277
|
+
link.id = id;
|
|
278
|
+
link.rel = 'stylesheet';
|
|
279
|
+
link.href = href;
|
|
280
|
+
document.head.appendChild(link);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function loadMarkdownPreviewBundle() {
|
|
284
|
+
if (!markdownPreviewBundlePromise) {
|
|
285
|
+
markdownPreviewBundlePromise = (async () => {
|
|
286
|
+
ensureExternalStylesheet(
|
|
287
|
+
'markdown-preview-github-css',
|
|
288
|
+
MARKDOWN_PREVIEW_GITHUB_CSS_URL
|
|
289
|
+
);
|
|
290
|
+
ensureExternalStylesheet(
|
|
291
|
+
'markdown-preview-highlight-css',
|
|
292
|
+
MARKDOWN_PREVIEW_HIGHLIGHT_CSS_URL
|
|
293
|
+
);
|
|
294
|
+
ensureExternalStylesheet(
|
|
295
|
+
'markdown-preview-katex-css',
|
|
296
|
+
MARKDOWN_PREVIEW_KATEX_CSS_URL
|
|
297
|
+
);
|
|
298
|
+
const [
|
|
299
|
+
{ default: MarkdownIt },
|
|
300
|
+
{ default: markdownItTaskLists },
|
|
301
|
+
{ default: markdownItKatex },
|
|
302
|
+
{ default: katex },
|
|
303
|
+
{ default: hljs }
|
|
304
|
+
] = await Promise.all([
|
|
305
|
+
import(MARKDOWN_IT_MODULE_URL),
|
|
306
|
+
import(MARKDOWN_TASK_LISTS_MODULE_URL),
|
|
307
|
+
import(MARKDOWN_KATEX_MODULE_URL),
|
|
308
|
+
import(KATEX_MODULE_URL),
|
|
309
|
+
import(HIGHLIGHT_JS_MODULE_URL)
|
|
310
|
+
]);
|
|
311
|
+
const renderer = new MarkdownIt({
|
|
312
|
+
html: true,
|
|
313
|
+
linkify: true,
|
|
314
|
+
breaks: false,
|
|
315
|
+
highlight(source, language) {
|
|
316
|
+
const code = String(source || '');
|
|
317
|
+
const nextLanguage = String(language || '').trim();
|
|
318
|
+
let html = '';
|
|
319
|
+
if (nextLanguage && hljs.getLanguage(nextLanguage)) {
|
|
320
|
+
html = hljs.highlight(code, {
|
|
321
|
+
language: nextLanguage,
|
|
322
|
+
ignoreIllegals: true
|
|
323
|
+
}).value;
|
|
324
|
+
} else {
|
|
325
|
+
html = hljs.highlightAuto(code).value;
|
|
326
|
+
}
|
|
327
|
+
const languageClass = nextLanguage
|
|
328
|
+
? ` language-${escapeHtml(nextLanguage)}`
|
|
329
|
+
: '';
|
|
330
|
+
return `<pre class="hljs"><code class="hljs${languageClass}">${html}</code></pre>`;
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
renderer.use(markdownItTaskLists, {
|
|
334
|
+
enabled: false,
|
|
335
|
+
label: true,
|
|
336
|
+
labelAfter: true
|
|
337
|
+
});
|
|
338
|
+
renderer.use(markdownItKatex, { katex });
|
|
339
|
+
return {
|
|
340
|
+
renderer
|
|
341
|
+
};
|
|
342
|
+
})().catch((error) => {
|
|
343
|
+
markdownPreviewBundlePromise = null;
|
|
344
|
+
throw error;
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
return await markdownPreviewBundlePromise;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function loadPdfJs() {
|
|
351
|
+
if (!pdfJsLibPromise) {
|
|
352
|
+
pdfJsLibPromise = import(PDFJS_MODULE_URL)
|
|
353
|
+
.then((pdfjsLib) => {
|
|
354
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = PDFJS_WORKER_URL;
|
|
355
|
+
return pdfjsLib;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return await pdfJsLibPromise;
|
|
359
|
+
}
|
|
360
|
+
|
|
204
361
|
function isCompactTerminalTabsMode() {
|
|
205
362
|
return !!window.__tabminalCompactTerminalTabsMode;
|
|
206
363
|
}
|
|
207
364
|
|
|
365
|
+
function canUseMarkdownSplitTabsMode() {
|
|
366
|
+
return !isForcedTerminalWorkspaceMode();
|
|
367
|
+
}
|
|
368
|
+
|
|
208
369
|
function isForcedTerminalWorkspaceMode() {
|
|
209
370
|
return isCompactWorkspaceMode() || isCompactTerminalTabsMode();
|
|
210
371
|
}
|
|
@@ -220,8 +381,66 @@ function buildMainTerminalTheme() {
|
|
|
220
381
|
}
|
|
221
382
|
|
|
222
383
|
function workspaceKeyToFilePath(key) {
|
|
223
|
-
if (
|
|
224
|
-
|
|
384
|
+
if (typeof key !== 'string' || key.length === 0) return '';
|
|
385
|
+
if (key.startsWith(MARKDOWN_PREVIEW_WORKSPACE_TAB_PREFIX)) {
|
|
386
|
+
return key.slice(MARKDOWN_PREVIEW_WORKSPACE_TAB_PREFIX.length);
|
|
387
|
+
}
|
|
388
|
+
if (key.startsWith(FILE_WORKSPACE_TAB_PREFIX)) {
|
|
389
|
+
return key.slice(FILE_WORKSPACE_TAB_PREFIX.length);
|
|
390
|
+
}
|
|
391
|
+
return '';
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function isExternalHref(href) {
|
|
395
|
+
return /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(String(href || '').trim());
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function resolveMarkdownLocalTarget(baseFilePath, href) {
|
|
399
|
+
const value = String(href || '').trim();
|
|
400
|
+
const basePath = String(baseFilePath || '').trim();
|
|
401
|
+
if (!value || !basePath || value.startsWith('#') || isExternalHref(value)) {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
const baseDir = basePath.includes('/')
|
|
405
|
+
? basePath.slice(0, basePath.lastIndexOf('/') + 1)
|
|
406
|
+
: '/';
|
|
407
|
+
try {
|
|
408
|
+
const resolved = new URL(
|
|
409
|
+
value,
|
|
410
|
+
`https://tabminal.local${encodeURI(baseDir)}`
|
|
411
|
+
);
|
|
412
|
+
if (resolved.origin !== 'https://tabminal.local') {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
path: decodeURIComponent(resolved.pathname),
|
|
417
|
+
hash: resolved.hash || ''
|
|
418
|
+
};
|
|
419
|
+
} catch {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function buildMarkdownContextBasePath(filePath = '', baseDirectory = '') {
|
|
425
|
+
const nextFilePath = String(filePath || '').trim();
|
|
426
|
+
if (nextFilePath) {
|
|
427
|
+
return nextFilePath;
|
|
428
|
+
}
|
|
429
|
+
const nextBaseDirectory = String(baseDirectory || '')
|
|
430
|
+
.trim()
|
|
431
|
+
.replace(/\/+$/, '');
|
|
432
|
+
if (!nextBaseDirectory) {
|
|
433
|
+
return '';
|
|
434
|
+
}
|
|
435
|
+
return `${nextBaseDirectory}/__tabminal__.md`;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function slugifyMarkdownHeading(text) {
|
|
439
|
+
return String(text || '')
|
|
440
|
+
.trim()
|
|
441
|
+
.toLowerCase()
|
|
442
|
+
.replace(/[^\p{L}\p{N}\s-]/gu, '')
|
|
443
|
+
.replace(/\s+/g, '-');
|
|
225
444
|
}
|
|
226
445
|
|
|
227
446
|
function getWorkspaceDeviceId() {
|
|
@@ -263,15 +482,44 @@ function normalizeWorkspaceSnapshot(input = {}, fallback = {}) {
|
|
|
263
482
|
? base.updatedBy
|
|
264
483
|
: ''
|
|
265
484
|
);
|
|
485
|
+
const openFiles = uniqueStringList(source.openFiles);
|
|
486
|
+
const fallbackOpenFiles = uniqueStringList(base.openFiles);
|
|
487
|
+
const markdownSplitPathSource =
|
|
488
|
+
typeof source.markdownSplitPath === 'string'
|
|
489
|
+
? source.markdownSplitPath
|
|
490
|
+
: (
|
|
491
|
+
typeof base.markdownSplitPath === 'string'
|
|
492
|
+
? base.markdownSplitPath
|
|
493
|
+
: ''
|
|
494
|
+
);
|
|
495
|
+
const markdownSplitPath = (
|
|
496
|
+
markdownSplitPathSource
|
|
497
|
+
&& isSupportedMarkdownPath(markdownSplitPathSource)
|
|
498
|
+
&& (
|
|
499
|
+
openFiles.includes(markdownSplitPathSource)
|
|
500
|
+
|| fallbackOpenFiles.includes(markdownSplitPathSource)
|
|
501
|
+
)
|
|
502
|
+
)
|
|
503
|
+
? markdownSplitPathSource
|
|
504
|
+
: '';
|
|
505
|
+
const activeWorkspaceTabKey = typeof source.activeWorkspaceTabKey === 'string'
|
|
506
|
+
? source.activeWorkspaceTabKey
|
|
507
|
+
: (
|
|
508
|
+
typeof base.activeWorkspaceTabKey === 'string'
|
|
509
|
+
? base.activeWorkspaceTabKey
|
|
510
|
+
: ''
|
|
511
|
+
);
|
|
266
512
|
return {
|
|
267
513
|
updatedAt,
|
|
268
514
|
updatedBy,
|
|
269
515
|
isVisible: !!source.isVisible,
|
|
270
|
-
openFiles
|
|
516
|
+
openFiles,
|
|
271
517
|
terminalDisplayMode: source.terminalDisplayMode === 'tab'
|
|
272
518
|
? 'tab'
|
|
273
519
|
: 'auto',
|
|
274
|
-
expandedPaths: uniqueStringList(source.expandedPaths)
|
|
520
|
+
expandedPaths: uniqueStringList(source.expandedPaths),
|
|
521
|
+
markdownSplitPath,
|
|
522
|
+
activeWorkspaceTabKey
|
|
275
523
|
};
|
|
276
524
|
}
|
|
277
525
|
|
|
@@ -299,6 +547,8 @@ function buildWorkspaceSnapshotForSession(session, overrides = {}) {
|
|
|
299
547
|
openFiles: session.editorState.openFiles,
|
|
300
548
|
terminalDisplayMode: session.sharedWorkspaceState.terminalDisplayMode,
|
|
301
549
|
expandedPaths: session.sharedWorkspaceState.expandedPaths,
|
|
550
|
+
markdownSplitPath: session.workspaceState.markdownSplitPath,
|
|
551
|
+
activeWorkspaceTabKey: session.workspaceState.activeTabKey,
|
|
302
552
|
...overrides
|
|
303
553
|
});
|
|
304
554
|
}
|
|
@@ -709,6 +959,26 @@ class EditorManager {
|
|
|
709
959
|
this.monacoContainer = document.getElementById('monaco-container');
|
|
710
960
|
this.imagePreviewContainer = document.getElementById('image-preview-container');
|
|
711
961
|
this.imagePreview = document.getElementById('image-preview');
|
|
962
|
+
this.pdfPreviewContainer = document.getElementById(
|
|
963
|
+
'pdf-preview-container'
|
|
964
|
+
);
|
|
965
|
+
this.pdfPreviewStatus = document.getElementById('pdf-preview-status');
|
|
966
|
+
this.pdfPreviewStatusPrimary = document.getElementById(
|
|
967
|
+
'pdf-preview-status-primary'
|
|
968
|
+
);
|
|
969
|
+
this.pdfPreviewStatusSecondary = document.getElementById(
|
|
970
|
+
'pdf-preview-status-secondary'
|
|
971
|
+
);
|
|
972
|
+
this.pdfPreviewPages = document.getElementById('pdf-preview-pages');
|
|
973
|
+
this.markdownPreviewContainer = document.getElementById(
|
|
974
|
+
'markdown-preview-container'
|
|
975
|
+
);
|
|
976
|
+
this.markdownPreviewScroll = document.getElementById(
|
|
977
|
+
'markdown-preview-scroll'
|
|
978
|
+
);
|
|
979
|
+
this.markdownPreviewContent = document.getElementById(
|
|
980
|
+
'markdown-preview-content'
|
|
981
|
+
);
|
|
712
982
|
this.emptyState = document.getElementById('empty-editor-state');
|
|
713
983
|
this.terminalWrapper = terminalWrapper;
|
|
714
984
|
this.terminalOriginalParent = terminalWrapper?.parentElement || null;
|
|
@@ -758,17 +1028,47 @@ class EditorManager {
|
|
|
758
1028
|
this.agentEmbeddedEditors = [];
|
|
759
1029
|
this.agentEmbeddedTerminals = new Map();
|
|
760
1030
|
this.agentTranscriptLayout = null;
|
|
1031
|
+
this.pdfPreviewState = {
|
|
1032
|
+
path: '',
|
|
1033
|
+
sessionKey: '',
|
|
1034
|
+
renderToken: 0,
|
|
1035
|
+
document: null,
|
|
1036
|
+
loadingTask: null,
|
|
1037
|
+
metadata: '',
|
|
1038
|
+
renderedWidth: 0,
|
|
1039
|
+
relayoutTimer: 0
|
|
1040
|
+
};
|
|
1041
|
+
this.markdownPreviewState = {
|
|
1042
|
+
path: '',
|
|
1043
|
+
sessionKey: '',
|
|
1044
|
+
renderToken: 0,
|
|
1045
|
+
renderTimer: 0,
|
|
1046
|
+
pendingHash: ''
|
|
1047
|
+
};
|
|
1048
|
+
this.fileVersionCheckTimer = null;
|
|
1049
|
+
this.fileVersionCheckPromise = null;
|
|
1050
|
+
this.fileConflictDialogKey = '';
|
|
1051
|
+
this.suppressFileWriteCapture = false;
|
|
761
1052
|
this.agentTranscriptResizeObserver = null;
|
|
1053
|
+
this.treeDirectoryFetches = new Map();
|
|
1054
|
+
this.treeRefreshInFlight = false;
|
|
1055
|
+
this.treeRefreshRerunRequested = false;
|
|
1056
|
+
this.treeRefreshBatchQueued = false;
|
|
1057
|
+
this.pendingForcedTreeRefreshSessions = new Set();
|
|
762
1058
|
|
|
763
1059
|
this.initTerminalControls();
|
|
764
1060
|
this.initResizer();
|
|
765
1061
|
this.initAgentPanel();
|
|
1062
|
+
this.initMarkdownPreview();
|
|
766
1063
|
this.initMonaco();
|
|
767
1064
|
this.loadIconMap();
|
|
768
1065
|
this.agentTimestampTimer = window.setInterval(() => {
|
|
769
1066
|
this.refreshAgentTimelineTimestamps();
|
|
770
1067
|
this.refreshAgentUsageHud();
|
|
771
1068
|
}, 1000);
|
|
1069
|
+
this.fileVersionCheckTimer = window.setInterval(() => {
|
|
1070
|
+
void this.checkActiveFileVersion();
|
|
1071
|
+
}, FILE_VERSION_CHECK_INTERVAL_MS);
|
|
772
1072
|
}
|
|
773
1073
|
|
|
774
1074
|
isTerminalTabPinned(session = this.currentSession) {
|
|
@@ -811,6 +1111,155 @@ class EditorManager {
|
|
|
811
1111
|
this.terminalWrapper.appendChild(this.terminalLayoutButton);
|
|
812
1112
|
}
|
|
813
1113
|
|
|
1114
|
+
initMarkdownPreview() {
|
|
1115
|
+
if (!this.markdownPreviewContainer || !this.markdownPreviewContent) {
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
this.markdownPreviewContainer.addEventListener('click', (event) => {
|
|
1119
|
+
const link = event.target.closest('a[data-markdown-local-path]');
|
|
1120
|
+
if (!link) {
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
const filePath = String(
|
|
1124
|
+
link.dataset.markdownLocalPath || ''
|
|
1125
|
+
).trim();
|
|
1126
|
+
if (!filePath) {
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
event.preventDefault();
|
|
1130
|
+
event.stopPropagation();
|
|
1131
|
+
void this.openLocalMarkdownLink(
|
|
1132
|
+
filePath,
|
|
1133
|
+
String(link.dataset.markdownLocalHash || '')
|
|
1134
|
+
);
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
getMarkdownSplitPath(session = this.currentSession) {
|
|
1139
|
+
if (!session?.workspaceState) {
|
|
1140
|
+
return '';
|
|
1141
|
+
}
|
|
1142
|
+
return typeof session.workspaceState.markdownSplitPath === 'string'
|
|
1143
|
+
? session.workspaceState.markdownSplitPath
|
|
1144
|
+
: '';
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
isMarkdownSplitViewEnabled(
|
|
1148
|
+
session = this.currentSession,
|
|
1149
|
+
filePath = session?.editorState?.activeFilePath || ''
|
|
1150
|
+
) {
|
|
1151
|
+
return !!(
|
|
1152
|
+
session
|
|
1153
|
+
&& canUseMarkdownSplitTabsMode()
|
|
1154
|
+
&& filePath
|
|
1155
|
+
&& this.getMarkdownSplitPath(session) === filePath
|
|
1156
|
+
&& isSupportedMarkdownPath(filePath)
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
setMarkdownSplitView(
|
|
1161
|
+
filePath,
|
|
1162
|
+
enabled,
|
|
1163
|
+
session = this.currentSession
|
|
1164
|
+
) {
|
|
1165
|
+
if (!session?.workspaceState || !isSupportedMarkdownPath(filePath)) {
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
const nextMarkdownSplitPath = (
|
|
1169
|
+
enabled
|
|
1170
|
+
&& canUseMarkdownSplitTabsMode()
|
|
1171
|
+
)
|
|
1172
|
+
? filePath
|
|
1173
|
+
: '';
|
|
1174
|
+
if (session.workspaceState.markdownSplitPath === nextMarkdownSplitPath) {
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
session.workspaceState.markdownSplitPath = nextMarkdownSplitPath;
|
|
1178
|
+
session.saveState({ touchWorkspace: true });
|
|
1179
|
+
if (session.key !== this.currentSession?.key) {
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
this.renderEditorTabs();
|
|
1183
|
+
const activeKey = this.getActiveWorkspaceTabKey(session);
|
|
1184
|
+
if (
|
|
1185
|
+
activeKey === makeFileWorkspaceTabKey(filePath)
|
|
1186
|
+
|| activeKey === makeMarkdownPreviewWorkspaceTabKey(filePath)
|
|
1187
|
+
) {
|
|
1188
|
+
this.activateWorkspaceTab(activeKey, true);
|
|
1189
|
+
}
|
|
1190
|
+
this.layout();
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
syncMarkdownSplitSupport(session = this.currentSession) {
|
|
1194
|
+
if (!session?.workspaceState) {
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
const markdownSplitPath = this.getMarkdownSplitPath(session);
|
|
1198
|
+
if (
|
|
1199
|
+
markdownSplitPath
|
|
1200
|
+
&& (
|
|
1201
|
+
!isSupportedMarkdownPath(markdownSplitPath)
|
|
1202
|
+
|| !session.editorState.openFiles.includes(markdownSplitPath)
|
|
1203
|
+
)
|
|
1204
|
+
) {
|
|
1205
|
+
session.workspaceState.markdownSplitPath = '';
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
showMarkdownSplitView(filePath, options = {}) {
|
|
1210
|
+
const session = options.session || this.currentSession;
|
|
1211
|
+
const focusEditor = options.focusEditor !== false;
|
|
1212
|
+
if (!session || !filePath) {
|
|
1213
|
+
return false;
|
|
1214
|
+
}
|
|
1215
|
+
const file = this.getModel(filePath, session);
|
|
1216
|
+
if (!file || file.type !== 'text') {
|
|
1217
|
+
return false;
|
|
1218
|
+
}
|
|
1219
|
+
if (!this.editor || !this.monacoContainer || !this.contentContainer) {
|
|
1220
|
+
return false;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
this.contentContainer.classList.add('markdown-split-active');
|
|
1224
|
+
this.agentContainer.style.display = 'none';
|
|
1225
|
+
this.imagePreviewContainer.style.display = 'none';
|
|
1226
|
+
this.hidePdfPreview();
|
|
1227
|
+
this.monacoContainer.style.display = 'block';
|
|
1228
|
+
this.markdownPreviewContainer.style.display = 'flex';
|
|
1229
|
+
this.emptyState.style.display = 'none';
|
|
1230
|
+
|
|
1231
|
+
if (!file.model && file.content !== null && this.monacoInstance) {
|
|
1232
|
+
file.model = this.monacoInstance.editor.createModel(
|
|
1233
|
+
file.content,
|
|
1234
|
+
undefined,
|
|
1235
|
+
this.monacoInstance.Uri.file(filePath)
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
if (file.model) {
|
|
1240
|
+
this.editor.setModel(file.model);
|
|
1241
|
+
this.editor.updateOptions({ readOnly: !!file.readonly });
|
|
1242
|
+
const savedViewState = session.editorState.viewStates.get(filePath);
|
|
1243
|
+
if (savedViewState) {
|
|
1244
|
+
this.editor.restoreViewState(savedViewState);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
void this.renderMarkdownPreview(filePath, {
|
|
1249
|
+
session,
|
|
1250
|
+
show: true
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
requestAnimationFrame(() => {
|
|
1254
|
+
this.layout();
|
|
1255
|
+
if (focusEditor && this.editor) {
|
|
1256
|
+
this.editor.focus();
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
return true;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
814
1263
|
updateTerminalLayoutButton() {
|
|
815
1264
|
if (!this.terminalLayoutButton) return;
|
|
816
1265
|
|
|
@@ -900,7 +1349,13 @@ class EditorManager {
|
|
|
900
1349
|
}
|
|
901
1350
|
} else if (isFileWorkspaceTabKey(lastNonTerminal)) {
|
|
902
1351
|
const filePath = workspaceKeyToFilePath(lastNonTerminal);
|
|
903
|
-
if (
|
|
1352
|
+
if (
|
|
1353
|
+
session.editorState.openFiles.includes(filePath)
|
|
1354
|
+
&& (
|
|
1355
|
+
!isMarkdownPreviewWorkspaceTabKey(lastNonTerminal)
|
|
1356
|
+
|| isSupportedMarkdownPath(filePath)
|
|
1357
|
+
)
|
|
1358
|
+
) {
|
|
904
1359
|
return lastNonTerminal;
|
|
905
1360
|
}
|
|
906
1361
|
}
|
|
@@ -1098,6 +1553,24 @@ class EditorManager {
|
|
|
1098
1553
|
this.agentTranscript = document.createElement('div');
|
|
1099
1554
|
this.agentTranscript.className = 'agent-panel-transcript';
|
|
1100
1555
|
this.agentTranscript.addEventListener('click', (event) => {
|
|
1556
|
+
const markdownLink = event.target.closest(
|
|
1557
|
+
'a[data-markdown-local-path]'
|
|
1558
|
+
);
|
|
1559
|
+
if (markdownLink) {
|
|
1560
|
+
const filePath = String(
|
|
1561
|
+
markdownLink.dataset.markdownLocalPath || ''
|
|
1562
|
+
).trim();
|
|
1563
|
+
if (!filePath) {
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
event.preventDefault();
|
|
1567
|
+
event.stopPropagation();
|
|
1568
|
+
void this.openLocalMarkdownLink(
|
|
1569
|
+
filePath,
|
|
1570
|
+
String(markdownLink.dataset.markdownLocalHash || '')
|
|
1571
|
+
);
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1101
1574
|
const anchor = event.target.closest('a');
|
|
1102
1575
|
if (!anchor) return;
|
|
1103
1576
|
const href = anchor.getAttribute('href') || '';
|
|
@@ -1108,6 +1581,20 @@ class EditorManager {
|
|
|
1108
1581
|
void this.openFile(href);
|
|
1109
1582
|
});
|
|
1110
1583
|
this.agentTranscript.addEventListener('scroll', () => {
|
|
1584
|
+
const activeAgentTab = getActiveAgentTab();
|
|
1585
|
+
if (
|
|
1586
|
+
activeAgentTab
|
|
1587
|
+
&& this.agentTranscript.scrollTop <= 24
|
|
1588
|
+
) {
|
|
1589
|
+
activeAgentTab.scrollToBottomOnNextRender = false;
|
|
1590
|
+
void this.loadOlderAgentTimeline(activeAgentTab);
|
|
1591
|
+
} else if (
|
|
1592
|
+
activeAgentTab
|
|
1593
|
+
&& this.isAgentTranscriptNearBottom(24)
|
|
1594
|
+
) {
|
|
1595
|
+
activeAgentTab.scrollToBottomOnNextRender = false;
|
|
1596
|
+
void this.loadNewerAgentTimeline(activeAgentTab);
|
|
1597
|
+
}
|
|
1111
1598
|
this.updateAgentScrollBottomButton();
|
|
1112
1599
|
this.rememberAgentTranscriptLayout();
|
|
1113
1600
|
});
|
|
@@ -1445,6 +1932,12 @@ class EditorManager {
|
|
|
1445
1932
|
&& session.editorState.openFiles.includes(
|
|
1446
1933
|
workspaceKeyToFilePath(explicitKey)
|
|
1447
1934
|
)
|
|
1935
|
+
&& (
|
|
1936
|
+
!isMarkdownPreviewWorkspaceTabKey(explicitKey)
|
|
1937
|
+
|| isSupportedMarkdownPath(
|
|
1938
|
+
workspaceKeyToFilePath(explicitKey)
|
|
1939
|
+
)
|
|
1940
|
+
)
|
|
1448
1941
|
) {
|
|
1449
1942
|
return explicitKey;
|
|
1450
1943
|
}
|
|
@@ -1485,53 +1978,526 @@ class EditorManager {
|
|
|
1485
1978
|
store.set(filePath, value);
|
|
1486
1979
|
}
|
|
1487
1980
|
|
|
1488
|
-
|
|
1489
|
-
if (typeof
|
|
1490
|
-
return
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1981
|
+
normalizePendingFileWrite(write, entry = null) {
|
|
1982
|
+
if (write && typeof write === 'object' && !Array.isArray(write)) {
|
|
1983
|
+
return {
|
|
1984
|
+
content: typeof write.content === 'string' ? write.content : '',
|
|
1985
|
+
expectedVersion: typeof write.expectedVersion === 'string'
|
|
1986
|
+
? write.expectedVersion
|
|
1987
|
+
: (
|
|
1988
|
+
typeof entry?.version === 'string'
|
|
1989
|
+
? entry.version
|
|
1990
|
+
: ''
|
|
1991
|
+
),
|
|
1992
|
+
blocked: write.blocked === true,
|
|
1993
|
+
force: write.force === true
|
|
1994
|
+
};
|
|
1500
1995
|
}
|
|
1501
|
-
return
|
|
1996
|
+
return {
|
|
1997
|
+
content: typeof write === 'string' ? write : '',
|
|
1998
|
+
expectedVersion: typeof entry?.version === 'string'
|
|
1999
|
+
? entry.version
|
|
2000
|
+
: '',
|
|
2001
|
+
blocked: false,
|
|
2002
|
+
force: false
|
|
2003
|
+
};
|
|
1502
2004
|
}
|
|
1503
2005
|
|
|
1504
|
-
|
|
1505
|
-
if (!
|
|
1506
|
-
const
|
|
1507
|
-
const
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
2006
|
+
queuePendingFileWrite(session, filePath, content, overrides = {}) {
|
|
2007
|
+
if (!session || !filePath) return;
|
|
2008
|
+
const pending = getPendingSession(session.key);
|
|
2009
|
+
const entry = this.getModel(filePath, session);
|
|
2010
|
+
const previous = this.normalizePendingFileWrite(
|
|
2011
|
+
pending.fileWrites.get(filePath),
|
|
2012
|
+
entry
|
|
2013
|
+
);
|
|
2014
|
+
pending.fileWrites.set(filePath, {
|
|
2015
|
+
...previous,
|
|
2016
|
+
content,
|
|
2017
|
+
expectedVersion: typeof overrides.expectedVersion === 'string'
|
|
2018
|
+
? overrides.expectedVersion
|
|
2019
|
+
: previous.expectedVersion,
|
|
2020
|
+
blocked: overrides.blocked ?? false,
|
|
2021
|
+
force: overrides.force ?? false
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
getPendingFileWrite(session, filePath) {
|
|
2026
|
+
if (!session || !filePath) return null;
|
|
2027
|
+
const pending = getPendingSession(session.key);
|
|
2028
|
+
if (!pending?.fileWrites?.has(filePath)) {
|
|
2029
|
+
return null;
|
|
2030
|
+
}
|
|
2031
|
+
return this.normalizePendingFileWrite(
|
|
2032
|
+
pending.fileWrites.get(filePath),
|
|
2033
|
+
this.getModel(filePath, session)
|
|
1512
2034
|
);
|
|
1513
|
-
return nextPath ? makeFileWorkspaceTabKey(nextPath) : key;
|
|
1514
2035
|
}
|
|
1515
2036
|
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
}
|
|
1521
|
-
if (
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
2037
|
+
getTextFileEntry(filePath, session = this.currentSession) {
|
|
2038
|
+
const entry = this.getModel(filePath, session);
|
|
2039
|
+
if (!entry || entry.type !== 'text') {
|
|
2040
|
+
return null;
|
|
2041
|
+
}
|
|
2042
|
+
if (typeof entry.contentVersion !== 'string') {
|
|
2043
|
+
entry.contentVersion = typeof entry.version === 'string'
|
|
2044
|
+
? entry.version
|
|
2045
|
+
: '';
|
|
2046
|
+
}
|
|
2047
|
+
return entry;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
getCurrentTextFileContent(filePath, session = this.currentSession) {
|
|
2051
|
+
const entry = this.getTextFileEntry(filePath, session);
|
|
2052
|
+
if (!entry) return '';
|
|
2053
|
+
try {
|
|
2054
|
+
if (typeof entry.model?.getValue === 'function') {
|
|
2055
|
+
return entry.model.getValue();
|
|
1529
2056
|
}
|
|
1530
|
-
|
|
2057
|
+
} catch {
|
|
2058
|
+
// Ignore model access failures and fall back to cached content.
|
|
2059
|
+
}
|
|
2060
|
+
return typeof entry.content === 'string' ? entry.content : '';
|
|
2061
|
+
}
|
|
1531
2062
|
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
2063
|
+
isActiveTextFile(session, filePath) {
|
|
2064
|
+
if (!session || !filePath) return false;
|
|
2065
|
+
if (this.currentSession?.key !== session.key) return false;
|
|
2066
|
+
if (state.activeSessionKey !== session.key) return false;
|
|
2067
|
+
if (session.editorState.activeFilePath !== filePath) return false;
|
|
2068
|
+
return this.getActiveWorkspaceTabKey(session)
|
|
2069
|
+
=== makeFileWorkspaceTabKey(filePath);
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
updateTextFileEntry(filePath, updates, session = this.currentSession) {
|
|
2073
|
+
const entry = this.getTextFileEntry(filePath, session);
|
|
2074
|
+
if (!entry || !updates || typeof updates !== 'object') {
|
|
2075
|
+
return null;
|
|
2076
|
+
}
|
|
2077
|
+
Object.assign(entry, updates);
|
|
2078
|
+
return entry;
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
updateActiveEditorReadOnlyState(session, filePath, readonly) {
|
|
2082
|
+
if (!this.isActiveTextFile(session, filePath) || !this.editor) {
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
this.editor.updateOptions({ readOnly: !!readonly });
|
|
2086
|
+
this.renderEditorTabs();
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
applyProgrammaticTextContent(entry, nextContent) {
|
|
2090
|
+
if (
|
|
2091
|
+
!entry?.model
|
|
2092
|
+
|| typeof entry.model.getValue !== 'function'
|
|
2093
|
+
|| typeof entry.model.setValue !== 'function'
|
|
2094
|
+
) {
|
|
2095
|
+
entry.content = nextContent;
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2098
|
+
const currentValue = entry.model.getValue();
|
|
2099
|
+
if (currentValue === nextContent) {
|
|
2100
|
+
entry.content = nextContent;
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
this.suppressFileWriteCapture = true;
|
|
2104
|
+
try {
|
|
2105
|
+
entry.model.setValue(nextContent);
|
|
2106
|
+
} finally {
|
|
2107
|
+
this.suppressFileWriteCapture = false;
|
|
2108
|
+
}
|
|
2109
|
+
entry.content = nextContent;
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
async readTextFileSnapshot(session, filePath) {
|
|
2113
|
+
if (!session || !filePath) {
|
|
2114
|
+
throw new Error('File path required');
|
|
2115
|
+
}
|
|
2116
|
+
const response = await session.server.fetch(
|
|
2117
|
+
`/api/fs/read?path=${encodeURIComponent(filePath)}`
|
|
2118
|
+
);
|
|
2119
|
+
if (!response.ok) {
|
|
2120
|
+
await throwResponseError(response, 'Failed to read file');
|
|
2121
|
+
}
|
|
2122
|
+
return await response.json();
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
async readTextFileInfo(session, filePath) {
|
|
2126
|
+
if (!session || !filePath) {
|
|
2127
|
+
throw new Error('File path required');
|
|
2128
|
+
}
|
|
2129
|
+
const response = await session.server.fetch(
|
|
2130
|
+
`/api/fs/info?path=${encodeURIComponent(filePath)}`
|
|
2131
|
+
);
|
|
2132
|
+
if (!response.ok) {
|
|
2133
|
+
await throwResponseError(response, 'Failed to inspect file');
|
|
2134
|
+
}
|
|
2135
|
+
return await response.json();
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
applyTextFileSnapshot(session, filePath, snapshot, options = {}) {
|
|
2139
|
+
const entry = this.getTextFileEntry(filePath, session);
|
|
2140
|
+
if (!entry || !snapshot || typeof snapshot !== 'object') {
|
|
2141
|
+
return null;
|
|
2142
|
+
}
|
|
2143
|
+
const useLocalContent = options.useLocalContent === true;
|
|
2144
|
+
const nextReadonly = !!snapshot.readonly;
|
|
2145
|
+
const nextVersion = typeof snapshot.version === 'string'
|
|
2146
|
+
? snapshot.version
|
|
2147
|
+
: entry.version || '';
|
|
2148
|
+
const nextContent = typeof snapshot.content === 'string'
|
|
2149
|
+
? snapshot.content
|
|
2150
|
+
: entry.content || '';
|
|
2151
|
+
|
|
2152
|
+
if (!entry.model && this.monacoInstance) {
|
|
2153
|
+
const uri = this.monacoInstance.Uri.file(filePath);
|
|
2154
|
+
const existing = this.monacoInstance.editor.getModel(uri);
|
|
2155
|
+
entry.model = existing || this.monacoInstance.editor.createModel(
|
|
2156
|
+
typeof entry.content === 'string' ? entry.content : '',
|
|
2157
|
+
undefined,
|
|
2158
|
+
uri
|
|
2159
|
+
);
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
if (!useLocalContent) {
|
|
2163
|
+
const restoreViewState = (
|
|
2164
|
+
this.isActiveTextFile(session, filePath)
|
|
2165
|
+
&& this.editor
|
|
2166
|
+
&& this.editor.getModel?.() === entry.model
|
|
2167
|
+
)
|
|
2168
|
+
? this.editor.saveViewState()
|
|
2169
|
+
: null;
|
|
2170
|
+
this.applyProgrammaticTextContent(entry, nextContent);
|
|
2171
|
+
if (restoreViewState && this.editor) {
|
|
2172
|
+
this.editor.restoreViewState(restoreViewState);
|
|
2173
|
+
}
|
|
2174
|
+
entry.contentVersion = nextVersion;
|
|
2175
|
+
} else if (typeof snapshot.content === 'string') {
|
|
2176
|
+
entry.content = snapshot.content;
|
|
2177
|
+
entry.contentVersion = nextVersion;
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
entry.version = nextVersion;
|
|
2181
|
+
entry.readonly = nextReadonly;
|
|
2182
|
+
entry.size = Number.isFinite(snapshot.size) ? snapshot.size : entry.size;
|
|
2183
|
+
entry.mtimeMs = Number.isFinite(snapshot.mtimeMs)
|
|
2184
|
+
? snapshot.mtimeMs
|
|
2185
|
+
: entry.mtimeMs;
|
|
2186
|
+
entry.lastDismissedRemoteVersion = '';
|
|
2187
|
+
this.updateActiveEditorReadOnlyState(session, filePath, nextReadonly);
|
|
2188
|
+
if (
|
|
2189
|
+
this.currentSession?.key === session.key
|
|
2190
|
+
&& isSupportedMarkdownPath(filePath)
|
|
2191
|
+
&& this.currentSession.editorState.activeFilePath === filePath
|
|
2192
|
+
) {
|
|
2193
|
+
this.scheduleMarkdownPreviewRender(filePath, session);
|
|
2194
|
+
}
|
|
2195
|
+
return entry;
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
getFileConflictDialogKey(session, filePath, version, source) {
|
|
2199
|
+
return [
|
|
2200
|
+
session?.key || '',
|
|
2201
|
+
filePath || '',
|
|
2202
|
+
version || '',
|
|
2203
|
+
source || ''
|
|
2204
|
+
].join(':');
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
async promptTextFileConflict(session, filePath, snapshot, source) {
|
|
2208
|
+
if (!session || !filePath || !snapshot) {
|
|
2209
|
+
return 'dismiss';
|
|
2210
|
+
}
|
|
2211
|
+
const version = typeof snapshot.version === 'string'
|
|
2212
|
+
? snapshot.version
|
|
2213
|
+
: '';
|
|
2214
|
+
const dialogKey = this.getFileConflictDialogKey(
|
|
2215
|
+
session,
|
|
2216
|
+
filePath,
|
|
2217
|
+
version,
|
|
2218
|
+
source
|
|
2219
|
+
);
|
|
2220
|
+
if (this.fileConflictDialogKey === dialogKey) {
|
|
2221
|
+
return 'dismiss';
|
|
2222
|
+
}
|
|
2223
|
+
this.fileConflictDialogKey = dialogKey;
|
|
2224
|
+
const fileName = filePath.split('/').pop() || filePath;
|
|
2225
|
+
const keepLocal = await showConfirmModal({
|
|
2226
|
+
title: source === 'save-conflict'
|
|
2227
|
+
? 'Save Conflict'
|
|
2228
|
+
: 'File Changed on Disk',
|
|
2229
|
+
message: source === 'save-conflict'
|
|
2230
|
+
? `“${fileName}” changed on disk before Tabminal could save it.`
|
|
2231
|
+
: `“${fileName}” was modified outside Tabminal.`,
|
|
2232
|
+
note: 'Use Remote reloads the disk version. Use Local keeps your '
|
|
2233
|
+
+ 'current editor contents and overwrites the remote change '
|
|
2234
|
+
+ 'on the next save.',
|
|
2235
|
+
confirmLabel: 'Use Local',
|
|
2236
|
+
cancelLabel: 'Use Remote',
|
|
2237
|
+
preferredFocus: 'cancel',
|
|
2238
|
+
allowDismiss: false,
|
|
2239
|
+
returnFocus: this.isActiveTextFile(session, filePath)
|
|
2240
|
+
? this.monacoContainer
|
|
2241
|
+
: document.activeElement
|
|
2242
|
+
});
|
|
2243
|
+
this.fileConflictDialogKey = '';
|
|
2244
|
+
return keepLocal ? 'local' : 'remote';
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
async resolveTextFileConflict(session, filePath, snapshot, source) {
|
|
2248
|
+
const entry = this.getTextFileEntry(filePath, session);
|
|
2249
|
+
if (!entry || !snapshot) {
|
|
2250
|
+
return;
|
|
2251
|
+
}
|
|
2252
|
+
const decision = await this.promptTextFileConflict(
|
|
2253
|
+
session,
|
|
2254
|
+
filePath,
|
|
2255
|
+
snapshot,
|
|
2256
|
+
source
|
|
2257
|
+
);
|
|
2258
|
+
if (decision === 'remote') {
|
|
2259
|
+
const remoteSnapshot = typeof snapshot.content === 'string'
|
|
2260
|
+
? snapshot
|
|
2261
|
+
: await this.readTextFileSnapshot(session, filePath);
|
|
2262
|
+
this.applyTextFileSnapshot(session, filePath, remoteSnapshot);
|
|
2263
|
+
this.clearPendingFileWrite(session.key, filePath);
|
|
2264
|
+
if (this.isActiveTextFile(session, filePath)) {
|
|
2265
|
+
this.renderEditorTabs();
|
|
2266
|
+
}
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
if (decision === 'local') {
|
|
2271
|
+
const currentContent = this.getCurrentTextFileContent(
|
|
2272
|
+
filePath,
|
|
2273
|
+
session
|
|
2274
|
+
);
|
|
2275
|
+
this.applyTextFileSnapshot(session, filePath, snapshot, {
|
|
2276
|
+
useLocalContent: true
|
|
2277
|
+
});
|
|
2278
|
+
this.queuePendingFileWrite(session, filePath, currentContent, {
|
|
2279
|
+
expectedVersion: typeof snapshot.version === 'string'
|
|
2280
|
+
? snapshot.version
|
|
2281
|
+
: entry.version || '',
|
|
2282
|
+
blocked: false,
|
|
2283
|
+
force: false
|
|
2284
|
+
});
|
|
2285
|
+
requestImmediateServerSync(session.server, 0);
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
async applyFileWriteResults(server, sessionResults, sentFileWrites) {
|
|
2290
|
+
if (!server || !Array.isArray(sessionResults)) {
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
for (const update of sessionResults) {
|
|
2294
|
+
const session = state.sessions.get(
|
|
2295
|
+
makeSessionKey(server.id, update?.id)
|
|
2296
|
+
);
|
|
2297
|
+
if (!session || !Array.isArray(update?.fileWrites)) {
|
|
2298
|
+
continue;
|
|
2299
|
+
}
|
|
2300
|
+
for (const result of update.fileWrites) {
|
|
2301
|
+
const filePath = typeof result?.path === 'string'
|
|
2302
|
+
? result.path
|
|
2303
|
+
: '';
|
|
2304
|
+
if (!filePath) continue;
|
|
2305
|
+
const entry = this.getTextFileEntry(filePath, session);
|
|
2306
|
+
const sentWrite = sentFileWrites?.get(update.id)?.get(filePath)
|
|
2307
|
+
|| null;
|
|
2308
|
+
if (!entry) {
|
|
2309
|
+
this.clearPendingFileWrite(session.key, filePath);
|
|
2310
|
+
continue;
|
|
2311
|
+
}
|
|
2312
|
+
if (result.status === 'ok') {
|
|
2313
|
+
const currentWrite = this.getPendingFileWrite(
|
|
2314
|
+
session,
|
|
2315
|
+
filePath
|
|
2316
|
+
);
|
|
2317
|
+
const sentContent = sentWrite?.content
|
|
2318
|
+
?? this.getCurrentTextFileContent(filePath, session);
|
|
2319
|
+
entry.content = sentContent;
|
|
2320
|
+
entry.version = typeof result.version === 'string'
|
|
2321
|
+
? result.version
|
|
2322
|
+
: entry.version || '';
|
|
2323
|
+
entry.contentVersion = entry.version;
|
|
2324
|
+
entry.readonly = !!result.readonly;
|
|
2325
|
+
entry.lastDismissedRemoteVersion = '';
|
|
2326
|
+
const hasNewerPendingWrite = !!(
|
|
2327
|
+
currentWrite
|
|
2328
|
+
&& sentWrite
|
|
2329
|
+
&& (
|
|
2330
|
+
currentWrite.content !== sentWrite.content
|
|
2331
|
+
|| currentWrite.expectedVersion
|
|
2332
|
+
!== sentWrite.expectedVersion
|
|
2333
|
+
|| currentWrite.force !== sentWrite.force
|
|
2334
|
+
)
|
|
2335
|
+
);
|
|
2336
|
+
if (hasNewerPendingWrite) {
|
|
2337
|
+
this.queuePendingFileWrite(
|
|
2338
|
+
session,
|
|
2339
|
+
filePath,
|
|
2340
|
+
currentWrite.content,
|
|
2341
|
+
{
|
|
2342
|
+
expectedVersion: entry.version,
|
|
2343
|
+
blocked: false,
|
|
2344
|
+
force: currentWrite.force
|
|
2345
|
+
}
|
|
2346
|
+
);
|
|
2347
|
+
} else {
|
|
2348
|
+
this.clearPendingFileWrite(session.key, filePath);
|
|
2349
|
+
}
|
|
2350
|
+
this.updateActiveEditorReadOnlyState(
|
|
2351
|
+
session,
|
|
2352
|
+
filePath,
|
|
2353
|
+
entry.readonly
|
|
2354
|
+
);
|
|
2355
|
+
continue;
|
|
2356
|
+
}
|
|
2357
|
+
if (result.status === 'conflict') {
|
|
2358
|
+
this.queuePendingFileWrite(
|
|
2359
|
+
session,
|
|
2360
|
+
filePath,
|
|
2361
|
+
this.getCurrentTextFileContent(filePath, session),
|
|
2362
|
+
{
|
|
2363
|
+
expectedVersion: typeof result.version === 'string'
|
|
2364
|
+
? result.version
|
|
2365
|
+
: entry.version || '',
|
|
2366
|
+
blocked: true,
|
|
2367
|
+
force: false
|
|
2368
|
+
}
|
|
2369
|
+
);
|
|
2370
|
+
await this.resolveTextFileConflict(
|
|
2371
|
+
session,
|
|
2372
|
+
filePath,
|
|
2373
|
+
result,
|
|
2374
|
+
'save-conflict'
|
|
2375
|
+
);
|
|
2376
|
+
continue;
|
|
2377
|
+
}
|
|
2378
|
+
this.queuePendingFileWrite(
|
|
2379
|
+
session,
|
|
2380
|
+
filePath,
|
|
2381
|
+
this.getCurrentTextFileContent(filePath, session),
|
|
2382
|
+
{
|
|
2383
|
+
blocked: true
|
|
2384
|
+
}
|
|
2385
|
+
);
|
|
2386
|
+
alert(result?.error || 'Failed to save file.', {
|
|
2387
|
+
type: 'error',
|
|
2388
|
+
title: 'Save Error'
|
|
2389
|
+
});
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
async checkActiveFileVersion() {
|
|
2395
|
+
if (
|
|
2396
|
+
this.fileVersionCheckPromise
|
|
2397
|
+
|| document.visibilityState === 'hidden'
|
|
2398
|
+
|| isConfirmModalOpen()
|
|
2399
|
+
) {
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
2402
|
+
const session = this.currentSession;
|
|
2403
|
+
const filePath = session?.editorState?.activeFilePath || '';
|
|
2404
|
+
if (!this.isActiveTextFile(session, filePath)) {
|
|
2405
|
+
return;
|
|
2406
|
+
}
|
|
2407
|
+
const entry = this.getTextFileEntry(filePath, session);
|
|
2408
|
+
if (!entry || entry.readonly) {
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
this.fileVersionCheckPromise = (async () => {
|
|
2412
|
+
try {
|
|
2413
|
+
const info = await this.readTextFileInfo(session, filePath);
|
|
2414
|
+
if (
|
|
2415
|
+
!info
|
|
2416
|
+
|| typeof info.version !== 'string'
|
|
2417
|
+
|| !info.version
|
|
2418
|
+
|| info.version === entry.version
|
|
2419
|
+
|| info.version === entry.lastDismissedRemoteVersion
|
|
2420
|
+
) {
|
|
2421
|
+
return;
|
|
2422
|
+
}
|
|
2423
|
+
const pendingWrite = this.getPendingFileWrite(session, filePath);
|
|
2424
|
+
if (pendingWrite?.blocked) {
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2427
|
+
await this.resolveTextFileConflict(
|
|
2428
|
+
session,
|
|
2429
|
+
filePath,
|
|
2430
|
+
info,
|
|
2431
|
+
'remote-change'
|
|
2432
|
+
);
|
|
2433
|
+
} catch (error) {
|
|
2434
|
+
console.warn('Failed to check file version:', error);
|
|
2435
|
+
}
|
|
2436
|
+
})();
|
|
2437
|
+
try {
|
|
2438
|
+
await this.fileVersionCheckPromise;
|
|
2439
|
+
} finally {
|
|
2440
|
+
this.fileVersionCheckPromise = null;
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
clearPendingFileWrite(sessionKey, filePath) {
|
|
2445
|
+
const pending = pendingChanges.sessions.get(sessionKey);
|
|
2446
|
+
pending?.fileWrites?.delete(filePath);
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
remapTreePath(pathValue, oldPath, newPath, isDirectory) {
|
|
2450
|
+
if (typeof pathValue !== 'string' || pathValue.length === 0) {
|
|
2451
|
+
return pathValue;
|
|
2452
|
+
}
|
|
2453
|
+
if (pathValue === oldPath) {
|
|
2454
|
+
return newPath;
|
|
2455
|
+
}
|
|
2456
|
+
if (
|
|
2457
|
+
isDirectory
|
|
2458
|
+
&& pathValue.startsWith(`${oldPath}/`)
|
|
2459
|
+
) {
|
|
2460
|
+
return `${newPath}${pathValue.slice(oldPath.length)}`;
|
|
2461
|
+
}
|
|
2462
|
+
return pathValue;
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
remapWorkspaceTabKey(key, oldPath, newPath, isDirectory) {
|
|
2466
|
+
if (!isFileWorkspaceTabKey(key)) return key;
|
|
2467
|
+
const filePath = workspaceKeyToFilePath(key);
|
|
2468
|
+
const nextPath = this.remapTreePath(
|
|
2469
|
+
filePath,
|
|
2470
|
+
oldPath,
|
|
2471
|
+
newPath,
|
|
2472
|
+
isDirectory
|
|
2473
|
+
);
|
|
2474
|
+
if (!nextPath) {
|
|
2475
|
+
return key;
|
|
2476
|
+
}
|
|
2477
|
+
return isMarkdownPreviewWorkspaceTabKey(key)
|
|
2478
|
+
? makeMarkdownPreviewWorkspaceTabKey(nextPath)
|
|
2479
|
+
: makeFileWorkspaceTabKey(nextPath);
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
cloneRenamedModelEntry(entry, nextPath) {
|
|
2483
|
+
if (!entry || typeof entry !== 'object') return entry;
|
|
2484
|
+
const nextEntry = {
|
|
2485
|
+
...entry
|
|
2486
|
+
};
|
|
2487
|
+
if (nextEntry.model) {
|
|
2488
|
+
let nextContent = nextEntry.content;
|
|
2489
|
+
try {
|
|
2490
|
+
if (typeof nextEntry.model.getValue === 'function') {
|
|
2491
|
+
nextContent = nextEntry.model.getValue();
|
|
2492
|
+
}
|
|
2493
|
+
} catch {
|
|
2494
|
+
// Ignore content extraction failure and keep cached content.
|
|
2495
|
+
}
|
|
2496
|
+
nextEntry.content = nextContent;
|
|
2497
|
+
|
|
2498
|
+
if (
|
|
2499
|
+
this.monacoInstance
|
|
2500
|
+
&& typeof nextEntry.model.getLanguageId === 'function'
|
|
1535
2501
|
) {
|
|
1536
2502
|
const oldModel = nextEntry.model;
|
|
1537
2503
|
const languageId = oldModel.getLanguageId();
|
|
@@ -1712,6 +2678,17 @@ class EditorManager {
|
|
|
1712
2678
|
visualChanged = true;
|
|
1713
2679
|
}
|
|
1714
2680
|
|
|
2681
|
+
const nextMarkdownSplitPath = this.remapTreePath(
|
|
2682
|
+
this.getMarkdownSplitPath(session),
|
|
2683
|
+
oldPath,
|
|
2684
|
+
newPath,
|
|
2685
|
+
isDirectory
|
|
2686
|
+
);
|
|
2687
|
+
if (nextMarkdownSplitPath !== this.getMarkdownSplitPath(session)) {
|
|
2688
|
+
session.workspaceState.markdownSplitPath = nextMarkdownSplitPath;
|
|
2689
|
+
visualChanged = true;
|
|
2690
|
+
}
|
|
2691
|
+
|
|
1715
2692
|
const nextActiveTabKey = this.remapWorkspaceTabKey(
|
|
1716
2693
|
session.workspaceState.activeTabKey,
|
|
1717
2694
|
oldPath,
|
|
@@ -1840,6 +2817,17 @@ class EditorManager {
|
|
|
1840
2817
|
visualChanged = true;
|
|
1841
2818
|
}
|
|
1842
2819
|
|
|
2820
|
+
if (
|
|
2821
|
+
this.pathMatchesTarget(
|
|
2822
|
+
this.getMarkdownSplitPath(session),
|
|
2823
|
+
targetPath,
|
|
2824
|
+
isDirectory
|
|
2825
|
+
)
|
|
2826
|
+
) {
|
|
2827
|
+
session.workspaceState.markdownSplitPath = '';
|
|
2828
|
+
visualChanged = true;
|
|
2829
|
+
}
|
|
2830
|
+
|
|
1843
2831
|
if (session.editorState.viewStates.size > 0) {
|
|
1844
2832
|
const nextViewStates = new Map();
|
|
1845
2833
|
let changed = false;
|
|
@@ -2138,24 +3126,7 @@ class EditorManager {
|
|
|
2138
3126
|
}
|
|
2139
3127
|
|
|
2140
3128
|
refreshSessionTree(session) {
|
|
2141
|
-
|
|
2142
|
-
session.fileTreeRenderToken = (session.fileTreeRenderToken || 0) + 1;
|
|
2143
|
-
const renderToken = session.fileTreeRenderToken;
|
|
2144
|
-
const scrollTop = session.fileTreeElement.scrollTop;
|
|
2145
|
-
void this.renderTree(
|
|
2146
|
-
session.cwd,
|
|
2147
|
-
session.fileTreeElement,
|
|
2148
|
-
session,
|
|
2149
|
-
renderToken
|
|
2150
|
-
).finally(() => {
|
|
2151
|
-
if (
|
|
2152
|
-
session.fileTreeElement
|
|
2153
|
-
&& session.fileTreeRenderToken === renderToken
|
|
2154
|
-
) {
|
|
2155
|
-
session.fileTreeElement.scrollTop = scrollTop;
|
|
2156
|
-
}
|
|
2157
|
-
});
|
|
2158
|
-
this.updateTreeAutoRefresh();
|
|
3129
|
+
this.requestSessionTreeRefresh(session);
|
|
2159
3130
|
}
|
|
2160
3131
|
|
|
2161
3132
|
isSessionTreeVisible(session) {
|
|
@@ -2167,63 +3138,220 @@ class EditorManager {
|
|
|
2167
3138
|
}
|
|
2168
3139
|
|
|
2169
3140
|
refreshVisibleSessionTrees() {
|
|
2170
|
-
|
|
2171
|
-
if (this.canRefreshSessionTree(session)) {
|
|
2172
|
-
this.requestSessionTreeRefresh(session);
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
3141
|
+
this.requestVisibleTreeRefresh();
|
|
2175
3142
|
}
|
|
2176
3143
|
|
|
2177
3144
|
requestSessionTreeRefresh(session, { force = false } = {}) {
|
|
2178
|
-
if (!
|
|
3145
|
+
if (!session?.fileTreeElement) {
|
|
2179
3146
|
this.updateTreeAutoRefresh();
|
|
2180
3147
|
return;
|
|
2181
3148
|
}
|
|
2182
|
-
if (session
|
|
2183
|
-
|
|
2184
|
-
requestAnimationFrame(() => {
|
|
2185
|
-
session.fileTreeRefreshQueued = false;
|
|
2186
|
-
if (force || this.canRefreshSessionTree(session)) {
|
|
2187
|
-
this.refreshSessionTree(session);
|
|
2188
|
-
} else {
|
|
2189
|
-
this.updateTreeAutoRefresh();
|
|
2190
|
-
}
|
|
2191
|
-
});
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
updateTreeAutoRefresh() {
|
|
2195
|
-
const shouldRun = (
|
|
2196
|
-
document.visibilityState === 'visible'
|
|
2197
|
-
&& Array.from(state.sessions.values()).some(
|
|
2198
|
-
(session) => this.canRefreshSessionTree(session)
|
|
2199
|
-
)
|
|
2200
|
-
);
|
|
2201
|
-
if (shouldRun && !this.treeRefreshTimer) {
|
|
2202
|
-
this.treeRefreshTimer = window.setInterval(() => {
|
|
2203
|
-
if (document.visibilityState !== 'visible') {
|
|
2204
|
-
this.updateTreeAutoRefresh();
|
|
2205
|
-
return;
|
|
2206
|
-
}
|
|
2207
|
-
const hasVisibleTrees = Array.from(
|
|
2208
|
-
state.sessions.values()
|
|
2209
|
-
).some((session) => this.canRefreshSessionTree(session));
|
|
2210
|
-
if (!hasVisibleTrees) {
|
|
2211
|
-
this.updateTreeAutoRefresh();
|
|
2212
|
-
return;
|
|
2213
|
-
}
|
|
2214
|
-
this.refreshVisibleSessionTrees();
|
|
2215
|
-
}, FILE_TREE_REFRESH_INTERVAL_MS);
|
|
3149
|
+
if (!force && !this.canRefreshSessionTree(session)) {
|
|
3150
|
+
this.updateTreeAutoRefresh();
|
|
2216
3151
|
return;
|
|
2217
3152
|
}
|
|
2218
|
-
if (
|
|
2219
|
-
|
|
2220
|
-
this.treeRefreshTimer = null;
|
|
3153
|
+
if (force) {
|
|
3154
|
+
this.pendingForcedTreeRefreshSessions.add(session.key);
|
|
2221
3155
|
}
|
|
3156
|
+
this.scheduleTreeRefreshBatch();
|
|
2222
3157
|
}
|
|
2223
3158
|
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
3159
|
+
requestVisibleTreeRefresh() {
|
|
3160
|
+
this.scheduleTreeRefreshBatch();
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3163
|
+
scheduleTreeRefreshBatch() {
|
|
3164
|
+
if (this.treeRefreshBatchQueued) {
|
|
3165
|
+
return;
|
|
3166
|
+
}
|
|
3167
|
+
this.treeRefreshBatchQueued = true;
|
|
3168
|
+
requestAnimationFrame(() => {
|
|
3169
|
+
this.treeRefreshBatchQueued = false;
|
|
3170
|
+
void this.flushTreeRefreshBatch();
|
|
3171
|
+
});
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
getTreeRefreshRequestKey(server, dirPath) {
|
|
3175
|
+
return `${server?.id || 'main'}:${dirPath}`;
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
getSessionTreeRefreshPaths(session) {
|
|
3179
|
+
if (!session?.cwd) {
|
|
3180
|
+
return [];
|
|
3181
|
+
}
|
|
3182
|
+
return uniqueStringList([
|
|
3183
|
+
session.cwd,
|
|
3184
|
+
...(session.sharedWorkspaceState?.expandedPaths || [])
|
|
3185
|
+
]);
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
collectTreeRefreshSessions() {
|
|
3189
|
+
const sessions = [];
|
|
3190
|
+
for (const session of state.sessions.values()) {
|
|
3191
|
+
if (this.canRefreshSessionTree(session)) {
|
|
3192
|
+
sessions.push(session);
|
|
3193
|
+
continue;
|
|
3194
|
+
}
|
|
3195
|
+
if (
|
|
3196
|
+
this.pendingForcedTreeRefreshSessions.has(session.key)
|
|
3197
|
+
&& this.isSessionTreeVisible(session)
|
|
3198
|
+
) {
|
|
3199
|
+
sessions.push(session);
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
return sessions;
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
async fetchTreeDirectoryListing(server, dirPath) {
|
|
3206
|
+
const key = this.getTreeRefreshRequestKey(server, dirPath);
|
|
3207
|
+
const existing = this.treeDirectoryFetches.get(key);
|
|
3208
|
+
if (existing) {
|
|
3209
|
+
return existing;
|
|
3210
|
+
}
|
|
3211
|
+
|
|
3212
|
+
const request = (async () => {
|
|
3213
|
+
const response = await server.fetch(
|
|
3214
|
+
`/api/fs/list?path=${encodeURIComponent(dirPath)}`
|
|
3215
|
+
);
|
|
3216
|
+
if (!response.ok) {
|
|
3217
|
+
throw new Error(`Failed to list path: ${dirPath}`);
|
|
3218
|
+
}
|
|
3219
|
+
const payload = await response.json();
|
|
3220
|
+
return {
|
|
3221
|
+
files: Array.isArray(payload)
|
|
3222
|
+
? payload
|
|
3223
|
+
: Array.isArray(payload?.items)
|
|
3224
|
+
? payload.items
|
|
3225
|
+
: [],
|
|
3226
|
+
creatable: Array.isArray(payload)
|
|
3227
|
+
? false
|
|
3228
|
+
: !!payload?.creatable
|
|
3229
|
+
};
|
|
3230
|
+
})().finally(() => {
|
|
3231
|
+
this.treeDirectoryFetches.delete(key);
|
|
3232
|
+
});
|
|
3233
|
+
|
|
3234
|
+
this.treeDirectoryFetches.set(key, request);
|
|
3235
|
+
return request;
|
|
3236
|
+
}
|
|
3237
|
+
|
|
3238
|
+
async flushTreeRefreshBatch() {
|
|
3239
|
+
if (this.treeRefreshInFlight) {
|
|
3240
|
+
this.treeRefreshRerunRequested = true;
|
|
3241
|
+
return;
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3244
|
+
const sessions = this.collectTreeRefreshSessions();
|
|
3245
|
+
this.pendingForcedTreeRefreshSessions.clear();
|
|
3246
|
+
if (sessions.length === 0) {
|
|
3247
|
+
this.updateTreeAutoRefresh();
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
this.treeRefreshInFlight = true;
|
|
3252
|
+
const renderPlans = sessions.map((session) => {
|
|
3253
|
+
session.fileTreeRenderToken = (session.fileTreeRenderToken || 0) + 1;
|
|
3254
|
+
return {
|
|
3255
|
+
session,
|
|
3256
|
+
renderToken: session.fileTreeRenderToken,
|
|
3257
|
+
scrollTop: session.fileTreeElement?.scrollTop || 0
|
|
3258
|
+
};
|
|
3259
|
+
});
|
|
3260
|
+
|
|
3261
|
+
const requestEntries = new Map();
|
|
3262
|
+
for (const { session } of renderPlans) {
|
|
3263
|
+
for (const dirPath of this.getSessionTreeRefreshPaths(session)) {
|
|
3264
|
+
requestEntries.set(
|
|
3265
|
+
this.getTreeRefreshRequestKey(session.server, dirPath),
|
|
3266
|
+
{
|
|
3267
|
+
server: session.server,
|
|
3268
|
+
dirPath
|
|
3269
|
+
}
|
|
3270
|
+
);
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
const directorySnapshots = new Map();
|
|
3275
|
+
await Promise.all(
|
|
3276
|
+
Array.from(requestEntries.entries()).map(
|
|
3277
|
+
async ([key, entry]) => {
|
|
3278
|
+
try {
|
|
3279
|
+
directorySnapshots.set(
|
|
3280
|
+
key,
|
|
3281
|
+
await this.fetchTreeDirectoryListing(
|
|
3282
|
+
entry.server,
|
|
3283
|
+
entry.dirPath
|
|
3284
|
+
)
|
|
3285
|
+
);
|
|
3286
|
+
} catch (error) {
|
|
3287
|
+
console.error(
|
|
3288
|
+
'Failed to fetch tree directory:',
|
|
3289
|
+
entry.dirPath,
|
|
3290
|
+
error
|
|
3291
|
+
);
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
)
|
|
3295
|
+
);
|
|
3296
|
+
|
|
3297
|
+
for (const plan of renderPlans) {
|
|
3298
|
+
const { session, renderToken, scrollTop } = plan;
|
|
3299
|
+
if (!session.fileTreeElement) {
|
|
3300
|
+
continue;
|
|
3301
|
+
}
|
|
3302
|
+
this.renderTreeFromSnapshots(
|
|
3303
|
+
session.cwd,
|
|
3304
|
+
session.fileTreeElement,
|
|
3305
|
+
session,
|
|
3306
|
+
directorySnapshots,
|
|
3307
|
+
renderToken
|
|
3308
|
+
);
|
|
3309
|
+
if (session.fileTreeRenderToken === renderToken) {
|
|
3310
|
+
session.fileTreeElement.scrollTop = scrollTop;
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
this.treeRefreshInFlight = false;
|
|
3315
|
+
this.updateTreeAutoRefresh();
|
|
3316
|
+
if (this.treeRefreshRerunRequested) {
|
|
3317
|
+
this.treeRefreshRerunRequested = false;
|
|
3318
|
+
this.scheduleTreeRefreshBatch();
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
updateTreeAutoRefresh() {
|
|
3323
|
+
const shouldRun = (
|
|
3324
|
+
document.visibilityState === 'visible'
|
|
3325
|
+
&& Array.from(state.sessions.values()).some(
|
|
3326
|
+
(session) => this.canRefreshSessionTree(session)
|
|
3327
|
+
)
|
|
3328
|
+
);
|
|
3329
|
+
if (shouldRun && !this.treeRefreshTimer) {
|
|
3330
|
+
this.treeRefreshTimer = window.setInterval(() => {
|
|
3331
|
+
if (document.visibilityState !== 'visible') {
|
|
3332
|
+
this.updateTreeAutoRefresh();
|
|
3333
|
+
return;
|
|
3334
|
+
}
|
|
3335
|
+
const hasVisibleTrees = Array.from(
|
|
3336
|
+
state.sessions.values()
|
|
3337
|
+
).some((session) => this.canRefreshSessionTree(session));
|
|
3338
|
+
if (!hasVisibleTrees) {
|
|
3339
|
+
this.updateTreeAutoRefresh();
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3342
|
+
this.requestVisibleTreeRefresh();
|
|
3343
|
+
}, FILE_TREE_REFRESH_INTERVAL_MS);
|
|
3344
|
+
return;
|
|
3345
|
+
}
|
|
3346
|
+
if (!shouldRun && this.treeRefreshTimer) {
|
|
3347
|
+
window.clearInterval(this.treeRefreshTimer);
|
|
3348
|
+
this.treeRefreshTimer = null;
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
setSelectedTreePath(session, path, { preserveFocus = false } = {}) {
|
|
3353
|
+
if (!session) return;
|
|
3354
|
+
const nextPath = typeof path === 'string' ? path : '';
|
|
2227
3355
|
if (session.selectedTreePath === nextPath) return;
|
|
2228
3356
|
session.selectedTreePath = nextPath;
|
|
2229
3357
|
if (preserveFocus && nextPath) {
|
|
@@ -2405,7 +3533,7 @@ class EditorManager {
|
|
|
2405
3533
|
isDirectory: !!payload.isDirectory,
|
|
2406
3534
|
renameable: true
|
|
2407
3535
|
});
|
|
2408
|
-
} catch (
|
|
3536
|
+
} catch (_error) {
|
|
2409
3537
|
alert(error.message || 'Failed to create path', {
|
|
2410
3538
|
type: 'error',
|
|
2411
3539
|
title: 'Files'
|
|
@@ -2477,7 +3605,7 @@ class EditorManager {
|
|
|
2477
3605
|
);
|
|
2478
3606
|
this.requestSessionTreeRefresh(session);
|
|
2479
3607
|
session.fileTreeElement?.focus({ preventScroll: true });
|
|
2480
|
-
} catch (
|
|
3608
|
+
} catch (_error) {
|
|
2481
3609
|
alert(error.message || 'Failed to delete path', {
|
|
2482
3610
|
type: 'error',
|
|
2483
3611
|
title: 'Files'
|
|
@@ -2652,7 +3780,7 @@ class EditorManager {
|
|
|
2652
3780
|
list.appendChild(row);
|
|
2653
3781
|
}
|
|
2654
3782
|
|
|
2655
|
-
updateTreeItem(li, file, session
|
|
3783
|
+
updateTreeItem(li, file, session) {
|
|
2656
3784
|
li.dataset.path = file.path;
|
|
2657
3785
|
li.dataset.isDirectory = file.isDirectory ? '1' : '0';
|
|
2658
3786
|
li.dataset.renameable = file.renameable ? '1' : '0';
|
|
@@ -2870,7 +3998,7 @@ class EditorManager {
|
|
|
2870
3998
|
});
|
|
2871
3999
|
|
|
2872
4000
|
icon.innerHTML = this.getIcon(file.name, true, true);
|
|
2873
|
-
|
|
4001
|
+
this.requestSessionTreeRefresh(session);
|
|
2874
4002
|
this.updateTreeAutoRefresh();
|
|
2875
4003
|
session.fileTreeElement?.focus({ preventScroll: true });
|
|
2876
4004
|
return;
|
|
@@ -2914,7 +4042,7 @@ class EditorManager {
|
|
|
2914
4042
|
}
|
|
2915
4043
|
}
|
|
2916
4044
|
|
|
2917
|
-
reconcileTreeList(list, dirPath, files, creatable, session
|
|
4045
|
+
reconcileTreeList(list, dirPath, files, creatable, session) {
|
|
2918
4046
|
const existingItems = new Map();
|
|
2919
4047
|
Array.from(list.children).forEach((child) => {
|
|
2920
4048
|
if (child.tagName === 'LI' && child.dataset.path) {
|
|
@@ -2930,7 +4058,7 @@ class EditorManager {
|
|
|
2930
4058
|
} else {
|
|
2931
4059
|
existingItems.delete(file.path);
|
|
2932
4060
|
}
|
|
2933
|
-
this.updateTreeItem(li, file, session
|
|
4061
|
+
this.updateTreeItem(li, file, session);
|
|
2934
4062
|
orderedItems.push(li);
|
|
2935
4063
|
}
|
|
2936
4064
|
|
|
@@ -2962,12 +4090,32 @@ class EditorManager {
|
|
|
2962
4090
|
});
|
|
2963
4091
|
|
|
2964
4092
|
this.editor.onDidChangeModelContent(() => {
|
|
4093
|
+
if (this.suppressFileWriteCapture) return;
|
|
2965
4094
|
if (!this.currentSession) return;
|
|
2966
4095
|
const filePath = this.currentSession.editorState.activeFilePath;
|
|
2967
4096
|
if (!filePath) return;
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
4097
|
+
const entry = this.getTextFileEntry(filePath, this.currentSession);
|
|
4098
|
+
if (!entry) return;
|
|
4099
|
+
const nextContent = this.editor.getValue();
|
|
4100
|
+
if (
|
|
4101
|
+
nextContent === (entry.content || '')
|
|
4102
|
+
&& (entry.contentVersion || '') === (entry.version || '')
|
|
4103
|
+
) {
|
|
4104
|
+
this.clearPendingFileWrite(this.currentSession.key, filePath);
|
|
4105
|
+
return;
|
|
4106
|
+
}
|
|
4107
|
+
entry.lastDismissedRemoteVersion = '';
|
|
4108
|
+
this.queuePendingFileWrite(
|
|
4109
|
+
this.currentSession,
|
|
4110
|
+
filePath,
|
|
4111
|
+
nextContent
|
|
4112
|
+
);
|
|
4113
|
+
if (isSupportedMarkdownPath(filePath)) {
|
|
4114
|
+
this.scheduleMarkdownPreviewRender(
|
|
4115
|
+
filePath,
|
|
4116
|
+
this.currentSession
|
|
4117
|
+
);
|
|
4118
|
+
}
|
|
2971
4119
|
});
|
|
2972
4120
|
|
|
2973
4121
|
monaco.editor.defineTheme('solarized-dark', {
|
|
@@ -3005,6 +4153,611 @@ class EditorManager {
|
|
|
3005
4153
|
});
|
|
3006
4154
|
}
|
|
3007
4155
|
|
|
4156
|
+
clearMarkdownPreview() {
|
|
4157
|
+
const state = this.markdownPreviewState;
|
|
4158
|
+
state.renderToken += 1;
|
|
4159
|
+
clearTimeout(state.renderTimer);
|
|
4160
|
+
state.renderTimer = 0;
|
|
4161
|
+
state.path = '';
|
|
4162
|
+
state.sessionKey = '';
|
|
4163
|
+
state.pendingHash = '';
|
|
4164
|
+
if (this.markdownPreviewContent) {
|
|
4165
|
+
this.markdownPreviewContent.innerHTML = '';
|
|
4166
|
+
}
|
|
4167
|
+
if (this.markdownPreviewScroll) {
|
|
4168
|
+
this.markdownPreviewScroll.scrollTop = 0;
|
|
4169
|
+
}
|
|
4170
|
+
}
|
|
4171
|
+
|
|
4172
|
+
hideMarkdownPreview() {
|
|
4173
|
+
if (this.contentContainer) {
|
|
4174
|
+
this.contentContainer.classList.remove('markdown-split-active');
|
|
4175
|
+
}
|
|
4176
|
+
if (this.markdownPreviewContainer) {
|
|
4177
|
+
this.markdownPreviewContainer.style.display = 'none';
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
|
|
4181
|
+
getMarkdownSourceContent(filePath, session = this.currentSession) {
|
|
4182
|
+
const entry = this.getModel(filePath, session);
|
|
4183
|
+
if (!entry || entry.type !== 'text') {
|
|
4184
|
+
return '';
|
|
4185
|
+
}
|
|
4186
|
+
if (entry.model && typeof entry.model.getValue === 'function') {
|
|
4187
|
+
return entry.model.getValue();
|
|
4188
|
+
}
|
|
4189
|
+
return typeof entry.content === 'string' ? entry.content : '';
|
|
4190
|
+
}
|
|
4191
|
+
|
|
4192
|
+
resolveMarkdownPreviewImageUrl(filePath, src, session) {
|
|
4193
|
+
const resolved = resolveMarkdownLocalTarget(filePath, src);
|
|
4194
|
+
if (resolved && isSupportedImagePath(resolved.path)) {
|
|
4195
|
+
return session.server.resolveUrl(
|
|
4196
|
+
`/api/fs/raw?path=${encodeURIComponent(resolved.path)}`
|
|
4197
|
+
+ `&token=${session.server.token}`
|
|
4198
|
+
);
|
|
4199
|
+
}
|
|
4200
|
+
return src;
|
|
4201
|
+
}
|
|
4202
|
+
|
|
4203
|
+
decorateMarkdownPreviewContent(root, filePath, session) {
|
|
4204
|
+
if (!(root instanceof DocumentFragment) && !(root instanceof Element)) {
|
|
4205
|
+
return;
|
|
4206
|
+
}
|
|
4207
|
+
const headingIds = new Map();
|
|
4208
|
+
for (const heading of root.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
|
|
4209
|
+
const baseId = slugifyMarkdownHeading(heading.textContent || '')
|
|
4210
|
+
|| 'section';
|
|
4211
|
+
const nextCount = (headingIds.get(baseId) || 0) + 1;
|
|
4212
|
+
headingIds.set(baseId, nextCount);
|
|
4213
|
+
heading.id = nextCount === 1
|
|
4214
|
+
? baseId
|
|
4215
|
+
: `${baseId}-${nextCount}`;
|
|
4216
|
+
}
|
|
4217
|
+
|
|
4218
|
+
for (const image of root.querySelectorAll('img[src]')) {
|
|
4219
|
+
const src = String(image.getAttribute('src') || '').trim();
|
|
4220
|
+
if (!src) {
|
|
4221
|
+
continue;
|
|
4222
|
+
}
|
|
4223
|
+
image.loading = 'lazy';
|
|
4224
|
+
image.decoding = 'async';
|
|
4225
|
+
image.src = this.resolveMarkdownPreviewImageUrl(
|
|
4226
|
+
filePath,
|
|
4227
|
+
src,
|
|
4228
|
+
session
|
|
4229
|
+
);
|
|
4230
|
+
}
|
|
4231
|
+
|
|
4232
|
+
for (const link of root.querySelectorAll('a[href]')) {
|
|
4233
|
+
const href = String(link.getAttribute('href') || '').trim();
|
|
4234
|
+
if (!href) {
|
|
4235
|
+
continue;
|
|
4236
|
+
}
|
|
4237
|
+
const resolved = resolveMarkdownLocalTarget(filePath, href);
|
|
4238
|
+
if (resolved) {
|
|
4239
|
+
link.dataset.markdownLocalPath = resolved.path;
|
|
4240
|
+
link.dataset.markdownLocalHash = resolved.hash || '';
|
|
4241
|
+
continue;
|
|
4242
|
+
}
|
|
4243
|
+
if (!href.startsWith('#')) {
|
|
4244
|
+
link.target = '_blank';
|
|
4245
|
+
link.rel = 'noreferrer noopener';
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
|
|
4250
|
+
getAgentMarkdownBaseDirectory(agentTab, message) {
|
|
4251
|
+
const messageCwd = String(message?.cwd || '').trim();
|
|
4252
|
+
if (messageCwd) {
|
|
4253
|
+
return messageCwd;
|
|
4254
|
+
}
|
|
4255
|
+
const tabCwd = String(agentTab?.cwd || '').trim();
|
|
4256
|
+
if (tabCwd) {
|
|
4257
|
+
return tabCwd;
|
|
4258
|
+
}
|
|
4259
|
+
const session = this.currentSession;
|
|
4260
|
+
return String(session?.cwd || session?.initialCwd || '').trim();
|
|
4261
|
+
}
|
|
4262
|
+
|
|
4263
|
+
async enhanceAgentMarkdownBody(agentTab, message, body) {
|
|
4264
|
+
if (!(body instanceof HTMLElement) || !message?.text) {
|
|
4265
|
+
return;
|
|
4266
|
+
}
|
|
4267
|
+
const session = this.currentSession;
|
|
4268
|
+
if (!session) {
|
|
4269
|
+
return;
|
|
4270
|
+
}
|
|
4271
|
+
|
|
4272
|
+
const renderToken = `${Date.now()}:${Math.random()}`;
|
|
4273
|
+
body.dataset.markdownRenderToken = renderToken;
|
|
4274
|
+
|
|
4275
|
+
try {
|
|
4276
|
+
const { renderer } = await loadMarkdownPreviewBundle();
|
|
4277
|
+
if (
|
|
4278
|
+
!body.isConnected
|
|
4279
|
+
|| body.dataset.markdownRenderToken !== renderToken
|
|
4280
|
+
) {
|
|
4281
|
+
return;
|
|
4282
|
+
}
|
|
4283
|
+
const rendered = renderer.render(String(message.text || ''));
|
|
4284
|
+
const sanitized = DOMPurify.sanitize(rendered, {
|
|
4285
|
+
USE_PROFILES: {
|
|
4286
|
+
html: true,
|
|
4287
|
+
mathMl: true,
|
|
4288
|
+
svg: true
|
|
4289
|
+
}
|
|
4290
|
+
});
|
|
4291
|
+
const template = document.createElement('template');
|
|
4292
|
+
template.innerHTML = sanitized;
|
|
4293
|
+
const basePath = buildMarkdownContextBasePath(
|
|
4294
|
+
'',
|
|
4295
|
+
this.getAgentMarkdownBaseDirectory(agentTab, message)
|
|
4296
|
+
);
|
|
4297
|
+
this.decorateMarkdownPreviewContent(
|
|
4298
|
+
template.content,
|
|
4299
|
+
basePath,
|
|
4300
|
+
session
|
|
4301
|
+
);
|
|
4302
|
+
body.replaceChildren(template.content);
|
|
4303
|
+
} catch {
|
|
4304
|
+
// Keep the lightweight fallback rendering.
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
|
|
4308
|
+
scrollMarkdownPreviewHash(hash) {
|
|
4309
|
+
const nextHash = String(hash || '').trim();
|
|
4310
|
+
if (!nextHash || !this.markdownPreviewScroll) {
|
|
4311
|
+
return;
|
|
4312
|
+
}
|
|
4313
|
+
const id = nextHash.startsWith('#') ? nextHash.slice(1) : nextHash;
|
|
4314
|
+
if (!id) {
|
|
4315
|
+
return;
|
|
4316
|
+
}
|
|
4317
|
+
const target = this.markdownPreviewContent?.querySelector(
|
|
4318
|
+
`#${CSS.escape(id)}`
|
|
4319
|
+
);
|
|
4320
|
+
if (!target) {
|
|
4321
|
+
return;
|
|
4322
|
+
}
|
|
4323
|
+
target.scrollIntoView({
|
|
4324
|
+
behavior: 'smooth',
|
|
4325
|
+
block: 'start'
|
|
4326
|
+
});
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4329
|
+
scheduleMarkdownPreviewRender(filePath, session = this.currentSession) {
|
|
4330
|
+
if (
|
|
4331
|
+
!session
|
|
4332
|
+
|| !filePath
|
|
4333
|
+
|| !isSupportedMarkdownPath(filePath)
|
|
4334
|
+
) {
|
|
4335
|
+
return;
|
|
4336
|
+
}
|
|
4337
|
+
const state = this.markdownPreviewState;
|
|
4338
|
+
clearTimeout(state.renderTimer);
|
|
4339
|
+
state.renderTimer = window.setTimeout(() => {
|
|
4340
|
+
if (
|
|
4341
|
+
this.currentSession?.key !== session.key
|
|
4342
|
+
|| this.currentSession?.editorState.activeFilePath !== filePath
|
|
4343
|
+
) {
|
|
4344
|
+
return;
|
|
4345
|
+
}
|
|
4346
|
+
const activeKey = this.getActiveWorkspaceTabKey(session);
|
|
4347
|
+
const shouldShow = (
|
|
4348
|
+
isMarkdownPreviewWorkspaceTabKey(activeKey)
|
|
4349
|
+
|| this.isMarkdownSplitViewEnabled(session, filePath)
|
|
4350
|
+
);
|
|
4351
|
+
void this.renderMarkdownPreview(filePath, {
|
|
4352
|
+
session,
|
|
4353
|
+
show: shouldShow
|
|
4354
|
+
});
|
|
4355
|
+
}, 60);
|
|
4356
|
+
}
|
|
4357
|
+
|
|
4358
|
+
async renderMarkdownPreview(filePath, options = {}) {
|
|
4359
|
+
const session = options.session || this.currentSession;
|
|
4360
|
+
const show = options.show === true;
|
|
4361
|
+
if (
|
|
4362
|
+
!session
|
|
4363
|
+
|| !filePath
|
|
4364
|
+
|| !this.markdownPreviewContainer
|
|
4365
|
+
|| !this.markdownPreviewContent
|
|
4366
|
+
|| !isSupportedMarkdownPath(filePath)
|
|
4367
|
+
) {
|
|
4368
|
+
return;
|
|
4369
|
+
}
|
|
4370
|
+
const state = this.markdownPreviewState;
|
|
4371
|
+
const renderToken = state.renderToken + 1;
|
|
4372
|
+
state.renderToken = renderToken;
|
|
4373
|
+
state.path = filePath;
|
|
4374
|
+
state.sessionKey = session.key;
|
|
4375
|
+
if (show) {
|
|
4376
|
+
this.markdownPreviewContainer.style.display = 'flex';
|
|
4377
|
+
}
|
|
4378
|
+
|
|
4379
|
+
try {
|
|
4380
|
+
const { renderer } = await loadMarkdownPreviewBundle();
|
|
4381
|
+
if (
|
|
4382
|
+
state.renderToken !== renderToken
|
|
4383
|
+
|| state.path !== filePath
|
|
4384
|
+
|| state.sessionKey !== session.key
|
|
4385
|
+
) {
|
|
4386
|
+
return;
|
|
4387
|
+
}
|
|
4388
|
+
const source = this.getMarkdownSourceContent(filePath, session);
|
|
4389
|
+
const rendered = renderer.render(source || '');
|
|
4390
|
+
const sanitized = DOMPurify.sanitize(rendered, {
|
|
4391
|
+
USE_PROFILES: {
|
|
4392
|
+
html: true,
|
|
4393
|
+
mathMl: true,
|
|
4394
|
+
svg: true
|
|
4395
|
+
}
|
|
4396
|
+
});
|
|
4397
|
+
const template = document.createElement('template');
|
|
4398
|
+
template.innerHTML = sanitized;
|
|
4399
|
+
this.decorateMarkdownPreviewContent(
|
|
4400
|
+
template.content,
|
|
4401
|
+
filePath,
|
|
4402
|
+
session
|
|
4403
|
+
);
|
|
4404
|
+
this.markdownPreviewContent.replaceChildren(template.content);
|
|
4405
|
+
const pendingHash = state.pendingHash;
|
|
4406
|
+
state.pendingHash = '';
|
|
4407
|
+
if (pendingHash) {
|
|
4408
|
+
requestAnimationFrame(() => {
|
|
4409
|
+
this.scrollMarkdownPreviewHash(pendingHash);
|
|
4410
|
+
});
|
|
4411
|
+
}
|
|
4412
|
+
} catch (error) {
|
|
4413
|
+
console.error('Failed to render markdown preview:', error);
|
|
4414
|
+
this.markdownPreviewContent.innerHTML = '';
|
|
4415
|
+
const fallback = document.createElement('div');
|
|
4416
|
+
fallback.className = 'markdown-preview-error';
|
|
4417
|
+
fallback.textContent = 'Failed to render markdown preview.';
|
|
4418
|
+
this.markdownPreviewContent.appendChild(fallback);
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
|
|
4422
|
+
async openLocalMarkdownLink(filePath, hash = '') {
|
|
4423
|
+
const session = this.currentSession;
|
|
4424
|
+
if (!session || !filePath) {
|
|
4425
|
+
return;
|
|
4426
|
+
}
|
|
4427
|
+
if (isSupportedMarkdownPath(filePath)) {
|
|
4428
|
+
this.markdownPreviewState.pendingHash = hash || '';
|
|
4429
|
+
await this.openFile(filePath, session, {
|
|
4430
|
+
activatePreview: true,
|
|
4431
|
+
focusEditor: false
|
|
4432
|
+
});
|
|
4433
|
+
return;
|
|
4434
|
+
}
|
|
4435
|
+
await this.openFile(filePath, session, {
|
|
4436
|
+
focusEditor: false
|
|
4437
|
+
});
|
|
4438
|
+
}
|
|
4439
|
+
|
|
4440
|
+
clearPdfPreview(preserveDocument = false) {
|
|
4441
|
+
const state = this.pdfPreviewState;
|
|
4442
|
+
state.renderToken += 1;
|
|
4443
|
+
clearTimeout(state.relayoutTimer);
|
|
4444
|
+
state.relayoutTimer = 0;
|
|
4445
|
+
if (!preserveDocument) {
|
|
4446
|
+
const documentRef = state.document;
|
|
4447
|
+
state.document = null;
|
|
4448
|
+
state.loadingTask = null;
|
|
4449
|
+
state.path = '';
|
|
4450
|
+
state.sessionKey = '';
|
|
4451
|
+
state.metadata = '';
|
|
4452
|
+
state.renderedWidth = 0;
|
|
4453
|
+
if (documentRef && typeof documentRef.destroy === 'function') {
|
|
4454
|
+
Promise.resolve(documentRef.destroy()).catch(() => {});
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
if (this.pdfPreviewPages) {
|
|
4458
|
+
this.pdfPreviewPages.innerHTML = '';
|
|
4459
|
+
}
|
|
4460
|
+
this.setPdfPreviewStatus('', '');
|
|
4461
|
+
}
|
|
4462
|
+
|
|
4463
|
+
hidePdfPreview() {
|
|
4464
|
+
this.pdfPreviewContainer.style.display = 'none';
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4467
|
+
getPdfPreviewUrl(filePath, session = this.currentSession) {
|
|
4468
|
+
if (!session) return '';
|
|
4469
|
+
return session.server.resolveUrl(
|
|
4470
|
+
`/api/fs/raw?path=${encodeURIComponent(filePath)}`
|
|
4471
|
+
+ `&token=${session.server.token}`
|
|
4472
|
+
);
|
|
4473
|
+
}
|
|
4474
|
+
|
|
4475
|
+
getPdfPreviewTargetWidth() {
|
|
4476
|
+
if (!this.pdfPreviewPages) {
|
|
4477
|
+
return 0;
|
|
4478
|
+
}
|
|
4479
|
+
const width = this.pdfPreviewPages.clientWidth - 36;
|
|
4480
|
+
return Math.max(240, Math.floor(Math.min(width, 960)));
|
|
4481
|
+
}
|
|
4482
|
+
|
|
4483
|
+
setPdfPreviewStatus(primary = '', secondary = '') {
|
|
4484
|
+
const nextPrimary = String(primary || '').trim();
|
|
4485
|
+
const nextSecondary = String(secondary || '').trim();
|
|
4486
|
+
if (this.pdfPreviewStatusPrimary) {
|
|
4487
|
+
this.pdfPreviewStatusPrimary.textContent = nextPrimary;
|
|
4488
|
+
}
|
|
4489
|
+
if (this.pdfPreviewStatusSecondary) {
|
|
4490
|
+
this.pdfPreviewStatusSecondary.textContent = nextSecondary;
|
|
4491
|
+
this.pdfPreviewStatusSecondary.title = nextSecondary;
|
|
4492
|
+
}
|
|
4493
|
+
if (this.pdfPreviewStatus) {
|
|
4494
|
+
this.pdfPreviewStatus.classList.toggle(
|
|
4495
|
+
'is-empty',
|
|
4496
|
+
!nextPrimary && !nextSecondary
|
|
4497
|
+
);
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
|
|
4501
|
+
formatPdfByteSize(bytes) {
|
|
4502
|
+
if (!Number.isFinite(bytes) || bytes <= 0) {
|
|
4503
|
+
return '';
|
|
4504
|
+
}
|
|
4505
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
4506
|
+
let value = bytes;
|
|
4507
|
+
let unitIndex = 0;
|
|
4508
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
4509
|
+
value /= 1024;
|
|
4510
|
+
unitIndex += 1;
|
|
4511
|
+
}
|
|
4512
|
+
const decimals = value >= 100 || unitIndex === 0 ? 0 : 1;
|
|
4513
|
+
return `${value.toFixed(decimals)} ${units[unitIndex]}`;
|
|
4514
|
+
}
|
|
4515
|
+
|
|
4516
|
+
describePdfPageSize(viewport) {
|
|
4517
|
+
if (!viewport) {
|
|
4518
|
+
return '';
|
|
4519
|
+
}
|
|
4520
|
+
const width = Math.min(viewport.width, viewport.height);
|
|
4521
|
+
const height = Math.max(viewport.width, viewport.height);
|
|
4522
|
+
const near = (targetWidth, targetHeight) => (
|
|
4523
|
+
Math.abs(width - targetWidth) < 2
|
|
4524
|
+
&& Math.abs(height - targetHeight) < 2
|
|
4525
|
+
);
|
|
4526
|
+
if (near(595.276, 841.89)) return 'A4';
|
|
4527
|
+
if (near(612, 792)) return 'Letter';
|
|
4528
|
+
return '';
|
|
4529
|
+
}
|
|
4530
|
+
|
|
4531
|
+
async loadPdfMetadata(documentRef) {
|
|
4532
|
+
const parts = [];
|
|
4533
|
+
try {
|
|
4534
|
+
const meta = await documentRef.getMetadata();
|
|
4535
|
+
const version = String(meta?.info?.PDFFormatVersion || '').trim();
|
|
4536
|
+
parts.push(version ? `PDF ${version}` : 'PDF');
|
|
4537
|
+
} catch {
|
|
4538
|
+
parts.push('PDF');
|
|
4539
|
+
}
|
|
4540
|
+
|
|
4541
|
+
try {
|
|
4542
|
+
const firstPage = await documentRef.getPage(1);
|
|
4543
|
+
const pageSize = this.describePdfPageSize(
|
|
4544
|
+
firstPage.getViewport({ scale: 1 })
|
|
4545
|
+
);
|
|
4546
|
+
if (pageSize) {
|
|
4547
|
+
parts.push(pageSize);
|
|
4548
|
+
}
|
|
4549
|
+
} catch {
|
|
4550
|
+
// Ignore optional page-size metadata failures.
|
|
4551
|
+
}
|
|
4552
|
+
|
|
4553
|
+
try {
|
|
4554
|
+
const downloadInfo = await documentRef.getDownloadInfo();
|
|
4555
|
+
const byteSize = this.formatPdfByteSize(downloadInfo?.length);
|
|
4556
|
+
if (byteSize) {
|
|
4557
|
+
parts.push(byteSize);
|
|
4558
|
+
}
|
|
4559
|
+
} catch {
|
|
4560
|
+
// Ignore optional size metadata failures.
|
|
4561
|
+
}
|
|
4562
|
+
|
|
4563
|
+
return parts.join(' · ');
|
|
4564
|
+
}
|
|
4565
|
+
|
|
4566
|
+
schedulePdfPreviewRelayout() {
|
|
4567
|
+
const state = this.pdfPreviewState;
|
|
4568
|
+
if (
|
|
4569
|
+
!this.pdfPreviewContainer
|
|
4570
|
+
|| this.pdfPreviewContainer.style.display === 'none'
|
|
4571
|
+
|| !state.document
|
|
4572
|
+
|| !state.path
|
|
4573
|
+
) {
|
|
4574
|
+
return;
|
|
4575
|
+
}
|
|
4576
|
+
clearTimeout(state.relayoutTimer);
|
|
4577
|
+
state.relayoutTimer = window.setTimeout(() => {
|
|
4578
|
+
const nextWidth = this.getPdfPreviewTargetWidth();
|
|
4579
|
+
if (
|
|
4580
|
+
nextWidth > 0
|
|
4581
|
+
&& Math.abs(nextWidth - state.renderedWidth) > 24
|
|
4582
|
+
) {
|
|
4583
|
+
void this.renderPdfPreview(state.path);
|
|
4584
|
+
}
|
|
4585
|
+
}, 120);
|
|
4586
|
+
}
|
|
4587
|
+
|
|
4588
|
+
async loadPdfDocument(filePath, session, renderToken) {
|
|
4589
|
+
const state = this.pdfPreviewState;
|
|
4590
|
+
const url = this.getPdfPreviewUrl(filePath, session);
|
|
4591
|
+
const pdfjsLib = await loadPdfJs();
|
|
4592
|
+
if (state.renderToken !== renderToken) {
|
|
4593
|
+
return null;
|
|
4594
|
+
}
|
|
4595
|
+
|
|
4596
|
+
let loadingTask = pdfjsLib.getDocument({
|
|
4597
|
+
url
|
|
4598
|
+
});
|
|
4599
|
+
state.loadingTask = loadingTask;
|
|
4600
|
+
try {
|
|
4601
|
+
return await loadingTask.promise;
|
|
4602
|
+
} catch (_error) {
|
|
4603
|
+
if (state.renderToken !== renderToken) {
|
|
4604
|
+
return null;
|
|
4605
|
+
}
|
|
4606
|
+
loadingTask = pdfjsLib.getDocument({
|
|
4607
|
+
url,
|
|
4608
|
+
disableWorker: true
|
|
4609
|
+
});
|
|
4610
|
+
state.loadingTask = loadingTask;
|
|
4611
|
+
return await loadingTask.promise;
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4614
|
+
|
|
4615
|
+
async renderPdfPreview(filePath) {
|
|
4616
|
+
const session = this.currentSession;
|
|
4617
|
+
if (!session || !filePath) {
|
|
4618
|
+
return;
|
|
4619
|
+
}
|
|
4620
|
+
const state = this.pdfPreviewState;
|
|
4621
|
+
const renderToken = state.renderToken + 1;
|
|
4622
|
+
const targetSessionKey = session.key;
|
|
4623
|
+
const nextWidth = this.getPdfPreviewTargetWidth();
|
|
4624
|
+
if (nextWidth <= 0) {
|
|
4625
|
+
requestAnimationFrame(() => {
|
|
4626
|
+
if (
|
|
4627
|
+
this.currentSession?.key === targetSessionKey
|
|
4628
|
+
&& this.currentSession?.editorState.activeFilePath === filePath
|
|
4629
|
+
) {
|
|
4630
|
+
void this.renderPdfPreview(filePath);
|
|
4631
|
+
}
|
|
4632
|
+
});
|
|
4633
|
+
return;
|
|
4634
|
+
}
|
|
4635
|
+
|
|
4636
|
+
if (
|
|
4637
|
+
state.path !== filePath
|
|
4638
|
+
|| state.sessionKey !== targetSessionKey
|
|
4639
|
+
) {
|
|
4640
|
+
this.clearPdfPreview();
|
|
4641
|
+
} else {
|
|
4642
|
+
this.clearPdfPreview(true);
|
|
4643
|
+
}
|
|
4644
|
+
state.renderToken = renderToken;
|
|
4645
|
+
state.path = filePath;
|
|
4646
|
+
state.sessionKey = targetSessionKey;
|
|
4647
|
+
this.setPdfPreviewStatus('Loading PDF…', '');
|
|
4648
|
+
|
|
4649
|
+
try {
|
|
4650
|
+
let documentRef = state.document;
|
|
4651
|
+
if (!documentRef) {
|
|
4652
|
+
documentRef = await this.loadPdfDocument(
|
|
4653
|
+
filePath,
|
|
4654
|
+
session,
|
|
4655
|
+
renderToken
|
|
4656
|
+
);
|
|
4657
|
+
if (!documentRef || state.renderToken !== renderToken) {
|
|
4658
|
+
return;
|
|
4659
|
+
}
|
|
4660
|
+
state.document = documentRef;
|
|
4661
|
+
state.metadata = await this.loadPdfMetadata(documentRef);
|
|
4662
|
+
if (state.renderToken !== renderToken) {
|
|
4663
|
+
return;
|
|
4664
|
+
}
|
|
4665
|
+
}
|
|
4666
|
+
|
|
4667
|
+
state.renderedWidth = nextWidth;
|
|
4668
|
+
if (this.pdfPreviewPages) {
|
|
4669
|
+
this.pdfPreviewPages.innerHTML = '';
|
|
4670
|
+
}
|
|
4671
|
+
const pageCount = documentRef.numPages;
|
|
4672
|
+
this.setPdfPreviewStatus(
|
|
4673
|
+
`${pageCount} page${pageCount === 1 ? '' : 's'}`,
|
|
4674
|
+
state.metadata || 'PDF'
|
|
4675
|
+
);
|
|
4676
|
+
|
|
4677
|
+
for (let pageNumber = 1; pageNumber <= documentRef.numPages; pageNumber += 1) {
|
|
4678
|
+
if (state.renderToken !== renderToken) {
|
|
4679
|
+
return;
|
|
4680
|
+
}
|
|
4681
|
+
const page = await documentRef.getPage(pageNumber);
|
|
4682
|
+
if (state.renderToken !== renderToken) {
|
|
4683
|
+
return;
|
|
4684
|
+
}
|
|
4685
|
+
const baseViewport = page.getViewport({ scale: 1 });
|
|
4686
|
+
const scale = nextWidth / baseViewport.width;
|
|
4687
|
+
const viewport = page.getViewport({ scale });
|
|
4688
|
+
const canvas = document.createElement('canvas');
|
|
4689
|
+
const context = canvas.getContext('2d', {
|
|
4690
|
+
alpha: false
|
|
4691
|
+
});
|
|
4692
|
+
if (!context) {
|
|
4693
|
+
throw new Error('Failed to create PDF canvas context');
|
|
4694
|
+
}
|
|
4695
|
+
const outputScale = Math.max(1, window.devicePixelRatio || 1);
|
|
4696
|
+
canvas.width = Math.ceil(viewport.width * outputScale);
|
|
4697
|
+
canvas.height = Math.ceil(viewport.height * outputScale);
|
|
4698
|
+
canvas.style.width = `${Math.ceil(viewport.width)}px`;
|
|
4699
|
+
canvas.style.height = `${Math.ceil(viewport.height)}px`;
|
|
4700
|
+
context.setTransform(outputScale, 0, 0, outputScale, 0, 0);
|
|
4701
|
+
const textLayer = document.createElement('div');
|
|
4702
|
+
textLayer.className = 'textLayer';
|
|
4703
|
+
const sheet = document.createElement('div');
|
|
4704
|
+
sheet.className = 'pdf-preview-sheet';
|
|
4705
|
+
sheet.style.width = `${Math.ceil(viewport.width)}px`;
|
|
4706
|
+
sheet.style.height = `${Math.ceil(viewport.height)}px`;
|
|
4707
|
+
sheet.style.setProperty('--user-unit', '1');
|
|
4708
|
+
sheet.style.setProperty('--scale-factor', String(scale));
|
|
4709
|
+
sheet.style.setProperty(
|
|
4710
|
+
'--total-scale-factor',
|
|
4711
|
+
String(scale)
|
|
4712
|
+
);
|
|
4713
|
+
sheet.style.setProperty('--scale-round-x', '1px');
|
|
4714
|
+
sheet.style.setProperty('--scale-round-y', '1px');
|
|
4715
|
+
await page.render({
|
|
4716
|
+
canvasContext: context,
|
|
4717
|
+
viewport
|
|
4718
|
+
}).promise;
|
|
4719
|
+
if (state.renderToken !== renderToken) {
|
|
4720
|
+
return;
|
|
4721
|
+
}
|
|
4722
|
+
const textContent = await page.getTextContent();
|
|
4723
|
+
if (state.renderToken !== renderToken) {
|
|
4724
|
+
return;
|
|
4725
|
+
}
|
|
4726
|
+
const textLayerBuilder = new pdfjsLib.TextLayer({
|
|
4727
|
+
textContentSource: textContent,
|
|
4728
|
+
container: textLayer,
|
|
4729
|
+
viewport
|
|
4730
|
+
});
|
|
4731
|
+
await textLayerBuilder.render();
|
|
4732
|
+
if (state.renderToken !== renderToken) {
|
|
4733
|
+
return;
|
|
4734
|
+
}
|
|
4735
|
+
const wrapper = document.createElement('div');
|
|
4736
|
+
wrapper.className = 'pdf-preview-page';
|
|
4737
|
+
wrapper.dataset.pageNumber = String(pageNumber);
|
|
4738
|
+
sheet.appendChild(canvas);
|
|
4739
|
+
sheet.appendChild(textLayer);
|
|
4740
|
+
wrapper.appendChild(sheet);
|
|
4741
|
+
this.pdfPreviewPages?.appendChild(wrapper);
|
|
4742
|
+
}
|
|
4743
|
+
} catch (error) {
|
|
4744
|
+
console.error('Failed to render PDF preview:', error);
|
|
4745
|
+
if (state.renderToken !== renderToken) {
|
|
4746
|
+
return;
|
|
4747
|
+
}
|
|
4748
|
+
this.clearPdfPreview();
|
|
4749
|
+
this.hidePdfPreview();
|
|
4750
|
+
alert(
|
|
4751
|
+
`Failed to load PDF: ${filePath.split('/').pop()}`,
|
|
4752
|
+
{
|
|
4753
|
+
type: 'error',
|
|
4754
|
+
title: 'PDF Preview Error'
|
|
4755
|
+
}
|
|
4756
|
+
);
|
|
4757
|
+
this.closeFile(filePath);
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4760
|
+
|
|
3008
4761
|
updateEditorPaneVisibility() {
|
|
3009
4762
|
if (!this.currentSession) return;
|
|
3010
4763
|
const state = this.currentSession.editorState;
|
|
@@ -3089,6 +4842,7 @@ class EditorManager {
|
|
|
3089
4842
|
}
|
|
3090
4843
|
|
|
3091
4844
|
this.currentSession = session;
|
|
4845
|
+
this.syncMarkdownSplitSupport(session);
|
|
3092
4846
|
if (!session) {
|
|
3093
4847
|
this.pane.style.display = 'none';
|
|
3094
4848
|
this.resizer.style.display = 'none';
|
|
@@ -3126,10 +4880,13 @@ class EditorManager {
|
|
|
3126
4880
|
layout() {
|
|
3127
4881
|
// console.log('[Editor] layout called');
|
|
3128
4882
|
if (!this.currentSession) return;
|
|
4883
|
+
this.syncMarkdownSplitSupport(this.currentSession);
|
|
3129
4884
|
this.currentSession.fitMainTerminalIfVisible();
|
|
3130
4885
|
if (this.editor && this.pane.style.display !== 'none') {
|
|
3131
|
-
const width = this.
|
|
3132
|
-
|
|
4886
|
+
const width = this.monacoContainer?.clientWidth
|
|
4887
|
+
|| this.pane.clientWidth;
|
|
4888
|
+
const height = this.monacoContainer?.clientHeight
|
|
4889
|
+
|| (this.pane.clientHeight - 35);
|
|
3133
4890
|
|
|
3134
4891
|
if (width > 0 && height > 0) {
|
|
3135
4892
|
this.editor.layout({ width, height });
|
|
@@ -3137,56 +4894,56 @@ class EditorManager {
|
|
|
3137
4894
|
this.editor.layout();
|
|
3138
4895
|
}
|
|
3139
4896
|
}
|
|
4897
|
+
this.schedulePdfPreviewRelayout();
|
|
3140
4898
|
}
|
|
3141
4899
|
|
|
3142
|
-
|
|
4900
|
+
renderTreeFromSnapshots(
|
|
3143
4901
|
dirPath,
|
|
3144
4902
|
container,
|
|
3145
4903
|
session,
|
|
4904
|
+
directorySnapshots,
|
|
3146
4905
|
renderToken = session?.fileTreeRenderToken || 0
|
|
3147
4906
|
) {
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
? payload.items
|
|
3158
|
-
: [];
|
|
3159
|
-
const creatable = Array.isArray(payload)
|
|
3160
|
-
? false
|
|
3161
|
-
: !!payload?.creatable;
|
|
3162
|
-
if ((session.fileTreeRenderToken || 0) !== renderToken) return;
|
|
3163
|
-
|
|
3164
|
-
const list = this.ensureTreeList(container);
|
|
3165
|
-
this.reconcileTreeList(
|
|
3166
|
-
list,
|
|
3167
|
-
dirPath,
|
|
3168
|
-
files,
|
|
3169
|
-
creatable,
|
|
3170
|
-
session,
|
|
3171
|
-
renderToken
|
|
3172
|
-
);
|
|
3173
|
-
if ((session.fileTreeRenderToken || 0) !== renderToken) return;
|
|
4907
|
+
const listing = directorySnapshots.get(
|
|
4908
|
+
this.getTreeRefreshRequestKey(session.server, dirPath)
|
|
4909
|
+
);
|
|
4910
|
+
if (!listing) {
|
|
4911
|
+
return;
|
|
4912
|
+
}
|
|
4913
|
+
if ((session.fileTreeRenderToken || 0) !== renderToken) {
|
|
4914
|
+
return;
|
|
4915
|
+
}
|
|
3174
4916
|
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
4917
|
+
const list = this.ensureTreeList(container);
|
|
4918
|
+
this.reconcileTreeList(
|
|
4919
|
+
list,
|
|
4920
|
+
dirPath,
|
|
4921
|
+
listing.files,
|
|
4922
|
+
listing.creatable,
|
|
4923
|
+
session
|
|
4924
|
+
);
|
|
4925
|
+
if ((session.fileTreeRenderToken || 0) !== renderToken) {
|
|
4926
|
+
return;
|
|
4927
|
+
}
|
|
4928
|
+
|
|
4929
|
+
for (const file of listing.files) {
|
|
4930
|
+
if (
|
|
4931
|
+
file.isDirectory
|
|
4932
|
+
&& this.getTreeItemExpanded(file.path, session)
|
|
4933
|
+
) {
|
|
4934
|
+
const item = Array.from(list.children).find(
|
|
4935
|
+
(child) => child.dataset.path === file.path
|
|
4936
|
+
);
|
|
4937
|
+
if (item) {
|
|
4938
|
+
this.renderTreeFromSnapshots(
|
|
4939
|
+
file.path,
|
|
4940
|
+
item,
|
|
4941
|
+
session,
|
|
4942
|
+
directorySnapshots,
|
|
4943
|
+
renderToken
|
|
3182
4944
|
);
|
|
3183
|
-
if (item) {
|
|
3184
|
-
void this.renderTree(file.path, item, session, renderToken);
|
|
3185
|
-
}
|
|
3186
4945
|
}
|
|
3187
4946
|
}
|
|
3188
|
-
} catch (err) {
|
|
3189
|
-
console.error('Failed to render tree:', err);
|
|
3190
4947
|
}
|
|
3191
4948
|
}
|
|
3192
4949
|
|
|
@@ -3209,34 +4966,31 @@ class EditorManager {
|
|
|
3209
4966
|
const state = targetSession.editorState;
|
|
3210
4967
|
const wasOpen = state.openFiles.includes(filePath);
|
|
3211
4968
|
const isImage = isSupportedImagePath(filePath);
|
|
4969
|
+
const isPdf = isSupportedPdfPath(filePath);
|
|
3212
4970
|
|
|
3213
|
-
if (!this.getModel(filePath)) {
|
|
4971
|
+
if (!this.getModel(filePath, targetSession)) {
|
|
3214
4972
|
let model = null;
|
|
3215
4973
|
let content = null;
|
|
3216
4974
|
let readonly = false;
|
|
4975
|
+
let version = '';
|
|
4976
|
+
let size = 0;
|
|
4977
|
+
let mtimeMs = 0;
|
|
3217
4978
|
|
|
3218
|
-
if (!isImage) {
|
|
4979
|
+
if (!isImage && !isPdf) {
|
|
3219
4980
|
try {
|
|
3220
|
-
const
|
|
3221
|
-
|
|
4981
|
+
const data = await this.readTextFileSnapshot(
|
|
4982
|
+
targetSession,
|
|
4983
|
+
filePath
|
|
3222
4984
|
);
|
|
3223
|
-
if (res.status === 415) {
|
|
3224
|
-
await showConfirmModal({
|
|
3225
|
-
title: 'Unsupported File Type',
|
|
3226
|
-
message: 'This file type is not supported yet.',
|
|
3227
|
-
note: 'Only text files and supported images can be opened right now.',
|
|
3228
|
-
confirmLabel: 'OK',
|
|
3229
|
-
hideCancel: true,
|
|
3230
|
-
returnFocus: document.activeElement
|
|
3231
|
-
});
|
|
3232
|
-
return;
|
|
3233
|
-
}
|
|
3234
|
-
if (!res.ok) {
|
|
3235
|
-
throw new Error('Failed to read file');
|
|
3236
|
-
}
|
|
3237
|
-
const data = await res.json();
|
|
3238
4985
|
content = data.content;
|
|
3239
4986
|
readonly = data.readonly;
|
|
4987
|
+
version = typeof data.version === 'string'
|
|
4988
|
+
? data.version
|
|
4989
|
+
: '';
|
|
4990
|
+
size = Number.isFinite(data.size) ? data.size : 0;
|
|
4991
|
+
mtimeMs = Number.isFinite(data.mtimeMs)
|
|
4992
|
+
? data.mtimeMs
|
|
4993
|
+
: 0;
|
|
3240
4994
|
|
|
3241
4995
|
if (this.monacoInstance) {
|
|
3242
4996
|
const uri = this.monacoInstance.Uri.file(filePath);
|
|
@@ -3249,6 +5003,17 @@ class EditorManager {
|
|
|
3249
5003
|
}
|
|
3250
5004
|
}
|
|
3251
5005
|
} catch (err) {
|
|
5006
|
+
if (err?.message === 'Unsupported file type') {
|
|
5007
|
+
await showConfirmModal({
|
|
5008
|
+
title: 'Unsupported File Type',
|
|
5009
|
+
message: 'This file type is not supported yet.',
|
|
5010
|
+
note: 'Only text files, supported images, and PDFs can be opened right now.',
|
|
5011
|
+
confirmLabel: 'OK',
|
|
5012
|
+
hideCancel: true,
|
|
5013
|
+
returnFocus: document.activeElement
|
|
5014
|
+
});
|
|
5015
|
+
return;
|
|
5016
|
+
}
|
|
3252
5017
|
alert(`Failed to open file: ${err.message}`, { type: 'error', title: 'Error' });
|
|
3253
5018
|
this.closeFile(filePath);
|
|
3254
5019
|
return;
|
|
@@ -3256,11 +5021,16 @@ class EditorManager {
|
|
|
3256
5021
|
}
|
|
3257
5022
|
|
|
3258
5023
|
this.setModel(filePath, {
|
|
3259
|
-
type: isImage ? 'image' : 'text',
|
|
5024
|
+
type: isImage ? 'image' : isPdf ? 'pdf' : 'text',
|
|
3260
5025
|
model: model,
|
|
3261
5026
|
content: content,
|
|
3262
|
-
readonly: readonly
|
|
3263
|
-
|
|
5027
|
+
readonly: readonly,
|
|
5028
|
+
version,
|
|
5029
|
+
contentVersion: version,
|
|
5030
|
+
size,
|
|
5031
|
+
mtimeMs,
|
|
5032
|
+
lastDismissedRemoteVersion: ''
|
|
5033
|
+
}, targetSession);
|
|
3264
5034
|
}
|
|
3265
5035
|
|
|
3266
5036
|
let touchedWorkspace = false;
|
|
@@ -3272,7 +5042,11 @@ class EditorManager {
|
|
|
3272
5042
|
|
|
3273
5043
|
this.updateEditorPaneVisibility();
|
|
3274
5044
|
|
|
3275
|
-
|
|
5045
|
+
if (options.activatePreview && isSupportedMarkdownPath(filePath)) {
|
|
5046
|
+
this.activateMarkdownPreviewTab(filePath, false);
|
|
5047
|
+
} else {
|
|
5048
|
+
this.activateFileTab(filePath, false, options);
|
|
5049
|
+
}
|
|
3276
5050
|
if (touchedWorkspace) {
|
|
3277
5051
|
targetSession.saveState({ touchWorkspace: true });
|
|
3278
5052
|
}
|
|
@@ -3281,6 +5055,9 @@ class EditorManager {
|
|
|
3281
5055
|
closeFile(filePath) {
|
|
3282
5056
|
if (!this.currentSession) return;
|
|
3283
5057
|
const state = this.currentSession.editorState;
|
|
5058
|
+
if (this.getMarkdownSplitPath(this.currentSession) === filePath) {
|
|
5059
|
+
this.currentSession.workspaceState.markdownSplitPath = '';
|
|
5060
|
+
}
|
|
3284
5061
|
|
|
3285
5062
|
const index = state.openFiles.indexOf(filePath);
|
|
3286
5063
|
let touchedWorkspace = false;
|
|
@@ -3319,8 +5096,10 @@ class EditorManager {
|
|
|
3319
5096
|
|
|
3320
5097
|
renderEditorTabs() {
|
|
3321
5098
|
if (!this.currentSession) return;
|
|
5099
|
+
this.syncMarkdownSplitSupport(this.currentSession);
|
|
3322
5100
|
const state = this.currentSession.editorState;
|
|
3323
5101
|
const activeWorkspaceTabKey = this.getActiveWorkspaceTabKey();
|
|
5102
|
+
const splitPath = this.getMarkdownSplitPath(this.currentSession);
|
|
3324
5103
|
|
|
3325
5104
|
this.tabsContainer.innerHTML = '';
|
|
3326
5105
|
if (this.hasCompactWorkspaceTabs(this.currentSession)) {
|
|
@@ -3349,11 +5128,28 @@ class EditorManager {
|
|
|
3349
5128
|
}
|
|
3350
5129
|
|
|
3351
5130
|
for (const path of state.openFiles) {
|
|
5131
|
+
const splitEnabled = this.isMarkdownSplitViewEnabled(
|
|
5132
|
+
this.currentSession,
|
|
5133
|
+
path
|
|
5134
|
+
);
|
|
3352
5135
|
const tab = document.createElement('div');
|
|
3353
5136
|
tab.className = 'editor-tab';
|
|
3354
|
-
if (
|
|
5137
|
+
if (
|
|
5138
|
+
makeFileWorkspaceTabKey(path) === activeWorkspaceTabKey
|
|
5139
|
+
|| (
|
|
5140
|
+
splitEnabled
|
|
5141
|
+
&& makeMarkdownPreviewWorkspaceTabKey(path)
|
|
5142
|
+
=== activeWorkspaceTabKey
|
|
5143
|
+
)
|
|
5144
|
+
) {
|
|
3355
5145
|
tab.classList.add('active');
|
|
3356
5146
|
}
|
|
5147
|
+
if (isSupportedMarkdownPath(path)) {
|
|
5148
|
+
tab.classList.add('bound-tab', 'bound-tab-primary');
|
|
5149
|
+
}
|
|
5150
|
+
if (splitEnabled) {
|
|
5151
|
+
tab.classList.add('is-split');
|
|
5152
|
+
}
|
|
3357
5153
|
|
|
3358
5154
|
const fileModel = this.getModel(path);
|
|
3359
5155
|
if (fileModel && fileModel.readonly) {
|
|
@@ -3375,16 +5171,96 @@ class EditorManager {
|
|
|
3375
5171
|
e.stopPropagation();
|
|
3376
5172
|
this.closeFile(path);
|
|
3377
5173
|
};
|
|
5174
|
+
let unsplitBtn = null;
|
|
5175
|
+
|
|
5176
|
+
if (splitEnabled) {
|
|
5177
|
+
unsplitBtn = document.createElement('span');
|
|
5178
|
+
unsplitBtn.className = 'tab-action-btn markdown-unsplit-btn';
|
|
5179
|
+
unsplitBtn.innerHTML = MARKDOWN_SPLIT_DISABLE_ICON_SVG;
|
|
5180
|
+
unsplitBtn.title = 'Restore tabbed markdown view';
|
|
5181
|
+
unsplitBtn.onclick = (e) => {
|
|
5182
|
+
e.stopPropagation();
|
|
5183
|
+
this.setMarkdownSplitView(
|
|
5184
|
+
path,
|
|
5185
|
+
false,
|
|
5186
|
+
this.currentSession
|
|
5187
|
+
);
|
|
5188
|
+
this.activateFileTab(path, false, {
|
|
5189
|
+
focusEditor: false
|
|
5190
|
+
});
|
|
5191
|
+
};
|
|
5192
|
+
tab.appendChild(unsplitBtn);
|
|
5193
|
+
}
|
|
3378
5194
|
|
|
3379
5195
|
tab.onclick = () => this.activateFileTab(path);
|
|
3380
5196
|
bindSingleTapActivation(tab, () => this.activateFileTab(path), {
|
|
3381
|
-
ignoreSelector: '.close-btn'
|
|
5197
|
+
ignoreSelector: '.close-btn, .tab-action-btn'
|
|
3382
5198
|
});
|
|
3383
5199
|
|
|
3384
5200
|
tab.appendChild(icon);
|
|
3385
5201
|
tab.appendChild(span);
|
|
5202
|
+
if (unsplitBtn) {
|
|
5203
|
+
tab.appendChild(unsplitBtn);
|
|
5204
|
+
}
|
|
3386
5205
|
tab.appendChild(closeBtn);
|
|
3387
5206
|
this.tabsContainer.appendChild(tab);
|
|
5207
|
+
|
|
5208
|
+
if (
|
|
5209
|
+
isSupportedMarkdownPath(path)
|
|
5210
|
+
&& !splitEnabled
|
|
5211
|
+
) {
|
|
5212
|
+
const previewTab = document.createElement('div');
|
|
5213
|
+
previewTab.className = 'editor-tab markdown-preview-tab bound-tab bound-tab-secondary';
|
|
5214
|
+
if (
|
|
5215
|
+
makeMarkdownPreviewWorkspaceTabKey(path)
|
|
5216
|
+
=== activeWorkspaceTabKey
|
|
5217
|
+
) {
|
|
5218
|
+
previewTab.classList.add('active');
|
|
5219
|
+
}
|
|
5220
|
+
|
|
5221
|
+
const previewIcon = document.createElement('span');
|
|
5222
|
+
previewIcon.className = 'file-editor-tab-icon';
|
|
5223
|
+
previewIcon.innerHTML = MARKDOWN_PREVIEW_ICON_SVG;
|
|
5224
|
+
|
|
5225
|
+
const previewLabel = document.createElement('span');
|
|
5226
|
+
previewLabel.textContent = 'Preview';
|
|
5227
|
+
let splitBtn = null;
|
|
5228
|
+
|
|
5229
|
+
if (
|
|
5230
|
+
path !== splitPath
|
|
5231
|
+
&& canUseMarkdownSplitTabsMode()
|
|
5232
|
+
) {
|
|
5233
|
+
splitBtn = document.createElement('span');
|
|
5234
|
+
splitBtn.className = 'tab-action-btn markdown-split-btn';
|
|
5235
|
+
splitBtn.innerHTML = MARKDOWN_SPLIT_ENABLE_ICON_SVG;
|
|
5236
|
+
splitBtn.title = 'Show markdown editor and preview side by side';
|
|
5237
|
+
splitBtn.onclick = (event) => {
|
|
5238
|
+
event.stopPropagation();
|
|
5239
|
+
this.setMarkdownSplitView(
|
|
5240
|
+
path,
|
|
5241
|
+
true,
|
|
5242
|
+
this.currentSession
|
|
5243
|
+
);
|
|
5244
|
+
};
|
|
5245
|
+
previewTab.appendChild(splitBtn);
|
|
5246
|
+
}
|
|
5247
|
+
|
|
5248
|
+
previewTab.onclick = () => this.activateMarkdownPreviewTab(path);
|
|
5249
|
+
bindSingleTapActivation(
|
|
5250
|
+
previewTab,
|
|
5251
|
+
() => this.activateMarkdownPreviewTab(path),
|
|
5252
|
+
{
|
|
5253
|
+
ignoreSelector: '.tab-action-btn'
|
|
5254
|
+
}
|
|
5255
|
+
);
|
|
5256
|
+
|
|
5257
|
+
previewTab.appendChild(previewIcon);
|
|
5258
|
+
previewTab.appendChild(previewLabel);
|
|
5259
|
+
if (splitBtn) {
|
|
5260
|
+
previewTab.appendChild(splitBtn);
|
|
5261
|
+
}
|
|
5262
|
+
this.tabsContainer.appendChild(previewTab);
|
|
5263
|
+
}
|
|
3388
5264
|
}
|
|
3389
5265
|
|
|
3390
5266
|
for (const agentTab of getAgentTabsForSession(this.currentSession)) {
|
|
@@ -3403,7 +5279,10 @@ class EditorManager {
|
|
|
3403
5279
|
);
|
|
3404
5280
|
|
|
3405
5281
|
const label = document.createElement('span');
|
|
3406
|
-
|
|
5282
|
+
tab.title = String(getAgentDisplayLabel(agentTab) || '').trim();
|
|
5283
|
+
label.textContent = formatWorkspaceTabTitle(
|
|
5284
|
+
getAgentDisplayLabel(agentTab)
|
|
5285
|
+
);
|
|
3407
5286
|
|
|
3408
5287
|
const closeBtn = document.createElement('span');
|
|
3409
5288
|
closeBtn.className = 'close-btn';
|
|
@@ -3436,6 +5315,13 @@ class EditorManager {
|
|
|
3436
5315
|
this.activateAgentTab(workspaceTabKey, isRestore);
|
|
3437
5316
|
return;
|
|
3438
5317
|
}
|
|
5318
|
+
if (isMarkdownPreviewWorkspaceTabKey(workspaceTabKey)) {
|
|
5319
|
+
this.activateMarkdownPreviewTab(
|
|
5320
|
+
workspaceKeyToFilePath(workspaceTabKey),
|
|
5321
|
+
isRestore
|
|
5322
|
+
);
|
|
5323
|
+
return;
|
|
5324
|
+
}
|
|
3439
5325
|
this.activateFileTab(workspaceKeyToFilePath(workspaceTabKey), isRestore);
|
|
3440
5326
|
}
|
|
3441
5327
|
|
|
@@ -3461,22 +5347,78 @@ class EditorManager {
|
|
|
3461
5347
|
TERMINAL_WORKSPACE_TAB_KEY;
|
|
3462
5348
|
this.currentSession.needsAttention = false;
|
|
3463
5349
|
if (!isRestore) {
|
|
3464
|
-
this.currentSession.saveState();
|
|
5350
|
+
this.currentSession.saveState({ touchWorkspace: true });
|
|
3465
5351
|
}
|
|
3466
5352
|
this.renderEditorTabs();
|
|
3467
5353
|
this.currentSession.updateTabUI();
|
|
3468
5354
|
this.monacoContainer.style.display = 'none';
|
|
3469
5355
|
this.imagePreviewContainer.style.display = 'none';
|
|
5356
|
+
this.hidePdfPreview();
|
|
5357
|
+
this.hideMarkdownPreview();
|
|
3470
5358
|
this.agentContainer.style.display = 'none';
|
|
3471
5359
|
this.emptyState.style.display = 'none';
|
|
3472
|
-
this.syncTerminalWorkspacePlacement(TERMINAL_WORKSPACE_TAB_KEY);
|
|
5360
|
+
this.syncTerminalWorkspacePlacement(TERMINAL_WORKSPACE_TAB_KEY);
|
|
5361
|
+
|
|
5362
|
+
requestAnimationFrame(() => {
|
|
5363
|
+
if (this.currentSession.fitMainTerminalIfVisible()) {
|
|
5364
|
+
this.currentSession.mainTerm.focus();
|
|
5365
|
+
}
|
|
5366
|
+
this.currentSession.reportResize();
|
|
5367
|
+
});
|
|
5368
|
+
}
|
|
5369
|
+
|
|
5370
|
+
activateMarkdownPreviewTab(filePath, isRestore = false) {
|
|
5371
|
+
if (!this.currentSession || !filePath) return;
|
|
5372
|
+
|
|
5373
|
+
const state = this.currentSession.editorState;
|
|
5374
|
+
if (!isRestore && state.activeFilePath && state.activeFilePath !== filePath) {
|
|
5375
|
+
const currentGlobal = this.getModel(state.activeFilePath);
|
|
5376
|
+
if (currentGlobal && currentGlobal.type === 'text' && this.editor) {
|
|
5377
|
+
state.viewStates.set(
|
|
5378
|
+
state.activeFilePath,
|
|
5379
|
+
this.editor.saveViewState()
|
|
5380
|
+
);
|
|
5381
|
+
}
|
|
5382
|
+
}
|
|
5383
|
+
|
|
5384
|
+
state.activeFilePath = filePath;
|
|
5385
|
+
this.currentSession.workspaceState.activeTabKey =
|
|
5386
|
+
makeMarkdownPreviewWorkspaceTabKey(filePath);
|
|
5387
|
+
this.currentSession.workspaceState.lastNonTerminalTabKey =
|
|
5388
|
+
makeMarkdownPreviewWorkspaceTabKey(filePath);
|
|
5389
|
+
if (!isRestore) {
|
|
5390
|
+
this.currentSession.saveState({ touchWorkspace: true });
|
|
5391
|
+
}
|
|
5392
|
+
const file = this.getModel(filePath);
|
|
5393
|
+
|
|
5394
|
+
this.renderEditorTabs();
|
|
5395
|
+
this.emptyState.style.display = 'none';
|
|
5396
|
+
this.syncTerminalWorkspacePlacement();
|
|
3473
5397
|
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
5398
|
+
if (!file) {
|
|
5399
|
+
void this.openFile(filePath, true, {
|
|
5400
|
+
activatePreview: true,
|
|
5401
|
+
focusEditor: false
|
|
5402
|
+
});
|
|
5403
|
+
return;
|
|
5404
|
+
}
|
|
5405
|
+
|
|
5406
|
+
this.agentContainer.style.display = 'none';
|
|
5407
|
+
this.imagePreviewContainer.style.display = 'none';
|
|
5408
|
+
this.hidePdfPreview();
|
|
5409
|
+
if (this.isMarkdownSplitViewEnabled(this.currentSession, filePath)) {
|
|
5410
|
+
this.showMarkdownSplitView(filePath, {
|
|
5411
|
+
session: this.currentSession,
|
|
5412
|
+
focusEditor: false
|
|
5413
|
+
});
|
|
5414
|
+
} else {
|
|
5415
|
+
this.contentContainer.classList.remove('markdown-split-active');
|
|
5416
|
+
this.monacoContainer.style.display = 'none';
|
|
5417
|
+
this.markdownPreviewContainer.style.display = 'flex';
|
|
5418
|
+
void this.renderMarkdownPreview(filePath, {
|
|
5419
|
+
show: true
|
|
5420
|
+
});
|
|
5421
|
+
}
|
|
3480
5422
|
}
|
|
3481
5423
|
|
|
3482
5424
|
activateFileTab(filePath, isRestore = false, options = {}) {
|
|
@@ -3496,7 +5438,9 @@ class EditorManager {
|
|
|
3496
5438
|
this.currentSession.workspaceState.activeTabKey = makeFileWorkspaceTabKey(filePath);
|
|
3497
5439
|
this.currentSession.workspaceState.lastNonTerminalTabKey =
|
|
3498
5440
|
makeFileWorkspaceTabKey(filePath);
|
|
3499
|
-
|
|
5441
|
+
if (!isRestore) {
|
|
5442
|
+
this.currentSession.saveState({ touchWorkspace: true });
|
|
5443
|
+
}
|
|
3500
5444
|
const file = this.getModel(filePath);
|
|
3501
5445
|
|
|
3502
5446
|
this.renderEditorTabs();
|
|
@@ -3511,7 +5455,9 @@ class EditorManager {
|
|
|
3511
5455
|
if (file.type === 'image') {
|
|
3512
5456
|
this.agentContainer.style.display = 'none';
|
|
3513
5457
|
this.monacoContainer.style.display = 'none';
|
|
5458
|
+
this.hideMarkdownPreview();
|
|
3514
5459
|
this.imagePreviewContainer.style.display = 'flex';
|
|
5460
|
+
this.hidePdfPreview();
|
|
3515
5461
|
|
|
3516
5462
|
this.imagePreview.onerror = () => {
|
|
3517
5463
|
alert(`Failed to load image: ${filePath.split('/').pop()}`, { type: 'error', title: 'Error' });
|
|
@@ -3522,9 +5468,55 @@ class EditorManager {
|
|
|
3522
5468
|
this.imagePreview.src = this.currentSession.server.resolveUrl(
|
|
3523
5469
|
`/api/fs/raw?path=${encodeURIComponent(filePath)}&token=${this.currentSession.server.token}`
|
|
3524
5470
|
);
|
|
5471
|
+
} else if (file.type === 'pdf') {
|
|
5472
|
+
this.agentContainer.style.display = 'none';
|
|
5473
|
+
this.monacoContainer.style.display = 'none';
|
|
5474
|
+
this.imagePreviewContainer.style.display = 'none';
|
|
5475
|
+
this.hideMarkdownPreview();
|
|
5476
|
+
this.pdfPreviewContainer.style.display = 'flex';
|
|
5477
|
+
void this.renderPdfPreview(filePath);
|
|
5478
|
+
} else if (isSupportedMarkdownPath(filePath)) {
|
|
5479
|
+
if (this.isMarkdownSplitViewEnabled(this.currentSession, filePath)) {
|
|
5480
|
+
this.showMarkdownSplitView(filePath, {
|
|
5481
|
+
session: this.currentSession,
|
|
5482
|
+
focusEditor
|
|
5483
|
+
});
|
|
5484
|
+
this.scheduleMarkdownPreviewRender(
|
|
5485
|
+
filePath,
|
|
5486
|
+
this.currentSession
|
|
5487
|
+
);
|
|
5488
|
+
return;
|
|
5489
|
+
}
|
|
5490
|
+
this.agentContainer.style.display = 'none';
|
|
5491
|
+
this.imagePreviewContainer.style.display = 'none';
|
|
5492
|
+
this.hidePdfPreview();
|
|
5493
|
+
this.hideMarkdownPreview();
|
|
5494
|
+
this.monacoContainer.style.display = 'block';
|
|
5495
|
+
if (!file.model && file.content !== null && this.monacoInstance) {
|
|
5496
|
+
file.model = this.monacoInstance.editor.createModel(
|
|
5497
|
+
file.content,
|
|
5498
|
+
undefined,
|
|
5499
|
+
this.monacoInstance.Uri.file(filePath)
|
|
5500
|
+
);
|
|
5501
|
+
}
|
|
5502
|
+
if (this.editor && file.model) {
|
|
5503
|
+
this.editor.setModel(file.model);
|
|
5504
|
+
this.editor.updateOptions({ readOnly: !!file.readonly });
|
|
5505
|
+
const savedViewState = state.viewStates.get(filePath);
|
|
5506
|
+
if (savedViewState) {
|
|
5507
|
+
this.editor.restoreViewState(savedViewState);
|
|
5508
|
+
}
|
|
5509
|
+
if (focusEditor) {
|
|
5510
|
+
this.editor.focus();
|
|
5511
|
+
}
|
|
5512
|
+
requestAnimationFrame(() => this.editor.layout());
|
|
5513
|
+
}
|
|
5514
|
+
this.scheduleMarkdownPreviewRender(filePath, this.currentSession);
|
|
3525
5515
|
} else {
|
|
3526
5516
|
this.agentContainer.style.display = 'none';
|
|
3527
5517
|
this.imagePreviewContainer.style.display = 'none';
|
|
5518
|
+
this.hidePdfPreview();
|
|
5519
|
+
this.hideMarkdownPreview();
|
|
3528
5520
|
this.monacoContainer.style.display = 'block';
|
|
3529
5521
|
|
|
3530
5522
|
if (!file.model && file.content !== null && this.monacoInstance) {
|
|
@@ -3545,6 +5537,7 @@ class EditorManager {
|
|
|
3545
5537
|
// Force layout to ensure content is visible
|
|
3546
5538
|
requestAnimationFrame(() => this.editor.layout());
|
|
3547
5539
|
}
|
|
5540
|
+
void this.checkActiveFileVersion();
|
|
3548
5541
|
}
|
|
3549
5542
|
}
|
|
3550
5543
|
|
|
@@ -3581,15 +5574,21 @@ class EditorManager {
|
|
|
3581
5574
|
this.currentSession.workspaceState.lastNonTerminalTabKey = agentTabKey;
|
|
3582
5575
|
noteRecentAgentTab(this.currentSession, agentTabKey);
|
|
3583
5576
|
agentTab.needsAttention = false;
|
|
3584
|
-
|
|
5577
|
+
if (!isRestore) {
|
|
5578
|
+
this.currentSession.saveState({ touchWorkspace: true });
|
|
5579
|
+
}
|
|
3585
5580
|
this.renderEditorTabs();
|
|
3586
5581
|
this.currentSession.updateTabUI();
|
|
3587
5582
|
this.syncTerminalWorkspacePlacement(agentTabKey);
|
|
3588
5583
|
this.monacoContainer.style.display = 'none';
|
|
3589
5584
|
this.imagePreviewContainer.style.display = 'none';
|
|
5585
|
+
this.hidePdfPreview();
|
|
5586
|
+
this.hideMarkdownPreview();
|
|
3590
5587
|
this.emptyState.style.display = 'none';
|
|
3591
5588
|
this.agentContainer.style.display = 'flex';
|
|
3592
|
-
this.renderAgentPanel(agentTab
|
|
5589
|
+
this.renderAgentPanel(agentTab, {
|
|
5590
|
+
reason: isRestore ? 'activate-restore' : 'activate'
|
|
5591
|
+
});
|
|
3593
5592
|
}
|
|
3594
5593
|
|
|
3595
5594
|
async closeAgentTab(agentTabKey) {
|
|
@@ -3599,7 +5598,7 @@ class EditorManager {
|
|
|
3599
5598
|
removeAgentTab(agentTabKey);
|
|
3600
5599
|
}
|
|
3601
5600
|
|
|
3602
|
-
renderAgentPanel(agentTab) {
|
|
5601
|
+
renderAgentPanel(agentTab, options = {}) {
|
|
3603
5602
|
this.disposeAgentEmbeddedEditors();
|
|
3604
5603
|
const previousLayout = this.captureAgentTranscriptLayout();
|
|
3605
5604
|
const previousScrollTop = previousLayout?.scrollTop || 0;
|
|
@@ -3666,12 +5665,29 @@ class EditorManager {
|
|
|
3666
5665
|
|
|
3667
5666
|
this.agentTranscript.innerHTML = '';
|
|
3668
5667
|
const timeline = getAgentTimelineItems(agentTab);
|
|
5668
|
+
const shouldPinToBottom = wasNearBottom || (
|
|
5669
|
+
agentTab.scrollToBottomOnNextRender
|
|
5670
|
+
&& isAgentTranscriptWindowNearLatest(
|
|
5671
|
+
agentTab,
|
|
5672
|
+
timeline.length
|
|
5673
|
+
)
|
|
5674
|
+
);
|
|
5675
|
+
const transcriptWindow = getAgentTranscriptWindow(
|
|
5676
|
+
agentTab,
|
|
5677
|
+
timeline.length,
|
|
5678
|
+
{ pinToBottom: shouldPinToBottom }
|
|
5679
|
+
);
|
|
5680
|
+
const visibleTimeline = timeline.slice(
|
|
5681
|
+
transcriptWindow.start,
|
|
5682
|
+
transcriptWindow.end
|
|
5683
|
+
);
|
|
3669
5684
|
if (timeline.length === 0) {
|
|
3670
5685
|
this.agentTranscript.appendChild(
|
|
3671
5686
|
this.buildAgentEmptyState(agentTab)
|
|
3672
5687
|
);
|
|
3673
5688
|
} else {
|
|
3674
|
-
for (const [index, entry] of
|
|
5689
|
+
for (const [index, entry] of visibleTimeline.entries()) {
|
|
5690
|
+
const timelineIndex = transcriptWindow.start + index;
|
|
3675
5691
|
let node = null;
|
|
3676
5692
|
if (entry.type === 'message') {
|
|
3677
5693
|
node = this.buildAgentMessageNode(agentTab, entry.value);
|
|
@@ -3689,8 +5705,12 @@ class EditorManager {
|
|
|
3689
5705
|
);
|
|
3690
5706
|
}
|
|
3691
5707
|
if (node) {
|
|
5708
|
+
node.dataset.timelineKey = getAgentTimelineItemKey(
|
|
5709
|
+
entry,
|
|
5710
|
+
timelineIndex
|
|
5711
|
+
);
|
|
3692
5712
|
if (
|
|
3693
|
-
|
|
5713
|
+
timelineIndex > 0
|
|
3694
5714
|
&& entry.type === 'message'
|
|
3695
5715
|
&& String(entry.value?.role || '').toLowerCase()
|
|
3696
5716
|
=== 'user'
|
|
@@ -3701,13 +5721,19 @@ class EditorManager {
|
|
|
3701
5721
|
}
|
|
3702
5722
|
}
|
|
3703
5723
|
}
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
5724
|
+
if (options.preserveTranscriptAnchor) {
|
|
5725
|
+
const restored = this.restoreAgentTranscriptAnchor(
|
|
5726
|
+
options.preserveTranscriptAnchor
|
|
5727
|
+
);
|
|
5728
|
+
if (!restored) {
|
|
5729
|
+
this.agentTranscript.scrollTop = previousScrollTop;
|
|
5730
|
+
}
|
|
5731
|
+
} else if (shouldPinToBottom) {
|
|
3707
5732
|
this.agentTranscript.scrollTop = this.agentTranscript.scrollHeight;
|
|
3708
5733
|
agentTab.scrollToBottomOnNextRender = false;
|
|
3709
5734
|
} else {
|
|
3710
5735
|
this.agentTranscript.scrollTop = previousScrollTop;
|
|
5736
|
+
agentTab.scrollToBottomOnNextRender = false;
|
|
3711
5737
|
}
|
|
3712
5738
|
this.updateAgentScrollBottomButton();
|
|
3713
5739
|
this.rememberAgentTranscriptLayout();
|
|
@@ -3725,6 +5751,95 @@ class EditorManager {
|
|
|
3725
5751
|
this.scheduleAgentTranscriptViewportUpdate(shouldPinToBottom);
|
|
3726
5752
|
}
|
|
3727
5753
|
|
|
5754
|
+
async loadOlderAgentTimeline(agentTab) {
|
|
5755
|
+
if (!agentTab || agentTab.historyWindowLoading) {
|
|
5756
|
+
return;
|
|
5757
|
+
}
|
|
5758
|
+
const timeline = getAgentTimelineItems(agentTab);
|
|
5759
|
+
const transcriptWindow = getAgentTranscriptWindow(
|
|
5760
|
+
agentTab,
|
|
5761
|
+
timeline.length
|
|
5762
|
+
);
|
|
5763
|
+
if (transcriptWindow.start <= 0) {
|
|
5764
|
+
return;
|
|
5765
|
+
}
|
|
5766
|
+
agentTab.scrollToBottomOnNextRender = false;
|
|
5767
|
+
const currentWindowSize = transcriptWindow.end
|
|
5768
|
+
- transcriptWindow.start;
|
|
5769
|
+
const step = Math.min(
|
|
5770
|
+
AGENT_TRANSCRIPT_WINDOW_STEP,
|
|
5771
|
+
transcriptWindow.start
|
|
5772
|
+
);
|
|
5773
|
+
const nextStart = Math.max(0, transcriptWindow.start - step);
|
|
5774
|
+
const anchor = this.captureAgentTranscriptAnchor(
|
|
5775
|
+
getAgentTimelineItemKey(
|
|
5776
|
+
timeline[transcriptWindow.start],
|
|
5777
|
+
transcriptWindow.start
|
|
5778
|
+
)
|
|
5779
|
+
);
|
|
5780
|
+
agentTab.historyWindowLoading = true;
|
|
5781
|
+
agentTab.historyWindowStart = nextStart;
|
|
5782
|
+
agentTab.historyWindowEnd = Math.min(
|
|
5783
|
+
timeline.length,
|
|
5784
|
+
nextStart + currentWindowSize
|
|
5785
|
+
);
|
|
5786
|
+
try {
|
|
5787
|
+
this.renderAgentPanel(agentTab, {
|
|
5788
|
+
reason: 'history-older',
|
|
5789
|
+
preserveTranscriptAnchor: anchor
|
|
5790
|
+
});
|
|
5791
|
+
} finally {
|
|
5792
|
+
agentTab.historyWindowLoading = false;
|
|
5793
|
+
}
|
|
5794
|
+
}
|
|
5795
|
+
|
|
5796
|
+
async loadNewerAgentTimeline(agentTab) {
|
|
5797
|
+
if (!agentTab || agentTab.historyWindowLoading) {
|
|
5798
|
+
return;
|
|
5799
|
+
}
|
|
5800
|
+
const timeline = getAgentTimelineItems(agentTab);
|
|
5801
|
+
const transcriptWindow = getAgentTranscriptWindow(
|
|
5802
|
+
agentTab,
|
|
5803
|
+
timeline.length
|
|
5804
|
+
);
|
|
5805
|
+
if (transcriptWindow.end >= timeline.length) {
|
|
5806
|
+
return;
|
|
5807
|
+
}
|
|
5808
|
+
agentTab.scrollToBottomOnNextRender = false;
|
|
5809
|
+
const currentWindowSize = transcriptWindow.end
|
|
5810
|
+
- transcriptWindow.start;
|
|
5811
|
+
const step = Math.min(
|
|
5812
|
+
AGENT_TRANSCRIPT_WINDOW_STEP,
|
|
5813
|
+
timeline.length - transcriptWindow.end
|
|
5814
|
+
);
|
|
5815
|
+
const nextEnd = Math.min(
|
|
5816
|
+
timeline.length,
|
|
5817
|
+
transcriptWindow.end + step
|
|
5818
|
+
);
|
|
5819
|
+
const nextStart = Math.max(0, nextEnd - currentWindowSize);
|
|
5820
|
+
const anchorIndex = Math.max(
|
|
5821
|
+
transcriptWindow.start,
|
|
5822
|
+
transcriptWindow.end - 1
|
|
5823
|
+
);
|
|
5824
|
+
const anchor = this.captureAgentTranscriptAnchor(
|
|
5825
|
+
getAgentTimelineItemKey(
|
|
5826
|
+
timeline[anchorIndex],
|
|
5827
|
+
anchorIndex
|
|
5828
|
+
)
|
|
5829
|
+
);
|
|
5830
|
+
agentTab.historyWindowLoading = true;
|
|
5831
|
+
agentTab.historyWindowStart = nextStart;
|
|
5832
|
+
agentTab.historyWindowEnd = nextEnd;
|
|
5833
|
+
try {
|
|
5834
|
+
this.renderAgentPanel(agentTab, {
|
|
5835
|
+
reason: 'history-newer',
|
|
5836
|
+
preserveTranscriptAnchor: anchor
|
|
5837
|
+
});
|
|
5838
|
+
} finally {
|
|
5839
|
+
agentTab.historyWindowLoading = false;
|
|
5840
|
+
}
|
|
5841
|
+
}
|
|
5842
|
+
|
|
3728
5843
|
refreshAgentTimelineTimestamps() {
|
|
3729
5844
|
if (!this.agentContainer || this.agentContainer.style.display === 'none') {
|
|
3730
5845
|
return;
|
|
@@ -4028,6 +6143,7 @@ class EditorManager {
|
|
|
4028
6143
|
) {
|
|
4029
6144
|
body.classList.add('markdown');
|
|
4030
6145
|
body.innerHTML = renderAgentMessageMarkdown(message.text || '');
|
|
6146
|
+
void this.enhanceAgentMarkdownBody(agentTab, message, body);
|
|
4031
6147
|
} else {
|
|
4032
6148
|
body.classList.add('plain');
|
|
4033
6149
|
body.textContent = message.text || '';
|
|
@@ -4113,9 +6229,27 @@ class EditorManager {
|
|
|
4113
6229
|
toolStatusClass
|
|
4114
6230
|
);
|
|
4115
6231
|
details.appendChild(summary);
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
)
|
|
6232
|
+
const bodyHost = document.createElement('div');
|
|
6233
|
+
bodyHost.className = 'agent-tool-call-section-content';
|
|
6234
|
+
const mountBody = () => {
|
|
6235
|
+
if (bodyHost.dataset.mounted === 'true') {
|
|
6236
|
+
return;
|
|
6237
|
+
}
|
|
6238
|
+
bodyHost.dataset.mounted = 'true';
|
|
6239
|
+
bodyHost.appendChild(
|
|
6240
|
+
this.buildAgentSectionBody(details, section)
|
|
6241
|
+
);
|
|
6242
|
+
};
|
|
6243
|
+
details.appendChild(bodyHost);
|
|
6244
|
+
if (details.open) {
|
|
6245
|
+
queueMicrotask(mountBody);
|
|
6246
|
+
} else {
|
|
6247
|
+
details.addEventListener('toggle', () => {
|
|
6248
|
+
if (details.open) {
|
|
6249
|
+
mountBody();
|
|
6250
|
+
}
|
|
6251
|
+
}, { once: true });
|
|
6252
|
+
}
|
|
4119
6253
|
sectionContainer.appendChild(details);
|
|
4120
6254
|
}
|
|
4121
6255
|
node.appendChild(sectionContainer);
|
|
@@ -4207,9 +6341,27 @@ class EditorManager {
|
|
|
4207
6341
|
permission.status || 'pending'
|
|
4208
6342
|
);
|
|
4209
6343
|
details.appendChild(summary);
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
)
|
|
6344
|
+
const bodyHost = document.createElement('div');
|
|
6345
|
+
bodyHost.className = 'agent-tool-call-section-content';
|
|
6346
|
+
const mountBody = () => {
|
|
6347
|
+
if (bodyHost.dataset.mounted === 'true') {
|
|
6348
|
+
return;
|
|
6349
|
+
}
|
|
6350
|
+
bodyHost.dataset.mounted = 'true';
|
|
6351
|
+
bodyHost.appendChild(
|
|
6352
|
+
this.buildAgentSectionBody(details, section)
|
|
6353
|
+
);
|
|
6354
|
+
};
|
|
6355
|
+
details.appendChild(bodyHost);
|
|
6356
|
+
if (details.open) {
|
|
6357
|
+
queueMicrotask(mountBody);
|
|
6358
|
+
} else {
|
|
6359
|
+
details.addEventListener('toggle', () => {
|
|
6360
|
+
if (details.open) {
|
|
6361
|
+
mountBody();
|
|
6362
|
+
}
|
|
6363
|
+
}, { once: true });
|
|
6364
|
+
}
|
|
4213
6365
|
sectionContainer.appendChild(details);
|
|
4214
6366
|
}
|
|
4215
6367
|
card.appendChild(sectionContainer);
|
|
@@ -4904,6 +7056,51 @@ class EditorManager {
|
|
|
4904
7056
|
};
|
|
4905
7057
|
}
|
|
4906
7058
|
|
|
7059
|
+
findAgentTranscriptNodeByKey(timelineKey = '') {
|
|
7060
|
+
if (!this.agentTranscript || !timelineKey) {
|
|
7061
|
+
return null;
|
|
7062
|
+
}
|
|
7063
|
+
for (const node of this.agentTranscript.children) {
|
|
7064
|
+
if (node?.dataset?.timelineKey === timelineKey) {
|
|
7065
|
+
return node;
|
|
7066
|
+
}
|
|
7067
|
+
}
|
|
7068
|
+
return null;
|
|
7069
|
+
}
|
|
7070
|
+
|
|
7071
|
+
captureAgentTranscriptAnchor(timelineKey = '') {
|
|
7072
|
+
const node = this.findAgentTranscriptNodeByKey(timelineKey);
|
|
7073
|
+
if (!node || !this.agentTranscript) {
|
|
7074
|
+
return null;
|
|
7075
|
+
}
|
|
7076
|
+
return {
|
|
7077
|
+
timelineKey,
|
|
7078
|
+
scrollTop: this.agentTranscript.scrollTop,
|
|
7079
|
+
offsetTop: node.offsetTop
|
|
7080
|
+
};
|
|
7081
|
+
}
|
|
7082
|
+
|
|
7083
|
+
restoreAgentTranscriptAnchor(anchor = null) {
|
|
7084
|
+
if (!anchor || !this.agentTranscript) {
|
|
7085
|
+
return false;
|
|
7086
|
+
}
|
|
7087
|
+
const node = this.findAgentTranscriptNodeByKey(
|
|
7088
|
+
anchor.timelineKey || ''
|
|
7089
|
+
);
|
|
7090
|
+
if (!node) {
|
|
7091
|
+
return false;
|
|
7092
|
+
}
|
|
7093
|
+
const previousOffsetTop = Number.isFinite(anchor.offsetTop)
|
|
7094
|
+
? anchor.offsetTop
|
|
7095
|
+
: 0;
|
|
7096
|
+
const previousScrollTop = Number.isFinite(anchor.scrollTop)
|
|
7097
|
+
? anchor.scrollTop
|
|
7098
|
+
: 0;
|
|
7099
|
+
this.agentTranscript.scrollTop = previousScrollTop
|
|
7100
|
+
+ (node.offsetTop - previousOffsetTop);
|
|
7101
|
+
return true;
|
|
7102
|
+
}
|
|
7103
|
+
|
|
4907
7104
|
rememberAgentTranscriptLayout() {
|
|
4908
7105
|
this.agentTranscriptLayout = this.captureAgentTranscriptLayout();
|
|
4909
7106
|
}
|
|
@@ -4924,6 +7121,32 @@ class EditorManager {
|
|
|
4924
7121
|
}
|
|
4925
7122
|
|
|
4926
7123
|
scrollAgentTranscriptToBottom() {
|
|
7124
|
+
const activeTab = getActiveAgentTab();
|
|
7125
|
+
if (activeTab) {
|
|
7126
|
+
const total = getAgentTimelineItems(activeTab).length;
|
|
7127
|
+
const transcriptWindow = getAgentTranscriptWindow(
|
|
7128
|
+
activeTab,
|
|
7129
|
+
total,
|
|
7130
|
+
{ pinToBottom: false }
|
|
7131
|
+
);
|
|
7132
|
+
const latestWindow = getAgentTranscriptWindow(
|
|
7133
|
+
null,
|
|
7134
|
+
total,
|
|
7135
|
+
{ pinToBottom: true }
|
|
7136
|
+
);
|
|
7137
|
+
const alreadyLatest = transcriptWindow.start === latestWindow.start
|
|
7138
|
+
&& transcriptWindow.end === latestWindow.end;
|
|
7139
|
+
if (!alreadyLatest) {
|
|
7140
|
+
activeTab.historyWindowStart = latestWindow.start;
|
|
7141
|
+
activeTab.historyWindowEnd = latestWindow.end;
|
|
7142
|
+
activeTab.scrollToBottomOnNextRender = true;
|
|
7143
|
+
this.renderAgentPanel(activeTab, {
|
|
7144
|
+
reason: 'scroll-latest'
|
|
7145
|
+
});
|
|
7146
|
+
return;
|
|
7147
|
+
}
|
|
7148
|
+
activeTab.scrollToBottomOnNextRender = false;
|
|
7149
|
+
}
|
|
4927
7150
|
if (!this.agentTranscript) return;
|
|
4928
7151
|
this.agentTranscript.scrollTop = this.agentTranscript.scrollHeight;
|
|
4929
7152
|
this.updateAgentScrollBottomButton();
|
|
@@ -5292,20 +7515,57 @@ class EditorManager {
|
|
|
5292
7515
|
const session = agentTab.getLinkedSession();
|
|
5293
7516
|
if (!session) return;
|
|
5294
7517
|
try {
|
|
7518
|
+
let targetAgentTab = null;
|
|
7519
|
+
let targetPromptDraft = '';
|
|
5295
7520
|
if (command.openTabKey) {
|
|
5296
7521
|
const existingTab = state.agentTabs.get(command.openTabKey);
|
|
5297
7522
|
const existingSession = existingTab?.getLinkedSession() || null;
|
|
5298
7523
|
if (existingTab && existingSession) {
|
|
5299
|
-
|
|
7524
|
+
targetPromptDraft = String(
|
|
7525
|
+
existingTab.promptDraft || ''
|
|
7526
|
+
);
|
|
7527
|
+
targetAgentTab = await activateAgentTab(
|
|
7528
|
+
existingSession,
|
|
7529
|
+
existingTab,
|
|
7530
|
+
{
|
|
5300
7531
|
switchSession: true
|
|
5301
|
-
|
|
7532
|
+
}
|
|
7533
|
+
);
|
|
5302
7534
|
} else {
|
|
5303
|
-
await resumeAgentTabFromHistory(
|
|
7535
|
+
targetAgentTab = await resumeAgentTabFromHistory(
|
|
7536
|
+
session,
|
|
7537
|
+
agentTab,
|
|
7538
|
+
command
|
|
7539
|
+
);
|
|
7540
|
+
targetPromptDraft = String(
|
|
7541
|
+
targetAgentTab?.promptDraft || ''
|
|
7542
|
+
);
|
|
5304
7543
|
}
|
|
5305
7544
|
} else {
|
|
5306
|
-
await resumeAgentTabFromHistory(
|
|
7545
|
+
targetAgentTab = await resumeAgentTabFromHistory(
|
|
7546
|
+
session,
|
|
7547
|
+
agentTab,
|
|
7548
|
+
command
|
|
7549
|
+
);
|
|
7550
|
+
targetPromptDraft = String(
|
|
7551
|
+
targetAgentTab?.promptDraft || ''
|
|
7552
|
+
);
|
|
5307
7553
|
}
|
|
5308
|
-
|
|
7554
|
+
const targetPromptIntent = getAgentPromptIntent(
|
|
7555
|
+
targetAgentTab,
|
|
7556
|
+
targetPromptDraft
|
|
7557
|
+
);
|
|
7558
|
+
if (targetPromptIntent.kind === 'resume') {
|
|
7559
|
+
targetPromptDraft = '';
|
|
7560
|
+
if (targetAgentTab) {
|
|
7561
|
+
targetAgentTab.promptDraft = '';
|
|
7562
|
+
}
|
|
7563
|
+
}
|
|
7564
|
+
agentTab.promptDraft = '';
|
|
7565
|
+
this.setAgentPromptValue(
|
|
7566
|
+
targetPromptDraft,
|
|
7567
|
+
targetAgentTab || getActiveAgentTab() || agentTab
|
|
7568
|
+
);
|
|
5309
7569
|
} catch (error) {
|
|
5310
7570
|
alert(error.message, {
|
|
5311
7571
|
type: 'error',
|
|
@@ -5327,6 +7587,8 @@ class EditorManager {
|
|
|
5327
7587
|
showEmptyState() {
|
|
5328
7588
|
this.monacoContainer.style.display = 'none';
|
|
5329
7589
|
this.imagePreviewContainer.style.display = 'none';
|
|
7590
|
+
this.hidePdfPreview();
|
|
7591
|
+
this.hideMarkdownPreview();
|
|
5330
7592
|
this.agentContainer.style.display = 'none';
|
|
5331
7593
|
this.emptyState.style.display = 'flex';
|
|
5332
7594
|
this.syncTerminalWorkspacePlacement('');
|
|
@@ -5994,13 +8256,31 @@ class Session {
|
|
|
5994
8256
|
: Array.from(this.server.expandedPaths)
|
|
5995
8257
|
}
|
|
5996
8258
|
);
|
|
8259
|
+
const sharedActiveWorkspaceTabKey = typeof (
|
|
8260
|
+
this.sharedWorkspaceState.activeWorkspaceTabKey
|
|
8261
|
+
) === 'string'
|
|
8262
|
+
? this.sharedWorkspaceState.activeWorkspaceTabKey
|
|
8263
|
+
: '';
|
|
8264
|
+
const initialActiveWorkspaceTabKey = (
|
|
8265
|
+
isFileWorkspaceTabKey(sharedActiveWorkspaceTabKey)
|
|
8266
|
+
&& !this.sharedWorkspaceState.openFiles.includes(
|
|
8267
|
+
workspaceKeyToFilePath(sharedActiveWorkspaceTabKey)
|
|
8268
|
+
)
|
|
8269
|
+
)
|
|
8270
|
+
? ''
|
|
8271
|
+
: sharedActiveWorkspaceTabKey;
|
|
8272
|
+
const preferredActiveFilePath = isFileWorkspaceTabKey(
|
|
8273
|
+
initialActiveWorkspaceTabKey
|
|
8274
|
+
)
|
|
8275
|
+
? workspaceKeyToFilePath(initialActiveWorkspaceTabKey)
|
|
8276
|
+
: legacyEditorState.activeFilePath;
|
|
5997
8277
|
const initialActiveFilePath = (
|
|
5998
|
-
typeof
|
|
8278
|
+
typeof preferredActiveFilePath === 'string'
|
|
5999
8279
|
&& this.sharedWorkspaceState.openFiles.includes(
|
|
6000
|
-
|
|
8280
|
+
preferredActiveFilePath
|
|
6001
8281
|
)
|
|
6002
8282
|
)
|
|
6003
|
-
?
|
|
8283
|
+
? preferredActiveFilePath
|
|
6004
8284
|
: (this.sharedWorkspaceState.openFiles[0] || null);
|
|
6005
8285
|
|
|
6006
8286
|
this.editorState = {
|
|
@@ -6011,15 +8291,15 @@ class Session {
|
|
|
6011
8291
|
viewStates: new Map() // Path -> ViewState
|
|
6012
8292
|
};
|
|
6013
8293
|
this.workspaceState = {
|
|
6014
|
-
activeTabKey:
|
|
8294
|
+
activeTabKey: initialActiveWorkspaceTabKey
|
|
6015
8295
|
|| (initialActiveFilePath
|
|
6016
8296
|
? makeFileWorkspaceTabKey(initialActiveFilePath)
|
|
6017
8297
|
: ''),
|
|
6018
|
-
lastNonTerminalTabKey:
|
|
8298
|
+
lastNonTerminalTabKey: initialActiveWorkspaceTabKey
|
|
6019
8299
|
&& !isTerminalWorkspaceTabKey(
|
|
6020
|
-
|
|
8300
|
+
initialActiveWorkspaceTabKey
|
|
6021
8301
|
)
|
|
6022
|
-
?
|
|
8302
|
+
? initialActiveWorkspaceTabKey
|
|
6023
8303
|
: (initialActiveFilePath
|
|
6024
8304
|
? makeFileWorkspaceTabKey(initialActiveFilePath)
|
|
6025
8305
|
: ''),
|
|
@@ -6029,7 +8309,8 @@ class Session {
|
|
|
6029
8309
|
? legacyEditorState.recentAgentTabKeys.filter(
|
|
6030
8310
|
(key) => typeof key === 'string' && key.length > 0
|
|
6031
8311
|
)
|
|
6032
|
-
: []
|
|
8312
|
+
: [],
|
|
8313
|
+
markdownSplitPath: this.sharedWorkspaceState.markdownSplitPath || ''
|
|
6033
8314
|
};
|
|
6034
8315
|
|
|
6035
8316
|
this.layoutState = {
|
|
@@ -6166,6 +8447,7 @@ class Session {
|
|
|
6166
8447
|
this.sharedWorkspaceState = normalized;
|
|
6167
8448
|
this.editorState.isVisible = normalized.isVisible;
|
|
6168
8449
|
this.editorState.openFiles = [...normalized.openFiles];
|
|
8450
|
+
this.workspaceState.markdownSplitPath = normalized.markdownSplitPath;
|
|
6169
8451
|
|
|
6170
8452
|
if (
|
|
6171
8453
|
this.editorState.activeFilePath
|
|
@@ -6180,7 +8462,13 @@ class Session {
|
|
|
6180
8462
|
const activeKey = this.workspaceState.activeTabKey || '';
|
|
6181
8463
|
if (isFileWorkspaceTabKey(activeKey)) {
|
|
6182
8464
|
const filePath = workspaceKeyToFilePath(activeKey);
|
|
6183
|
-
if (
|
|
8465
|
+
if (
|
|
8466
|
+
!this.editorState.openFiles.includes(filePath)
|
|
8467
|
+
|| (
|
|
8468
|
+
isMarkdownPreviewWorkspaceTabKey(activeKey)
|
|
8469
|
+
&& !isSupportedMarkdownPath(filePath)
|
|
8470
|
+
)
|
|
8471
|
+
) {
|
|
6184
8472
|
this.workspaceState.activeTabKey = resolveFallbackActiveKey();
|
|
6185
8473
|
}
|
|
6186
8474
|
} else if (
|
|
@@ -6194,7 +8482,13 @@ class Session {
|
|
|
6194
8482
|
this.workspaceState.lastNonTerminalTabKey || '';
|
|
6195
8483
|
if (isFileWorkspaceTabKey(lastNonTerminalKey)) {
|
|
6196
8484
|
const filePath = workspaceKeyToFilePath(lastNonTerminalKey);
|
|
6197
|
-
if (
|
|
8485
|
+
if (
|
|
8486
|
+
!this.editorState.openFiles.includes(filePath)
|
|
8487
|
+
|| (
|
|
8488
|
+
isMarkdownPreviewWorkspaceTabKey(lastNonTerminalKey)
|
|
8489
|
+
&& !isSupportedMarkdownPath(filePath)
|
|
8490
|
+
)
|
|
8491
|
+
) {
|
|
6198
8492
|
this.workspaceState.lastNonTerminalTabKey = '';
|
|
6199
8493
|
}
|
|
6200
8494
|
}
|
|
@@ -6358,10 +8652,11 @@ class Session {
|
|
|
6358
8652
|
|
|
6359
8653
|
const titleEl = tab.querySelector('.title');
|
|
6360
8654
|
const titleTextEl = tab.querySelector('.tab-title-text');
|
|
8655
|
+
const displayTitle = formatWorkspaceTabTitle(this.title);
|
|
6361
8656
|
if (titleTextEl) {
|
|
6362
|
-
titleTextEl.textContent =
|
|
8657
|
+
titleTextEl.textContent = displayTitle;
|
|
6363
8658
|
} else if (titleEl) {
|
|
6364
|
-
titleEl.textContent =
|
|
8659
|
+
titleEl.textContent = displayTitle;
|
|
6365
8660
|
}
|
|
6366
8661
|
|
|
6367
8662
|
const titleIconEl = tab.querySelector('.tab-status-icon');
|
|
@@ -6790,6 +9085,9 @@ class AgentTab {
|
|
|
6790
9085
|
this.scrollToBottomOnNextRender = true;
|
|
6791
9086
|
this.busySyncTimer = null;
|
|
6792
9087
|
this.planHistory = [];
|
|
9088
|
+
this.historyWindowStart = -1;
|
|
9089
|
+
this.historyWindowEnd = -1;
|
|
9090
|
+
this.historyWindowLoading = false;
|
|
6793
9091
|
this.resumeSessions = [];
|
|
6794
9092
|
this.resumeSessionsLoadedAt = 0;
|
|
6795
9093
|
this.resumeSessionsPromise = null;
|
|
@@ -7818,6 +10116,25 @@ function getActiveServer() {
|
|
|
7818
10116
|
return getActiveSession()?.server || getMainServer();
|
|
7819
10117
|
}
|
|
7820
10118
|
|
|
10119
|
+
function getDocumentTitle() {
|
|
10120
|
+
const server = getActiveServer();
|
|
10121
|
+
if (!server) {
|
|
10122
|
+
return 'Tabminal';
|
|
10123
|
+
}
|
|
10124
|
+
const host = String(getDisplayHost(server) || '').trim();
|
|
10125
|
+
if (!host || host.toLowerCase() === 'unknown') {
|
|
10126
|
+
return 'Tabminal';
|
|
10127
|
+
}
|
|
10128
|
+
return `Tabminal: ${host}`;
|
|
10129
|
+
}
|
|
10130
|
+
|
|
10131
|
+
function updateDocumentTitle() {
|
|
10132
|
+
const nextTitle = getDocumentTitle();
|
|
10133
|
+
if (document.title !== nextTitle) {
|
|
10134
|
+
document.title = nextTitle;
|
|
10135
|
+
}
|
|
10136
|
+
}
|
|
10137
|
+
|
|
7821
10138
|
function getSessionsForServer(serverId) {
|
|
7822
10139
|
return Array.from(state.sessions.values()).filter(
|
|
7823
10140
|
session => session.serverId === serverId
|
|
@@ -7984,6 +10301,9 @@ function getWorkspaceTabKeysForSession(session) {
|
|
|
7984
10301
|
}
|
|
7985
10302
|
for (const path of session.editorState?.openFiles || []) {
|
|
7986
10303
|
keys.push(makeFileWorkspaceTabKey(path));
|
|
10304
|
+
if (isSupportedMarkdownPath(path)) {
|
|
10305
|
+
keys.push(makeMarkdownPreviewWorkspaceTabKey(path));
|
|
10306
|
+
}
|
|
7987
10307
|
}
|
|
7988
10308
|
for (const agentTab of getAgentTabsForSession(session)) {
|
|
7989
10309
|
keys.push(agentTab.key);
|
|
@@ -8147,6 +10467,7 @@ function normalizeAgentSessionCapabilities(sessionCapabilities) {
|
|
|
8147
10467
|
return {
|
|
8148
10468
|
load: !!source.load,
|
|
8149
10469
|
list: !!source.list,
|
|
10470
|
+
listAll: !!source.listAll,
|
|
8150
10471
|
resume: !!source.resume,
|
|
8151
10472
|
fork: !!source.fork
|
|
8152
10473
|
};
|
|
@@ -8564,6 +10885,9 @@ function getAgentResumeSuggestions(agentTab, promptValue, sessions = []) {
|
|
|
8564
10885
|
const intent = getAgentPromptIntent(agentTab, promptValue);
|
|
8565
10886
|
if (intent.kind !== 'resume') return [];
|
|
8566
10887
|
const query = String(intent.query || '').toLowerCase();
|
|
10888
|
+
const currentCwd = String(
|
|
10889
|
+
agentTab?.cwd || agentTab?.getLinkedSession?.()?.cwd || ''
|
|
10890
|
+
).trim().toLowerCase();
|
|
8567
10891
|
const openSessions = getOpenAgentSessionsForServer(
|
|
8568
10892
|
agentTab?.serverId,
|
|
8569
10893
|
agentTab?.agentId
|
|
@@ -8577,24 +10901,30 @@ function getAgentResumeSuggestions(agentTab, promptValue, sessions = []) {
|
|
|
8577
10901
|
).toLowerCase();
|
|
8578
10902
|
const cwd = String(session.cwd || '').toLowerCase();
|
|
8579
10903
|
const sessionId = String(session.sessionId || '').toLowerCase();
|
|
10904
|
+
const cwdMatch = !!currentCwd && cwd === currentCwd;
|
|
8580
10905
|
const titleMatch = !query || displayName.includes(query);
|
|
8581
|
-
const otherMatch = !query
|
|
10906
|
+
const otherMatch = !query
|
|
10907
|
+
|| cwd.includes(query)
|
|
10908
|
+
|| sessionId.includes(query);
|
|
8582
10909
|
return {
|
|
8583
10910
|
session,
|
|
8584
10911
|
index,
|
|
10912
|
+
cwdMatch,
|
|
8585
10913
|
titleMatch,
|
|
8586
10914
|
matched: titleMatch || otherMatch
|
|
8587
10915
|
};
|
|
8588
10916
|
})
|
|
8589
10917
|
.filter(({ matched }) => matched)
|
|
8590
10918
|
.sort((left, right) => {
|
|
10919
|
+
if (left.cwdMatch !== right.cwdMatch) {
|
|
10920
|
+
return left.cwdMatch ? -1 : 1;
|
|
10921
|
+
}
|
|
8591
10922
|
if (left.titleMatch !== right.titleMatch) {
|
|
8592
10923
|
return left.titleMatch ? -1 : 1;
|
|
8593
10924
|
}
|
|
8594
10925
|
return left.index - right.index;
|
|
8595
10926
|
})
|
|
8596
10927
|
.map(({ session }) => session)
|
|
8597
|
-
.slice(0, 12)
|
|
8598
10928
|
.map((session) => ({
|
|
8599
10929
|
...session,
|
|
8600
10930
|
openTabKey: openSessions.get(session.sessionId)?.key || '',
|
|
@@ -8891,6 +11221,106 @@ function getAgentTimelineItems(agentTab) {
|
|
|
8891
11221
|
return items;
|
|
8892
11222
|
}
|
|
8893
11223
|
|
|
11224
|
+
function getAgentTimelineItemKey(entry, absoluteIndex = 0) {
|
|
11225
|
+
if (!entry) {
|
|
11226
|
+
return `unknown:${absoluteIndex}`;
|
|
11227
|
+
}
|
|
11228
|
+
const order = Number.isFinite(entry.order) ? entry.order : -1;
|
|
11229
|
+
return `${entry.type}:${order}:${absoluteIndex}`;
|
|
11230
|
+
}
|
|
11231
|
+
|
|
11232
|
+
function formatWorkspaceTabTitle(
|
|
11233
|
+
value,
|
|
11234
|
+
maxLength = WORKSPACE_TAB_TITLE_MAX_LENGTH
|
|
11235
|
+
) {
|
|
11236
|
+
const text = String(value || '');
|
|
11237
|
+
const characters = Array.from(text);
|
|
11238
|
+
if (characters.length <= maxLength) {
|
|
11239
|
+
return text;
|
|
11240
|
+
}
|
|
11241
|
+
if (maxLength <= 3) {
|
|
11242
|
+
return '.'.repeat(Math.max(0, maxLength));
|
|
11243
|
+
}
|
|
11244
|
+
return `${characters.slice(0, maxLength - 3).join('')}...`;
|
|
11245
|
+
}
|
|
11246
|
+
|
|
11247
|
+
function getAgentTranscriptWindow(
|
|
11248
|
+
agentTab,
|
|
11249
|
+
totalCount = 0,
|
|
11250
|
+
options = {}
|
|
11251
|
+
) {
|
|
11252
|
+
const total = Number.isFinite(totalCount)
|
|
11253
|
+
? Math.max(0, totalCount)
|
|
11254
|
+
: 0;
|
|
11255
|
+
const windowSize = Math.min(total, AGENT_TRANSCRIPT_INITIAL_VISIBLE_BLOCKS);
|
|
11256
|
+
const latestStart = Math.max(0, total - windowSize);
|
|
11257
|
+
const latestWindow = {
|
|
11258
|
+
start: latestStart,
|
|
11259
|
+
end: total
|
|
11260
|
+
};
|
|
11261
|
+
if (!agentTab) {
|
|
11262
|
+
return latestWindow;
|
|
11263
|
+
}
|
|
11264
|
+
if (windowSize === 0) {
|
|
11265
|
+
agentTab.historyWindowStart = 0;
|
|
11266
|
+
agentTab.historyWindowEnd = 0;
|
|
11267
|
+
return { start: 0, end: 0 };
|
|
11268
|
+
}
|
|
11269
|
+
if (options.pinToBottom) {
|
|
11270
|
+
agentTab.historyWindowStart = latestWindow.start;
|
|
11271
|
+
agentTab.historyWindowEnd = latestWindow.end;
|
|
11272
|
+
return latestWindow;
|
|
11273
|
+
}
|
|
11274
|
+
let start = Number.isFinite(agentTab.historyWindowStart)
|
|
11275
|
+
? Math.max(0, Math.floor(agentTab.historyWindowStart))
|
|
11276
|
+
: latestWindow.start;
|
|
11277
|
+
let end = Number.isFinite(agentTab.historyWindowEnd)
|
|
11278
|
+
? Math.max(start, Math.floor(agentTab.historyWindowEnd))
|
|
11279
|
+
: latestWindow.end;
|
|
11280
|
+
if (end > total) {
|
|
11281
|
+
end = total;
|
|
11282
|
+
}
|
|
11283
|
+
if (end - start !== windowSize) {
|
|
11284
|
+
if (total <= windowSize) {
|
|
11285
|
+
start = 0;
|
|
11286
|
+
end = total;
|
|
11287
|
+
} else if (end >= total) {
|
|
11288
|
+
end = total;
|
|
11289
|
+
start = latestWindow.start;
|
|
11290
|
+
} else if (start <= 0) {
|
|
11291
|
+
start = 0;
|
|
11292
|
+
end = windowSize;
|
|
11293
|
+
} else {
|
|
11294
|
+
end = Math.min(total, start + windowSize);
|
|
11295
|
+
start = Math.max(0, end - windowSize);
|
|
11296
|
+
}
|
|
11297
|
+
}
|
|
11298
|
+
agentTab.historyWindowStart = start;
|
|
11299
|
+
agentTab.historyWindowEnd = end;
|
|
11300
|
+
return { start, end };
|
|
11301
|
+
}
|
|
11302
|
+
|
|
11303
|
+
function isAgentTranscriptWindowNearLatest(agentTab, totalCount = 0) {
|
|
11304
|
+
const total = Number.isFinite(totalCount)
|
|
11305
|
+
? Math.max(0, totalCount)
|
|
11306
|
+
: 0;
|
|
11307
|
+
if (!agentTab) {
|
|
11308
|
+
return true;
|
|
11309
|
+
}
|
|
11310
|
+
if (
|
|
11311
|
+
!Number.isFinite(agentTab.historyWindowStart)
|
|
11312
|
+
|| !Number.isFinite(agentTab.historyWindowEnd)
|
|
11313
|
+
|| agentTab.historyWindowStart < 0
|
|
11314
|
+
|| agentTab.historyWindowEnd < 0
|
|
11315
|
+
) {
|
|
11316
|
+
return true;
|
|
11317
|
+
}
|
|
11318
|
+
return agentTab.historyWindowEnd >= Math.max(
|
|
11319
|
+
0,
|
|
11320
|
+
total - AGENT_TRANSCRIPT_FOLLOW_LATEST_TOLERANCE
|
|
11321
|
+
);
|
|
11322
|
+
}
|
|
11323
|
+
|
|
8894
11324
|
function normalizePlanStatusClass(status = '') {
|
|
8895
11325
|
const value = String(status || '').toLowerCase();
|
|
8896
11326
|
if (value === 'completed') return 'completed';
|
|
@@ -11135,7 +13565,7 @@ async function syncAgentsForServer(server, { force = false } = {}) {
|
|
|
11135
13565
|
&& state.agentTabs.has(activeKey)
|
|
11136
13566
|
) {
|
|
11137
13567
|
noteRecentAgentTab(session, activeKey);
|
|
11138
|
-
session.saveState();
|
|
13568
|
+
session.saveState({ touchWorkspace: true });
|
|
11139
13569
|
}
|
|
11140
13570
|
}
|
|
11141
13571
|
|
|
@@ -11169,7 +13599,7 @@ async function syncAgentsForServer(server, { force = false } = {}) {
|
|
|
11169
13599
|
getAgentTabsForSession(preferredSession)[0]?.key || ''
|
|
11170
13600
|
);
|
|
11171
13601
|
}
|
|
11172
|
-
preferredSession.saveState();
|
|
13602
|
+
preferredSession.saveState({ touchWorkspace: true });
|
|
11173
13603
|
if (state.activeSessionKey === preferredSession.key) {
|
|
11174
13604
|
restoreWorkspaceForSession(preferredSession);
|
|
11175
13605
|
} else {
|
|
@@ -11208,7 +13638,7 @@ async function activateAgentTab(session, agentTab, options = {}) {
|
|
|
11208
13638
|
}
|
|
11209
13639
|
session.workspaceState.activeTabKey = agentTab.key;
|
|
11210
13640
|
noteRecentAgentTab(session, agentTab.key);
|
|
11211
|
-
session.saveState();
|
|
13641
|
+
session.saveState({ touchWorkspace: true });
|
|
11212
13642
|
if (state.activeSessionKey === session.key) {
|
|
11213
13643
|
restoreWorkspaceForSession(session);
|
|
11214
13644
|
requestAnimationFrame(() => {
|
|
@@ -11481,6 +13911,7 @@ async function syncServer(server) {
|
|
|
11481
13911
|
}
|
|
11482
13912
|
|
|
11483
13913
|
const updates = { sessions: [] };
|
|
13914
|
+
const sentFileWrites = new Map();
|
|
11484
13915
|
for (const [sessionKey, pending] of pendingChanges.sessions) {
|
|
11485
13916
|
const { serverId, sessionId } = splitSessionKey(sessionKey);
|
|
11486
13917
|
if (serverId !== server.id) continue;
|
|
@@ -11497,10 +13928,31 @@ async function syncServer(server) {
|
|
|
11497
13928
|
hasUpdate = true;
|
|
11498
13929
|
}
|
|
11499
13930
|
if (pending.fileWrites && pending.fileWrites.size > 0) {
|
|
11500
|
-
|
|
13931
|
+
const fileWrites = Array.from(
|
|
11501
13932
|
pending.fileWrites.entries()
|
|
11502
|
-
)
|
|
11503
|
-
|
|
13933
|
+
)
|
|
13934
|
+
.map(([path, write]) => ({
|
|
13935
|
+
path,
|
|
13936
|
+
write: editorManager.normalizePendingFileWrite(write)
|
|
13937
|
+
}))
|
|
13938
|
+
.filter(({ write }) => !write.blocked);
|
|
13939
|
+
if (fileWrites.length > 0) {
|
|
13940
|
+
sessionUpdate.fileWrites = fileWrites.map(
|
|
13941
|
+
({ path, write }) => ({
|
|
13942
|
+
path,
|
|
13943
|
+
content: write.content,
|
|
13944
|
+
expectedVersion: write.expectedVersion,
|
|
13945
|
+
force: write.force === true
|
|
13946
|
+
})
|
|
13947
|
+
);
|
|
13948
|
+
sentFileWrites.set(
|
|
13949
|
+
sessionUpdate.id,
|
|
13950
|
+
new Map(
|
|
13951
|
+
fileWrites.map(({ path, write }) => [path, write])
|
|
13952
|
+
)
|
|
13953
|
+
);
|
|
13954
|
+
hasUpdate = true;
|
|
13955
|
+
}
|
|
11504
13956
|
}
|
|
11505
13957
|
|
|
11506
13958
|
if (hasUpdate) {
|
|
@@ -11544,11 +13996,6 @@ async function syncServer(server) {
|
|
|
11544
13996
|
|
|
11545
13997
|
if (update.resize) delete pending.resize;
|
|
11546
13998
|
if (update.workspaceState) delete pending.workspaceState;
|
|
11547
|
-
if (update.fileWrites) {
|
|
11548
|
-
for (const file of update.fileWrites) {
|
|
11549
|
-
pending.fileWrites.delete(file.path);
|
|
11550
|
-
}
|
|
11551
|
-
}
|
|
11552
13999
|
}
|
|
11553
14000
|
|
|
11554
14001
|
const data = await response.json();
|
|
@@ -11575,6 +14022,33 @@ async function syncServer(server) {
|
|
|
11575
14022
|
const sessions = Array.isArray(data) ? data : data.sessions;
|
|
11576
14023
|
reconcileSessions(server, sessions || []);
|
|
11577
14024
|
reconcileAgentInventory(server, data.agents);
|
|
14025
|
+
await editorManager.applyFileWriteResults(
|
|
14026
|
+
server,
|
|
14027
|
+
Array.isArray(data?.fileWriteResults)
|
|
14028
|
+
? data.fileWriteResults
|
|
14029
|
+
: [],
|
|
14030
|
+
sentFileWrites
|
|
14031
|
+
);
|
|
14032
|
+
|
|
14033
|
+
for (const [sessionId, writes] of sentFileWrites.entries()) {
|
|
14034
|
+
const pending = pendingChanges.sessions.get(
|
|
14035
|
+
makeSessionKey(server.id, sessionId)
|
|
14036
|
+
);
|
|
14037
|
+
if (!pending?.fileWrites) {
|
|
14038
|
+
continue;
|
|
14039
|
+
}
|
|
14040
|
+
for (const [path] of writes.entries()) {
|
|
14041
|
+
if (!pending.fileWrites.has(path)) {
|
|
14042
|
+
continue;
|
|
14043
|
+
}
|
|
14044
|
+
const current = editorManager.normalizePendingFileWrite(
|
|
14045
|
+
pending.fileWrites.get(path)
|
|
14046
|
+
);
|
|
14047
|
+
if (!current.blocked) {
|
|
14048
|
+
pending.fileWrites.delete(path);
|
|
14049
|
+
}
|
|
14050
|
+
}
|
|
14051
|
+
}
|
|
11578
14052
|
} catch (error) {
|
|
11579
14053
|
if (!wasReconnecting) {
|
|
11580
14054
|
console.warn(
|
|
@@ -12096,7 +14570,7 @@ function reconcileSessions(server, remoteSessions) {
|
|
|
12096
14570
|
}
|
|
12097
14571
|
}
|
|
12098
14572
|
|
|
12099
|
-
async function createNewSession(server = getActiveServer()) {
|
|
14573
|
+
async function createNewSession(server = getActiveServer(), options = {}) {
|
|
12100
14574
|
if (!server) return;
|
|
12101
14575
|
if (server.needsLogin || !server.isAuthenticated) {
|
|
12102
14576
|
const password = window.prompt(`Password for ${getDisplayHost(server)}`);
|
|
@@ -12104,16 +14578,15 @@ async function createNewSession(server = getActiveServer()) {
|
|
|
12104
14578
|
await server.login(password);
|
|
12105
14579
|
}
|
|
12106
14580
|
try {
|
|
12107
|
-
const
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
options.cwd = activeSession.cwd;
|
|
14581
|
+
const request = {};
|
|
14582
|
+
if (typeof options.cwd === 'string' && options.cwd.trim()) {
|
|
14583
|
+
request.cwd = options.cwd.trim();
|
|
12111
14584
|
}
|
|
12112
14585
|
|
|
12113
14586
|
const response = await server.fetch('/api/sessions', {
|
|
12114
14587
|
method: 'POST',
|
|
12115
14588
|
headers: { 'Content-Type': 'application/json' },
|
|
12116
|
-
body: JSON.stringify(
|
|
14589
|
+
body: JSON.stringify(request)
|
|
12117
14590
|
});
|
|
12118
14591
|
if (!response.ok) throw new Error('Failed to create session');
|
|
12119
14592
|
const newSession = await response.json();
|
|
@@ -12141,6 +14614,7 @@ function removeSession(key) {
|
|
|
12141
14614
|
|
|
12142
14615
|
// #region UI Logic
|
|
12143
14616
|
function renderTabs() {
|
|
14617
|
+
updateDocumentTitle();
|
|
12144
14618
|
if (!tabListEl) return;
|
|
12145
14619
|
|
|
12146
14620
|
const newTabItem = document.getElementById('new-tab-item');
|
|
@@ -12561,7 +15035,8 @@ const confirmModalState = {
|
|
|
12561
15035
|
resolve: null,
|
|
12562
15036
|
returnFocus: null,
|
|
12563
15037
|
preferredFocus: 'confirm',
|
|
12564
|
-
hideCancel: false
|
|
15038
|
+
hideCancel: false,
|
|
15039
|
+
allowDismiss: true
|
|
12565
15040
|
};
|
|
12566
15041
|
|
|
12567
15042
|
function isConfirmModalOpen() {
|
|
@@ -12600,6 +15075,7 @@ function settleConfirmModal(result) {
|
|
|
12600
15075
|
confirmModalState.returnFocus = null;
|
|
12601
15076
|
confirmModalState.preferredFocus = 'confirm';
|
|
12602
15077
|
confirmModalState.hideCancel = false;
|
|
15078
|
+
confirmModalState.allowDismiss = true;
|
|
12603
15079
|
if (returnFocus instanceof HTMLElement) {
|
|
12604
15080
|
requestAnimationFrame(() => {
|
|
12605
15081
|
try {
|
|
@@ -12617,8 +15093,11 @@ function showConfirmModal({
|
|
|
12617
15093
|
message = '',
|
|
12618
15094
|
note = '',
|
|
12619
15095
|
confirmLabel = 'Confirm',
|
|
15096
|
+
cancelLabel = 'Cancel',
|
|
12620
15097
|
danger = false,
|
|
12621
15098
|
hideCancel = false,
|
|
15099
|
+
preferredFocus = 'confirm',
|
|
15100
|
+
allowDismiss = true,
|
|
12622
15101
|
returnFocus = null
|
|
12623
15102
|
} = {}) {
|
|
12624
15103
|
if (
|
|
@@ -12638,13 +15117,17 @@ function showConfirmModal({
|
|
|
12638
15117
|
confirmModalMessage.textContent = message;
|
|
12639
15118
|
confirmModalNote.textContent = note;
|
|
12640
15119
|
confirmModalNote.style.display = note ? '' : 'none';
|
|
15120
|
+
confirmModalCancel.textContent = cancelLabel;
|
|
12641
15121
|
confirmModalCancel.style.display = hideCancel ? 'none' : '';
|
|
12642
15122
|
confirmModalConfirm.textContent = confirmLabel;
|
|
12643
15123
|
confirmModalConfirm.classList.toggle('danger-button', danger);
|
|
12644
15124
|
confirmModal.style.display = 'flex';
|
|
12645
15125
|
confirmModalState.returnFocus = returnFocus;
|
|
12646
15126
|
confirmModalState.hideCancel = hideCancel;
|
|
12647
|
-
confirmModalState.preferredFocus = '
|
|
15127
|
+
confirmModalState.preferredFocus = preferredFocus === 'cancel'
|
|
15128
|
+
? 'cancel'
|
|
15129
|
+
: 'confirm';
|
|
15130
|
+
confirmModalState.allowDismiss = allowDismiss !== false;
|
|
12648
15131
|
requestAnimationFrame(() => {
|
|
12649
15132
|
getConfirmModalPreferredButton()?.focus({ preventScroll: true });
|
|
12650
15133
|
});
|
|
@@ -12679,6 +15162,7 @@ function moveConfirmModalFocus(delta) {
|
|
|
12679
15162
|
}
|
|
12680
15163
|
|
|
12681
15164
|
function renderServerControls() {
|
|
15165
|
+
updateDocumentTitle();
|
|
12682
15166
|
if (!serverControlsEl) return;
|
|
12683
15167
|
serverControlsEl.innerHTML = '';
|
|
12684
15168
|
|
|
@@ -12834,12 +15318,14 @@ window.addEventListener('focus', () => {
|
|
|
12834
15318
|
enterAppNotificationQuietPeriod();
|
|
12835
15319
|
editorManager.refreshVisibleSessionTrees();
|
|
12836
15320
|
editorManager.updateTreeAutoRefresh();
|
|
15321
|
+
void editorManager.checkActiveFileVersion();
|
|
12837
15322
|
});
|
|
12838
15323
|
window.addEventListener('pageshow', () => {
|
|
12839
15324
|
noteAppInteraction();
|
|
12840
15325
|
enterAppNotificationQuietPeriod();
|
|
12841
15326
|
editorManager.refreshVisibleSessionTrees();
|
|
12842
15327
|
editorManager.updateTreeAutoRefresh();
|
|
15328
|
+
void editorManager.checkActiveFileVersion();
|
|
12843
15329
|
});
|
|
12844
15330
|
|
|
12845
15331
|
document.addEventListener('click', () => {
|
|
@@ -12851,6 +15337,7 @@ document.addEventListener('visibilitychange', () => {
|
|
|
12851
15337
|
enterAppNotificationQuietPeriod();
|
|
12852
15338
|
clearVisibleAttentionState();
|
|
12853
15339
|
editorManager.refreshVisibleSessionTrees();
|
|
15340
|
+
void editorManager.checkActiveFileVersion();
|
|
12854
15341
|
}
|
|
12855
15342
|
editorManager.updateTreeAutoRefresh();
|
|
12856
15343
|
});
|
|
@@ -13048,19 +15535,19 @@ window.addEventListener('tabminal:layout-modechange', () => {
|
|
|
13048
15535
|
&& terminalEl.contains(activeElement)
|
|
13049
15536
|
);
|
|
13050
15537
|
|
|
13051
|
-
|
|
13052
|
-
|
|
13053
|
-
|
|
13054
|
-
|
|
15538
|
+
if (isForcedTerminalWorkspaceMode()) {
|
|
15539
|
+
if (terminalHasFocus) {
|
|
15540
|
+
session.workspaceState.activeTabKey = TERMINAL_WORKSPACE_TAB_KEY;
|
|
15541
|
+
session.saveState({ touchWorkspace: true });
|
|
15542
|
+
}
|
|
15543
|
+
} else if (
|
|
15544
|
+
!editorManager.isTerminalTabPinned(session)
|
|
15545
|
+
&& isTerminalWorkspaceTabKey(session.workspaceState?.activeTabKey || '')
|
|
15546
|
+
) {
|
|
15547
|
+
session.workspaceState.activeTabKey =
|
|
15548
|
+
editorManager.getPreferredNonTerminalWorkspaceTabKey(session);
|
|
15549
|
+
session.saveState({ touchWorkspace: true });
|
|
13055
15550
|
}
|
|
13056
|
-
} else if (
|
|
13057
|
-
!editorManager.isTerminalTabPinned(session)
|
|
13058
|
-
&& isTerminalWorkspaceTabKey(session.workspaceState?.activeTabKey || '')
|
|
13059
|
-
) {
|
|
13060
|
-
session.workspaceState.activeTabKey =
|
|
13061
|
-
editorManager.getPreferredNonTerminalWorkspaceTabKey(session);
|
|
13062
|
-
session.saveState();
|
|
13063
|
-
}
|
|
13064
15551
|
|
|
13065
15552
|
editorManager.switchTo(session);
|
|
13066
15553
|
editorManager.updateEditorPaneVisibility();
|
|
@@ -13281,7 +15768,10 @@ if (
|
|
|
13281
15768
|
});
|
|
13282
15769
|
|
|
13283
15770
|
confirmModal.addEventListener('click', (event) => {
|
|
13284
|
-
if (
|
|
15771
|
+
if (
|
|
15772
|
+
event.target === confirmModal
|
|
15773
|
+
&& confirmModalState.allowDismiss
|
|
15774
|
+
) {
|
|
13285
15775
|
settleConfirmModal(false);
|
|
13286
15776
|
}
|
|
13287
15777
|
});
|
|
@@ -13292,6 +15782,11 @@ if (
|
|
|
13292
15782
|
|
|
13293
15783
|
confirmModal.addEventListener('keydown', (event) => {
|
|
13294
15784
|
if (event.key === 'Escape') {
|
|
15785
|
+
if (!confirmModalState.allowDismiss) {
|
|
15786
|
+
event.preventDefault();
|
|
15787
|
+
event.stopPropagation();
|
|
15788
|
+
return;
|
|
15789
|
+
}
|
|
13295
15790
|
event.preventDefault();
|
|
13296
15791
|
settleConfirmModal(false);
|
|
13297
15792
|
return;
|