dsh-vscode-mode 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/lib/client.js +518 -71
- package/lib/client.js.map +1 -1
- package/lib/index.js +193 -43
- package/lib/index.js.map +1 -1
- package/package.json +8 -7
- package/src/client/ai/inlineProvider.ts +42 -1
- package/src/client/index.ts +47 -45
- package/src/client/monaco/lsp/index.ts +33 -3
- package/src/client/monaco/lsp/lspClient.ts +3 -1
- package/src/client/monaco/lsp/providers.ts +32 -0
- package/src/client/officialSidebar.ts +67 -3
- package/src/client/sessionScope.ts +183 -0
- package/src/client/snippets/provider.ts +26 -4
- package/src/client/tabActions.ts +30 -5
- package/src/client/ui/AiSettings.ts +8 -0
- package/src/client/ui/EditorView.ts +51 -13
- package/src/compat.ts +3 -3
- package/src/dshVersion.ts +8 -1
- package/src/fileOpenSettings.ts +86 -10
- package/src/lsp/extmgr.ts +56 -3
- package/src/lsp/zip.ts +25 -9
- package/src/revert.ts +20 -8
- package/src/shared/ai.ts +25 -0
package/lib/client.js
CHANGED
|
@@ -6744,7 +6744,9 @@ window.__ModuleLoader__.load({
|
|
|
6744
6744
|
/**
|
|
6745
6745
|
* 订阅窗口会话广播(幂等):EditorView 是 sessionId 的权威来源,挂载与会话切换时广播。
|
|
6746
6746
|
* 独立于服务订阅,避免服务形状变化时 LSP 整体失联。
|
|
6747
|
-
*
|
|
6747
|
+
*
|
|
6748
|
+
* 返回值是可重入的取消订阅函数;调用方应保存并在卸载时调用(否则重载后会累积监听)。
|
|
6749
|
+
* @author ddj 2026年09月11号 / 2026年09月18号
|
|
6748
6750
|
* @returns 取消订阅函数
|
|
6749
6751
|
*/
|
|
6750
6752
|
function bindLspSession() {
|
|
@@ -6978,6 +6980,8 @@ window.__ModuleLoader__.load({
|
|
|
6978
6980
|
};
|
|
6979
6981
|
let registered$2 = false;
|
|
6980
6982
|
const disposables = [];
|
|
6983
|
+
/** LSP provider 注册标记的全局挂点名(跨插件重载认领;见 disposeLspProviders)。 */
|
|
6984
|
+
const LSP_PROVIDERS_GLOBAL = "__edrvLspProvidersRegistered__";
|
|
6981
6985
|
/** 目标文件打开并定位(复用现有 openFileAt 的 edrv:open-editor 事件通道)。 */
|
|
6982
6986
|
function openAt(path, target) {
|
|
6983
6987
|
window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: Object.assign({ path }, target) }));
|
|
@@ -7018,6 +7022,11 @@ window.__ModuleLoader__.load({
|
|
|
7018
7022
|
/** 注册全部 Monaco LSP provider 与文档跟踪(幂等)。 */
|
|
7019
7023
|
function registerLspProviders(monaco) {
|
|
7020
7024
|
if (registered$2) return;
|
|
7025
|
+
const host = typeof window === "undefined" ? void 0 : window;
|
|
7026
|
+
if (host && host[LSP_PROVIDERS_GLOBAL]) {
|
|
7027
|
+
registered$2 = true;
|
|
7028
|
+
return;
|
|
7029
|
+
}
|
|
7021
7030
|
registered$2 = true;
|
|
7022
7031
|
const attachModel = (model) => {
|
|
7023
7032
|
if (!model || model.uri.scheme !== "edrv") return;
|
|
@@ -7084,6 +7093,24 @@ window.__ModuleLoader__.load({
|
|
|
7084
7093
|
},
|
|
7085
7094
|
releaseDocumentSemanticTokens: () => {}
|
|
7086
7095
|
}));
|
|
7096
|
+
if (host) host[LSP_PROVIDERS_GLOBAL] = true;
|
|
7097
|
+
}
|
|
7098
|
+
/**
|
|
7099
|
+
* 卸载 LSP provider 集合:注销全部注册并复位状态(插件重载/卸载时调用)。
|
|
7100
|
+
*
|
|
7101
|
+
* 必须存在的原因:DSH 0.1.6-alpha.2 起支持插件运行时卸载/重载,而 `window.monaco`
|
|
7102
|
+
* 由 loader 注入后**跨重载存活**;模块级 `registered`/`disposables` 却随 bundle 重新
|
|
7103
|
+
* 求值清空 —— 不注销则每次重载都重复叠加一整套 provider(跳转/hover/语义高亮翻倍)。
|
|
7104
|
+
* @author ddj 2026年09月18号
|
|
7105
|
+
*/
|
|
7106
|
+
function disposeLspProviders() {
|
|
7107
|
+
for (const dispose of disposables.splice(0)) try {
|
|
7108
|
+
if (typeof dispose === "function") dispose();
|
|
7109
|
+
else if (dispose && typeof dispose.dispose === "function") dispose.dispose();
|
|
7110
|
+
} catch {}
|
|
7111
|
+
registered$2 = false;
|
|
7112
|
+
const host = typeof window === "undefined" ? void 0 : window;
|
|
7113
|
+
if (host) delete host[LSP_PROVIDERS_GLOBAL];
|
|
7087
7114
|
}
|
|
7088
7115
|
/** LSP SymbolInfo(host 归一化后)→ Monaco DocumentSymbol。 */
|
|
7089
7116
|
function toMonacoSymbol(symbol) {
|
|
@@ -7270,12 +7297,23 @@ window.__ModuleLoader__.load({
|
|
|
7270
7297
|
* 作者 ddj 2026-08-27
|
|
7271
7298
|
*/
|
|
7272
7299
|
let sessionBound = false;
|
|
7300
|
+
/** 会话广播取消订阅函数(卸载时调用;落 window 以便跨重载清理上一代监听)。 */
|
|
7301
|
+
let sessionUnbind = null;
|
|
7302
|
+
/** 会话广播订阅标记的全局挂点名(跨插件重载认领;见 disposeLsp)。 */
|
|
7303
|
+
const LSP_SESSION_GLOBAL = "__edrvLspSessionUnbind__";
|
|
7273
7304
|
/** Monaco 加载后装配(幂等;重复调用仅刷新会话)。 */
|
|
7274
7305
|
function setupLsp(monaco) {
|
|
7275
7306
|
registerLspProviders(monaco);
|
|
7307
|
+
const host = typeof window === "undefined" ? void 0 : window;
|
|
7308
|
+
if (host && typeof host[LSP_SESSION_GLOBAL] === "function") {
|
|
7309
|
+
sessionBound = true;
|
|
7310
|
+
sessionUnbind = host[LSP_SESSION_GLOBAL];
|
|
7311
|
+
return;
|
|
7312
|
+
}
|
|
7276
7313
|
if (!sessionBound) {
|
|
7277
7314
|
sessionBound = true;
|
|
7278
|
-
bindLspSession();
|
|
7315
|
+
sessionUnbind = bindLspSession();
|
|
7316
|
+
if (host) host[LSP_SESSION_GLOBAL] = sessionUnbind;
|
|
7279
7317
|
}
|
|
7280
7318
|
}
|
|
7281
7319
|
/** 会话切换:更新 host 侧文档归属 + 刷新状态。 */
|
|
@@ -7283,6 +7321,21 @@ window.__ModuleLoader__.load({
|
|
|
7283
7321
|
setLspSession(sessionId);
|
|
7284
7322
|
refreshStatus(true);
|
|
7285
7323
|
}
|
|
7324
|
+
/**
|
|
7325
|
+
* 卸载 LSP 装配(插件重载/卸载时调用):注销全部 provider、解绑会话广播并复位标记。
|
|
7326
|
+
* @author ddj 2026年09月18号
|
|
7327
|
+
*/
|
|
7328
|
+
function disposeLsp() {
|
|
7329
|
+
disposeLspProviders();
|
|
7330
|
+
const unbind = sessionUnbind;
|
|
7331
|
+
if (typeof unbind === "function") try {
|
|
7332
|
+
unbind();
|
|
7333
|
+
} catch {}
|
|
7334
|
+
sessionUnbind = null;
|
|
7335
|
+
sessionBound = false;
|
|
7336
|
+
const host = typeof window === "undefined" ? void 0 : window;
|
|
7337
|
+
if (host) delete host[LSP_SESSION_GLOBAL];
|
|
7338
|
+
}
|
|
7286
7339
|
//#endregion
|
|
7287
7340
|
//#region src/shared/ai.ts
|
|
7288
7341
|
/**
|
|
@@ -7294,6 +7347,29 @@ window.__ModuleLoader__.load({
|
|
|
7294
7347
|
/** 上下文窗口:前缀字符上限(含未保存编辑的当前文档)。 */
|
|
7295
7348
|
const AI_PREFIX_MAX = 2e3;
|
|
7296
7349
|
/**
|
|
7350
|
+
* 已从官方默认模型列表下架的模型标识(依据 DSH 0.1.6-alpha.2 release notes:
|
|
7351
|
+
* 「默认模型列表移除 V4 Flash 和 V4 Flash Vision Exp」)。
|
|
7352
|
+
*
|
|
7353
|
+
* 用途:用户此前保存过这些模型时,配置仍指向它们,而新版模型目录不再列出 →
|
|
7354
|
+
* 需给出可读降级提示(而非静默失败)。按模型名小写子串匹配,
|
|
7355
|
+
* 因为 provider 前缀与具体版本后缀(如 -exp)在不同部署下写法不一。
|
|
7356
|
+
*/
|
|
7357
|
+
const DELISTED_MODEL_MARKERS = ["v4-flash"];
|
|
7358
|
+
/**
|
|
7359
|
+
* 配置指向的模型是否已被官方下架(G7)。
|
|
7360
|
+
*
|
|
7361
|
+
* 语义:仅当确实指定了 model(非空 = 非自动路由)且命中下架标识时返回 true。
|
|
7362
|
+
* 空 model 表示「自动取首个可用模型」,不构成问题,故不提示。
|
|
7363
|
+
* @author ddj 2026年09月18号
|
|
7364
|
+
* @param model 配置中的模型标识(可空)
|
|
7365
|
+
* @returns 是否已下架
|
|
7366
|
+
*/
|
|
7367
|
+
function isDelistedModel(model) {
|
|
7368
|
+
const text = typeof model === "string" ? model.trim().toLowerCase() : "";
|
|
7369
|
+
if (!text) return false;
|
|
7370
|
+
return DELISTED_MODEL_MARKERS.some((marker) => text.includes(marker));
|
|
7371
|
+
}
|
|
7372
|
+
/**
|
|
7297
7373
|
* 按 AI_PREFIX_MAX/AI_SUFFIX_MAX 裁剪前后缀(client 侧发请求前调用)。
|
|
7298
7374
|
* @author ddj
|
|
7299
7375
|
* @param prefix 光标前全文
|
|
@@ -7356,6 +7432,16 @@ window.__ModuleLoader__.load({
|
|
|
7356
7432
|
let inFlight = null;
|
|
7357
7433
|
let debounceTimer = null;
|
|
7358
7434
|
const cache$1 = /* @__PURE__ */ new Map();
|
|
7435
|
+
/**
|
|
7436
|
+
* 补全 provider 注销器的全局挂点名。
|
|
7437
|
+
*
|
|
7438
|
+
* 为什么挂 window 而不是只靠模块级 registered:DSH 0.1.6-alpha.2 起支持插件运行时
|
|
7439
|
+
* 卸载/重载,而 `window.monaco` 由 loader 注入后**跨重载存活**,模块级状态却会随
|
|
7440
|
+
* bundle 重新求值而复位 —— 只判 registered 会在每次重载后再注册一次 provider,
|
|
7441
|
+
* 导致同一编辑器出现重复补全。故注销器落 window,重载时命中即跳过注册。
|
|
7442
|
+
* @author ddj 2026年09月18号
|
|
7443
|
+
*/
|
|
7444
|
+
const AI_INLINE_GLOBAL = "__edrvAiInlineDisposer__";
|
|
7359
7445
|
/** 上次开关状态(configUpdate 后经事件刷新,关闭时立即静默)。 */
|
|
7360
7446
|
let enabled = false;
|
|
7361
7447
|
/** 供设置面板保存后同步开关状态(不重载页面即时生效)。 */
|
|
@@ -7406,8 +7492,13 @@ window.__ModuleLoader__.load({
|
|
|
7406
7492
|
*/
|
|
7407
7493
|
function registerAiInline(monaco) {
|
|
7408
7494
|
if (registered$1 || !monaco?.languages?.registerInlineCompletionsProvider) return;
|
|
7495
|
+
const host = typeof window === "undefined" ? void 0 : window;
|
|
7496
|
+
if (host && host[AI_INLINE_GLOBAL]) {
|
|
7497
|
+
registered$1 = true;
|
|
7498
|
+
return;
|
|
7499
|
+
}
|
|
7409
7500
|
registered$1 = true;
|
|
7410
|
-
monaco.languages.registerInlineCompletionsProvider("*", {
|
|
7501
|
+
const inlineDisposer = monaco.languages.registerInlineCompletionsProvider("*", {
|
|
7411
7502
|
async provideInlineCompletions(model, position, context, token) {
|
|
7412
7503
|
const t0 = Date.now();
|
|
7413
7504
|
if (!enabled || token?.isCancellationRequested) return { items: [] };
|
|
@@ -7493,6 +7584,36 @@ window.__ModuleLoader__.load({
|
|
|
7493
7584
|
},
|
|
7494
7585
|
freeInlineCompletions() {}
|
|
7495
7586
|
});
|
|
7587
|
+
if (host) host[AI_INLINE_GLOBAL] = inlineDisposer;
|
|
7588
|
+
}
|
|
7589
|
+
/**
|
|
7590
|
+
* 卸载 AI 内联补全:注销 provider 并复位模块状态(插件重载/卸载时调用)。
|
|
7591
|
+
*
|
|
7592
|
+
* 复位 registered 让重装后能重新注册;清 window 挂点避免新实例被旧注销器误判为「已注册」。
|
|
7593
|
+
* 同时作废在飞请求与去抖定时器,防止卸载后仍有回调写状态。
|
|
7594
|
+
* @author ddj 2026年09月18号
|
|
7595
|
+
*/
|
|
7596
|
+
function disposeAiInline() {
|
|
7597
|
+
const host = typeof window === "undefined" ? void 0 : window;
|
|
7598
|
+
const inlineDisposer = host ? host[AI_INLINE_GLOBAL] : void 0;
|
|
7599
|
+
if (inlineDisposer && typeof inlineDisposer.dispose === "function") try {
|
|
7600
|
+
inlineDisposer.dispose();
|
|
7601
|
+
} catch {}
|
|
7602
|
+
if (host) delete host[AI_INLINE_GLOBAL];
|
|
7603
|
+
registered$1 = false;
|
|
7604
|
+
seq += 1;
|
|
7605
|
+
if (debounceTimer) {
|
|
7606
|
+
clearTimeout(debounceTimer);
|
|
7607
|
+
debounceTimer = null;
|
|
7608
|
+
}
|
|
7609
|
+
if (inFlight) {
|
|
7610
|
+
try {
|
|
7611
|
+
inFlight.abort();
|
|
7612
|
+
} catch {}
|
|
7613
|
+
inFlight = null;
|
|
7614
|
+
}
|
|
7615
|
+
cache$1.clear();
|
|
7616
|
+
monacoRef = null;
|
|
7496
7617
|
}
|
|
7497
7618
|
/** 缓存写入(超限整体清空,LRU 简化)。 */
|
|
7498
7619
|
function cacheSet(key, items) {
|
|
@@ -7582,6 +7703,16 @@ window.__ModuleLoader__.load({
|
|
|
7582
7703
|
let cacheAt = 0;
|
|
7583
7704
|
let pending = null;
|
|
7584
7705
|
let registered = false;
|
|
7706
|
+
let disposer = null;
|
|
7707
|
+
/**
|
|
7708
|
+
* 片段 provider 注销器的全局挂点名。
|
|
7709
|
+
*
|
|
7710
|
+
* 为什么挂 window:`window.monaco` 由 loader 注入后**跨插件重载存活**,而模块级
|
|
7711
|
+
* registered/disposer 会随 bundle 重新求值复位 —— 只判 registered 会在每次重载后
|
|
7712
|
+
* 重复注册补全 provider(同一编辑器出现重复候补)。故注销器落 window 供跨代认领与清理。
|
|
7713
|
+
* @author ddj 2026年09月18号
|
|
7714
|
+
*/
|
|
7715
|
+
const SNIPPET_GLOBAL = "__edrvSnippetsDisposer__";
|
|
7585
7716
|
/** 当前会话 id(补全按会话工作区叠加项目片段;EditorView 装配时注入)。 */
|
|
7586
7717
|
let sessionId = null;
|
|
7587
7718
|
/**
|
|
@@ -7653,11 +7784,16 @@ window.__ModuleLoader__.load({
|
|
|
7653
7784
|
*/
|
|
7654
7785
|
function registerSnippetProvider(monaco) {
|
|
7655
7786
|
if (registered || !monaco?.languages?.registerCompletionItemProvider) return;
|
|
7787
|
+
const host = typeof window === "undefined" ? void 0 : window;
|
|
7788
|
+
if (host && host[SNIPPET_GLOBAL]) {
|
|
7789
|
+
registered = true;
|
|
7790
|
+
return;
|
|
7791
|
+
}
|
|
7656
7792
|
const snippetKind = monaco.languages.CompletionItemKind?.Snippet;
|
|
7657
7793
|
const asSnippet = monaco.languages.CompletionItemInsertTextRule?.InsertAsSnippet;
|
|
7658
7794
|
if (typeof snippetKind !== "number" || typeof asSnippet !== "number") return;
|
|
7659
7795
|
registered = true;
|
|
7660
|
-
monaco.languages.registerCompletionItemProvider("*", { async provideCompletionItems(model, position) {
|
|
7796
|
+
disposer = monaco.languages.registerCompletionItemProvider("*", { async provideCompletionItems(model, position) {
|
|
7661
7797
|
const entries = await loadEntries();
|
|
7662
7798
|
if (!entries.length) return { suggestions: [] };
|
|
7663
7799
|
const items = entriesForLanguage(entries, model?.getLanguageId?.());
|
|
@@ -7680,6 +7816,7 @@ window.__ModuleLoader__.load({
|
|
|
7680
7816
|
range
|
|
7681
7817
|
})).filter((item) => Boolean(item.label)) };
|
|
7682
7818
|
} });
|
|
7819
|
+
if (host) host[SNIPPET_GLOBAL] = disposer;
|
|
7683
7820
|
}
|
|
7684
7821
|
/**
|
|
7685
7822
|
* 装配:Monaco 就绪后注册 provider(幂等)。
|
|
@@ -7690,6 +7827,22 @@ window.__ModuleLoader__.load({
|
|
|
7690
7827
|
registerSnippetProvider(monaco);
|
|
7691
7828
|
}
|
|
7692
7829
|
/**
|
|
7830
|
+
* 卸载:注销 provider 并复位状态(插件热重载/卸载时调用)。
|
|
7831
|
+
* 兼容上一代 bundle 遗留的 window 注销器(模块级 disposer 为空时仍能清干净)。
|
|
7832
|
+
* @author ddj 2026年09月10号 / 2026年09月18号
|
|
7833
|
+
*/
|
|
7834
|
+
function disposeSnippets() {
|
|
7835
|
+
const host = typeof window === "undefined" ? void 0 : window;
|
|
7836
|
+
const target = (typeof disposer === "function" ? disposer : null) ?? (host ? host[SNIPPET_GLOBAL] : null);
|
|
7837
|
+
if (target && typeof target.dispose === "function") try {
|
|
7838
|
+
target.dispose();
|
|
7839
|
+
} catch {}
|
|
7840
|
+
if (host) delete host[SNIPPET_GLOBAL];
|
|
7841
|
+
disposer = null;
|
|
7842
|
+
registered = false;
|
|
7843
|
+
invalidateSnippets();
|
|
7844
|
+
}
|
|
7845
|
+
/**
|
|
7693
7846
|
* 读取可插入条目(「插入代码片段」命令用;含无前缀条目)。
|
|
7694
7847
|
* @author ddj 2026年09月10号
|
|
7695
7848
|
* @param languageId 当前模型语言(缺省返回全语言 + 该语言条目)
|
|
@@ -8258,23 +8411,50 @@ window.__ModuleLoader__.load({
|
|
|
8258
8411
|
/**
|
|
8259
8412
|
* 归一化持久化的页签数据:兼容旧版 `string[]`、去重、固定分区。
|
|
8260
8413
|
* 损坏项(非字符串/非对象/无 path)直接丢弃。
|
|
8261
|
-
*
|
|
8414
|
+
*
|
|
8415
|
+
* G9:传入 cwd 时把每个路径收敛为**页签规范形态**(工作区相对路径),
|
|
8416
|
+
* 以迁移历史持久化数据里残留的绝对路径(见 {@link tabPathOf})。
|
|
8417
|
+
* @author ddj 2026年09月11号 / 2026年09月18号
|
|
8262
8418
|
* @param raw localStorage 解析结果(任意形状)
|
|
8419
|
+
* @param cwd 会话工作区目录(可选;给出时同步归一化路径形态)
|
|
8263
8420
|
* @returns 归一化后的页签数组
|
|
8264
8421
|
*/
|
|
8265
|
-
function normalizeTabs(raw) {
|
|
8422
|
+
function normalizeTabs(raw, cwd) {
|
|
8266
8423
|
if (!Array.isArray(raw)) return [];
|
|
8267
8424
|
const seen = /* @__PURE__ */ new Set();
|
|
8268
8425
|
const out = [];
|
|
8269
8426
|
for (const item of raw) {
|
|
8270
8427
|
const tab = tabOf(item);
|
|
8271
|
-
if (!tab
|
|
8272
|
-
|
|
8273
|
-
|
|
8428
|
+
if (!tab) continue;
|
|
8429
|
+
const path = tabPathOf(tab.path, cwd);
|
|
8430
|
+
if (!path || seen.has(path)) continue;
|
|
8431
|
+
seen.add(path);
|
|
8432
|
+
out.push(tab.pinned === true ? {
|
|
8433
|
+
path,
|
|
8434
|
+
pinned: true
|
|
8435
|
+
} : { path });
|
|
8274
8436
|
}
|
|
8275
8437
|
return pinnedFirst(out);
|
|
8276
8438
|
}
|
|
8277
8439
|
/**
|
|
8440
|
+
* 页签规范形态 = 工作区相对路径(G9)。
|
|
8441
|
+
*
|
|
8442
|
+
* 为什么需要:差异记录路径取自工具结果 `target.displayPath`(官方恒为绝对拼写),
|
|
8443
|
+
* 而资源管理器树给出的是工作区相对路径。两者进页签后,地址栏形态不一致,且
|
|
8444
|
+
* `insertTab` 按原串去重 → 同一文件可能出两个页签;绝对路径还会被
|
|
8445
|
+
* `isTreeRevealable` 判为不可定位,「在资源管理器视图中显示」对差异入口失效。
|
|
8446
|
+
* 统一经 {@link relativeOf} 收敛后,页签/地址栏/去重/持久化/`sameFile` 口径一致。
|
|
8447
|
+
*
|
|
8448
|
+
* 工作区外文件(全局片段/规则)由 relativeOf 自然回退为原绝对路径,语义不变。
|
|
8449
|
+
* @author ddj 2026年09月18号
|
|
8450
|
+
* @param path 原始路径(绝对或相对,两种分隔符均可)
|
|
8451
|
+
* @param cwd 会话工作区目录(可空;无 cwd 时仅做分隔符归一)
|
|
8452
|
+
* @returns 页签规范路径
|
|
8453
|
+
*/
|
|
8454
|
+
function tabPathOf(path, cwd) {
|
|
8455
|
+
return relativeOf(path, cwd);
|
|
8456
|
+
}
|
|
8457
|
+
/**
|
|
8278
8458
|
* 解释单条持久化页签:字符串 = 路径(旧版),对象取 path/pinned。
|
|
8279
8459
|
* @author ddj 2026年09月11号
|
|
8280
8460
|
* @param item 原始条目
|
|
@@ -11410,6 +11590,32 @@ window.__ModuleLoader__.load({
|
|
|
11410
11590
|
if (select) setActive(path);
|
|
11411
11591
|
};
|
|
11412
11592
|
/**
|
|
11593
|
+
* 页签规范路径(G9):统一收敛为工作区相对路径后再进页签。
|
|
11594
|
+
*
|
|
11595
|
+
* 差异记录路径取自工具结果 `target.displayPath`(官方恒绝对),而资源管理器树给相对路径;
|
|
11596
|
+
* 不归一会导致地址栏形态不一致、同文件出现两个页签(insertTab 按原串去重)、
|
|
11597
|
+
* 且绝对路径被 isTreeRevealable 判为不可定位(「在资源管理器视图中显示」失效)。
|
|
11598
|
+
* 工作区外文件由 relativeOf 回退原绝对路径,语义不变。
|
|
11599
|
+
* @author ddj 2026年09月18号
|
|
11600
|
+
* @param path 原始路径(绝对或相对)
|
|
11601
|
+
* @returns 页签规范路径
|
|
11602
|
+
*/
|
|
11603
|
+
const tabPath = (path) => tabPathOf(path, cwd);
|
|
11604
|
+
/**
|
|
11605
|
+
* 打开文件入口的统一收敛(G9):所有「进页签」路径都经此归一,避免逐点打补丁。
|
|
11606
|
+
* @author ddj 2026年09月18号
|
|
11607
|
+
* @param path 原始路径
|
|
11608
|
+
* @param select 是否设为活动页签
|
|
11609
|
+
* @returns 归一后的路径(空值返回 null)
|
|
11610
|
+
*/
|
|
11611
|
+
const addTabNorm = (path, select) => {
|
|
11612
|
+
if (!path) return null;
|
|
11613
|
+
const normalized = tabPath(path);
|
|
11614
|
+
if (!normalized) return null;
|
|
11615
|
+
addTab(normalized, select);
|
|
11616
|
+
return normalized;
|
|
11617
|
+
};
|
|
11618
|
+
/**
|
|
11413
11619
|
* 保存当前活动文件的视图状态(光标/滚动/折叠)到工作区作用域缓存。
|
|
11414
11620
|
* @author ddj 2026年08月28号
|
|
11415
11621
|
* @param path 要保存的文件路径(缺省 = 当前 active)
|
|
@@ -11495,7 +11701,7 @@ window.__ModuleLoader__.load({
|
|
|
11495
11701
|
flushSave();
|
|
11496
11702
|
saveViewState(active);
|
|
11497
11703
|
navPendingRef.current = entry;
|
|
11498
|
-
|
|
11704
|
+
addTabNorm(entry.path, true);
|
|
11499
11705
|
setFocusRequest((value) => value + 1);
|
|
11500
11706
|
};
|
|
11501
11707
|
/**
|
|
@@ -11978,22 +12184,24 @@ window.__ModuleLoader__.load({
|
|
|
11978
12184
|
const onOpen = (e) => {
|
|
11979
12185
|
const p = e?.detail?.path;
|
|
11980
12186
|
if (!p) return;
|
|
12187
|
+
const normalized = tabPath(p);
|
|
12188
|
+
if (!normalized) return;
|
|
11981
12189
|
if (e?.detail?.focusDiff === true) {
|
|
11982
12190
|
recordNav();
|
|
11983
12191
|
pendingFocusRef.current = {
|
|
11984
|
-
path:
|
|
12192
|
+
path: normalized,
|
|
11985
12193
|
region: null
|
|
11986
12194
|
};
|
|
11987
12195
|
setFocusRequest((value) => value + 1);
|
|
11988
|
-
addTab(
|
|
12196
|
+
addTab(normalized, true);
|
|
11989
12197
|
return;
|
|
11990
12198
|
}
|
|
11991
12199
|
if (e?.detail?.line != null) {
|
|
11992
|
-
openFileAt(
|
|
12200
|
+
openFileAt(normalized, e?.detail?.line, e?.detail?.column, e?.detail?.endLine, e?.detail?.endColumn);
|
|
11993
12201
|
return;
|
|
11994
12202
|
}
|
|
11995
12203
|
recordNav();
|
|
11996
|
-
addTab(
|
|
12204
|
+
addTab(normalized, true);
|
|
11997
12205
|
};
|
|
11998
12206
|
const onShowLauncher = (event) => {
|
|
11999
12207
|
const tab = event?.detail?.tab;
|
|
@@ -12077,10 +12285,11 @@ window.__ModuleLoader__.load({
|
|
|
12077
12285
|
const raw = localStorage.getItem(CACHE_KEY.editor + String(scope)) ?? localStorage.getItem(CACHE_KEY.editorLegacy + String(scope));
|
|
12078
12286
|
if (raw) {
|
|
12079
12287
|
const saved = JSON.parse(raw);
|
|
12080
|
-
const restored = normalizeTabs(saved?.tabs);
|
|
12288
|
+
const restored = normalizeTabs(saved?.tabs, cwd);
|
|
12081
12289
|
if (restored.length) {
|
|
12082
12290
|
setTabs(restored);
|
|
12083
|
-
|
|
12291
|
+
const wanted = typeof saved?.active === "string" ? tabPathOf(saved.active, cwd) : saved?.active;
|
|
12292
|
+
setActive(pickActive(restored, wanted));
|
|
12084
12293
|
}
|
|
12085
12294
|
}
|
|
12086
12295
|
} catch (e) {}
|
|
@@ -13818,9 +14027,9 @@ window.__ModuleLoader__.load({
|
|
|
13818
14027
|
if (!path) return;
|
|
13819
14028
|
setSvnDiff(null);
|
|
13820
14029
|
recordNav();
|
|
13821
|
-
|
|
13822
|
-
if (focusDiff) pendingFocusRef.current = {
|
|
13823
|
-
path,
|
|
14030
|
+
const normalized = addTabNorm(path, true);
|
|
14031
|
+
if (focusDiff && normalized) pendingFocusRef.current = {
|
|
14032
|
+
path: normalized,
|
|
13824
14033
|
region: null
|
|
13825
14034
|
};
|
|
13826
14035
|
};
|
|
@@ -13837,9 +14046,10 @@ window.__ModuleLoader__.load({
|
|
|
13837
14046
|
const openFileAt = (path, line, column, endLine, endColumn) => {
|
|
13838
14047
|
if (!path) return;
|
|
13839
14048
|
recordNav();
|
|
13840
|
-
|
|
14049
|
+
const normalized = addTabNorm(path, true);
|
|
14050
|
+
if (!normalized) return;
|
|
13841
14051
|
pendingFocusRef.current = {
|
|
13842
|
-
path,
|
|
14052
|
+
path: normalized,
|
|
13843
14053
|
region: null,
|
|
13844
14054
|
line: line ?? null,
|
|
13845
14055
|
column: column ?? 1,
|
|
@@ -14075,7 +14285,7 @@ window.__ModuleLoader__.load({
|
|
|
14075
14285
|
onClick: keepLocal
|
|
14076
14286
|
}, "保留本地"));
|
|
14077
14287
|
};
|
|
14078
|
-
const otherFiles = sum.pendingFiles.filter((f) => f.path
|
|
14288
|
+
const otherFiles = sum.pendingFiles.filter((f) => !sameFile(f.path, active));
|
|
14079
14289
|
/**
|
|
14080
14290
|
* 渲染编辑器/文件加载进度面板。
|
|
14081
14291
|
* @author ddj 2026年08月22号
|
|
@@ -14995,6 +15205,110 @@ window.__ModuleLoader__.load({
|
|
|
14995
15205
|
/** file 资源类型的 kind(与页类型 edrvEditor 区分:同 kind 只允许一份 extension)。 */
|
|
14996
15206
|
const OFFICIAL_FILE_KIND = "edrvEditorFile";
|
|
14997
15207
|
/**
|
|
15208
|
+
* Office 文档后缀:官方 `dsh-client-ui-sidebar-documentpreview` 的 Office 渲染器
|
|
15209
|
+
* 声明 `doc/docx/xls/xlsx/ppt/pptx`,并在 alpha.2 起提供侧栏 Office 预览。
|
|
15210
|
+
* 本插件让位官方(不认领),否则会把这些文件路由进 Monaco 而丢掉 Office 预览。
|
|
15211
|
+
*/
|
|
15212
|
+
const OFFICE_EXT = [
|
|
15213
|
+
"doc",
|
|
15214
|
+
"docx",
|
|
15215
|
+
"xls",
|
|
15216
|
+
"xlsx",
|
|
15217
|
+
"ppt",
|
|
15218
|
+
"pptx",
|
|
15219
|
+
"odt",
|
|
15220
|
+
"ods",
|
|
15221
|
+
"odp",
|
|
15222
|
+
"pages",
|
|
15223
|
+
"numbers"
|
|
15224
|
+
];
|
|
15225
|
+
/**
|
|
15226
|
+
* 官方「不可预览二进制容器」清单(`UNVIEWABLE_BINARY_EXTENSIONS`,逐项取自
|
|
15227
|
+
* `dsh-client-ui-sidebar-documentpreview/lib/client.js` 的 `UNVIEWABLE_BINARY_EXTENSIONS`)。
|
|
15228
|
+
* 这些后缀官方会给出「无法预览」提示;本插件让位官方,避免把二进制当文本读成乱码。
|
|
15229
|
+
*
|
|
15230
|
+
* ⚠️ 刻意例外:官方清单含 `avif`,但本插件 {@link IMAGE_MIME} 支持 avif 图片预览
|
|
15231
|
+
* (浏览器原生解码),故从本表**移除** `avif` —— 保留本插件的图片预览优于官方的「不可预览」。
|
|
15232
|
+
* @author ddj 2026年09月18号
|
|
15233
|
+
*/
|
|
15234
|
+
const BLIND_EXT = [
|
|
15235
|
+
"mp4",
|
|
15236
|
+
"mov",
|
|
15237
|
+
"avi",
|
|
15238
|
+
"mkv",
|
|
15239
|
+
"webm",
|
|
15240
|
+
"flv",
|
|
15241
|
+
"wmv",
|
|
15242
|
+
"m4v",
|
|
15243
|
+
"mp3",
|
|
15244
|
+
"wav",
|
|
15245
|
+
"flac",
|
|
15246
|
+
"ogg",
|
|
15247
|
+
"m4a",
|
|
15248
|
+
"aac",
|
|
15249
|
+
"wma",
|
|
15250
|
+
"opus",
|
|
15251
|
+
"zip",
|
|
15252
|
+
"gz",
|
|
15253
|
+
"tgz",
|
|
15254
|
+
"bz2",
|
|
15255
|
+
"xz",
|
|
15256
|
+
"zst",
|
|
15257
|
+
"7z",
|
|
15258
|
+
"rar",
|
|
15259
|
+
"tar",
|
|
15260
|
+
"jar",
|
|
15261
|
+
"exe",
|
|
15262
|
+
"dll",
|
|
15263
|
+
"so",
|
|
15264
|
+
"dylib",
|
|
15265
|
+
"bin",
|
|
15266
|
+
"o",
|
|
15267
|
+
"class",
|
|
15268
|
+
"pyc",
|
|
15269
|
+
"wasm",
|
|
15270
|
+
"ttf",
|
|
15271
|
+
"otf",
|
|
15272
|
+
"woff",
|
|
15273
|
+
"woff2",
|
|
15274
|
+
"eot",
|
|
15275
|
+
"dmg",
|
|
15276
|
+
"iso",
|
|
15277
|
+
"img",
|
|
15278
|
+
"sqlite",
|
|
15279
|
+
"db",
|
|
15280
|
+
"psd",
|
|
15281
|
+
"ai",
|
|
15282
|
+
"sketch",
|
|
15283
|
+
"tiff",
|
|
15284
|
+
"tif",
|
|
15285
|
+
"heic",
|
|
15286
|
+
"heif"
|
|
15287
|
+
];
|
|
15288
|
+
/** 让位判定用后缀集合(模块级构建一次;小写、无前导点)。 */
|
|
15289
|
+
const DEFER_EXT_SET = /* @__PURE__ */ new Set([...OFFICE_EXT, ...BLIND_EXT]);
|
|
15290
|
+
/**
|
|
15291
|
+
* 取 path 的 basename 后缀(小写、无点);无后缀/隐藏文件返回 ''。
|
|
15292
|
+
* @author ddj 2026年09月18号
|
|
15293
|
+
* @param path 文件路径(`/` 或 `\` 分隔均可)
|
|
15294
|
+
* @returns 小写后缀或 ''
|
|
15295
|
+
*/
|
|
15296
|
+
function suffixOf(path) {
|
|
15297
|
+
const base = String(path ?? "").replace(/\\/g, "/").split("/").pop() ?? "";
|
|
15298
|
+
const dot = base.lastIndexOf(".");
|
|
15299
|
+
return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
|
|
15300
|
+
}
|
|
15301
|
+
/**
|
|
15302
|
+
* 该路径是否应让位官方查看器(Office 文档 + 官方不可预览的二进制容器)。
|
|
15303
|
+
* 命中即不认领 `dsh-resource://file/**`,回落官方预览/提示。
|
|
15304
|
+
* @author ddj 2026年09月18号
|
|
15305
|
+
* @param path 文件路径(工作区相对或绝对)
|
|
15306
|
+
* @returns 是否让位官方
|
|
15307
|
+
*/
|
|
15308
|
+
function deferToOfficial(path) {
|
|
15309
|
+
return DEFER_EXT_SET.has(suffixOf(path));
|
|
15310
|
+
}
|
|
15311
|
+
/**
|
|
14998
15312
|
* 结构化探测官方右侧 Sidebar 服务(可选依赖:缺失/降级返回 undefined)。
|
|
14999
15313
|
* 服务仅存在于 DSH 0.1.5-alpha.1+,探测命中 ≡ 版本判定成立。
|
|
15000
15314
|
* @author ddj 2026年09月09号
|
|
@@ -15208,7 +15522,10 @@ window.__ModuleLoader__.load({
|
|
|
15208
15522
|
* 注册官方 file 资源地址认领(extension 档接管 `dsh-resource://file/**`,
|
|
15209
15523
|
* 聊天文件链接/文件树打开改落本插件编辑器;注销后官方 textpreview 自动恢复)。
|
|
15210
15524
|
* 认领与否由「文件链接使用工具」设置驱动(见 fileOpeners.shouldClaimFiles)。
|
|
15211
|
-
*
|
|
15525
|
+
*
|
|
15526
|
+
* 让位规则(DSH 0.1.6-alpha.2 起):Office 文档与官方「不可预览」二进制容器不认领,
|
|
15527
|
+
* 回落官方查看器(Office 侧栏预览 / 「无法预览」提示),见 {@link deferToOfficial}。
|
|
15528
|
+
* @author ddj 2026年09月09号 / 2026年09月18号
|
|
15212
15529
|
* @param options 装配参数(探测命中的注册表 + slots + 正文组件)
|
|
15213
15530
|
* @returns 卸载器(反注册类型与正文);类型注册抛错返回 null(回落官方查看器)
|
|
15214
15531
|
*/
|
|
@@ -15221,7 +15538,11 @@ window.__ModuleLoader__.load({
|
|
|
15221
15538
|
kind: OFFICIAL_FILE_KIND,
|
|
15222
15539
|
patterns: [OFFICIAL_FILE_PATTERN],
|
|
15223
15540
|
priority: "extension",
|
|
15224
|
-
canOpen: (address) =>
|
|
15541
|
+
canOpen: (address) => {
|
|
15542
|
+
const parsed = parseOfficialFileAddress(address);
|
|
15543
|
+
if (parsed === null) return false;
|
|
15544
|
+
return !deferToOfficial(parsed.path);
|
|
15545
|
+
},
|
|
15225
15546
|
title: (address) => officialFileTitle(address)
|
|
15226
15547
|
});
|
|
15227
15548
|
} catch (error) {
|
|
@@ -16793,7 +17114,8 @@ window.__ModuleLoader__.load({
|
|
|
16793
17114
|
};
|
|
16794
17115
|
if (!cfg) return react.default.createElement("div", { className: "vsm-mcp-empty" }, error || "正在读取 AI 补全配置…");
|
|
16795
17116
|
const modelValue = cfg.provider && cfg.model ? cfg.provider + "/" + cfg.model : "";
|
|
16796
|
-
|
|
17117
|
+
const delisted = isDelistedModel(cfg.model);
|
|
17118
|
+
return react.default.createElement("section", { className: "vsm-lsp-card" }, react.default.createElement("h3", null, "AI 自动补全(实验)"), react.default.createElement("p", { className: "vsm-lsp-note" }, "编辑停顿后由模型生成内联建议(ghost text),Tab 接受,Alt+\\ 手动触发。每次补全是一次模型调用;建议选择非推理模型,且思考强度选「跟随默认」或最低档以获得更快响应。"), delisted ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "当前模型「" + cfg.model + "」已从 DSH 默认模型列表移除(0.1.6-alpha.2 起)。该模型可能不再可用;将「模型」改回「自动」即可使用第一个可用模型(现有配置不会被自动改动)。") : null, react.default.createElement("div", { className: "vsm-lsp-row" }, react.default.createElement("button", {
|
|
16797
17119
|
className: cfg.enabled ? "vsm-primary" : "",
|
|
16798
17120
|
disabled: busy,
|
|
16799
17121
|
onClick: toggle
|
|
@@ -21476,6 +21798,137 @@ window.__ModuleLoader__.load({
|
|
|
21476
21798
|
};
|
|
21477
21799
|
}
|
|
21478
21800
|
//#endregion
|
|
21801
|
+
//#region src/client/sessionScope.ts
|
|
21802
|
+
/** 对象窄化(非对象返回 undefined,供后续安全取字段)。 */
|
|
21803
|
+
function asRecord(value) {
|
|
21804
|
+
return value && typeof value === "object" ? value : void 0;
|
|
21805
|
+
}
|
|
21806
|
+
/** 非空字符串取值(其他类型一律视为缺失)。 */
|
|
21807
|
+
function strOf(value) {
|
|
21808
|
+
return typeof value === "string" && value ? value : void 0;
|
|
21809
|
+
}
|
|
21810
|
+
/**
|
|
21811
|
+
* 从 `uiSession.current` 快照读出 sessionId。
|
|
21812
|
+
* 兼容两种形态:绑定源本身(`{ key }`)与其包裹形态(`{ value: { key } }`)——
|
|
21813
|
+
* 官方 `createBindingSource` 的 `getSnapshot()` 返回绑定值本身,但跨版本包裹层存在差异,
|
|
21814
|
+
* 故两者都尝试。
|
|
21815
|
+
* @author ddj 2026年09月18号
|
|
21816
|
+
* @param snapshot `uiSession.current` 的 `getSnapshot()` 结果(任意形状)
|
|
21817
|
+
* @returns sessionId,或 undefined
|
|
21818
|
+
*/
|
|
21819
|
+
function uiSessionIdOf(snapshot) {
|
|
21820
|
+
const binding = asRecord(snapshot);
|
|
21821
|
+
if (!binding) return void 0;
|
|
21822
|
+
return strOf(binding.key) ?? strOf(asRecord(binding.value)?.key);
|
|
21823
|
+
}
|
|
21824
|
+
/**
|
|
21825
|
+
* 从 `sessions.list` 快照读出 sessionId。
|
|
21826
|
+
* 先取旧版 `current`;缺失时回退 `byId` 中 `retainedBy.mainView > 0` 的首行
|
|
21827
|
+
* (与官方 ui-session 的 publishMain 同判据,故新版行为与官方视图一致)。
|
|
21828
|
+
* @author ddj 2026年09月18号
|
|
21829
|
+
* @param list `sessions.list` 的 `getSnapshot()` 结果(任意形状)
|
|
21830
|
+
* @returns sessionId,或 undefined
|
|
21831
|
+
*/
|
|
21832
|
+
function listSessionId(list) {
|
|
21833
|
+
const snap = asRecord(list);
|
|
21834
|
+
if (!snap) return void 0;
|
|
21835
|
+
const current = strOf(snap.current);
|
|
21836
|
+
if (current) return current;
|
|
21837
|
+
const byId = asRecord(snap.byId);
|
|
21838
|
+
if (!byId) return void 0;
|
|
21839
|
+
for (const [id, row] of Object.entries(byId)) {
|
|
21840
|
+
const mainView = asRecord(asRecord(row)?.retainedBy)?.mainView;
|
|
21841
|
+
if (typeof mainView === "number" && mainView > 0) return id;
|
|
21842
|
+
}
|
|
21843
|
+
}
|
|
21844
|
+
/**
|
|
21845
|
+
* 三级取值链(纯函数):决定当前会话 id 与其 cwd。
|
|
21846
|
+
* 优先级 uiSession > list.current > list.byId.retainedBy > fallback。
|
|
21847
|
+
* @author ddj 2026年09月18号
|
|
21848
|
+
* @param sources 候选来源集合
|
|
21849
|
+
* @returns 会话作用域(字段缺失即省略该键,不写 undefined 占位)
|
|
21850
|
+
*/
|
|
21851
|
+
function pickSessionScope(sources) {
|
|
21852
|
+
const sessionId = uiSessionIdOf(sources.uiSession) ?? listSessionId(sources.list) ?? strOf(sources.fallbackSessionId);
|
|
21853
|
+
if (!sessionId) return {};
|
|
21854
|
+
const cwd = strOf(asRecord(asRecord(asRecord(sources.list)?.byId)?.[sessionId])?.cwd);
|
|
21855
|
+
return cwd ? {
|
|
21856
|
+
sessionId,
|
|
21857
|
+
cwd
|
|
21858
|
+
} : { sessionId };
|
|
21859
|
+
}
|
|
21860
|
+
/** 读 `ctx.get(name)`(缺失/异常返回 undefined,不抛错)。 */
|
|
21861
|
+
function safeGet(ctx, name) {
|
|
21862
|
+
try {
|
|
21863
|
+
return ctx.get(name);
|
|
21864
|
+
} catch {
|
|
21865
|
+
return;
|
|
21866
|
+
}
|
|
21867
|
+
}
|
|
21868
|
+
/** 取 `uiSession.current` 快照(服务或订阅面缺失时返回 undefined)。 */
|
|
21869
|
+
function uiCurrentSnapshot(ctx) {
|
|
21870
|
+
const current = asRecord(asRecord(safeGet(ctx, "uiSession"))?.current);
|
|
21871
|
+
const getSnapshot = current?.getSnapshot;
|
|
21872
|
+
if (typeof getSnapshot !== "function") return void 0;
|
|
21873
|
+
try {
|
|
21874
|
+
return getSnapshot.call(current);
|
|
21875
|
+
} catch {
|
|
21876
|
+
return;
|
|
21877
|
+
}
|
|
21878
|
+
}
|
|
21879
|
+
/** 取 `sessions.list` 快照(服务缺失时返回 undefined)。 */
|
|
21880
|
+
function listSnapshot(ctx) {
|
|
21881
|
+
const list = asRecord(asRecord(safeGet(ctx, "sessions"))?.list);
|
|
21882
|
+
const getSnapshot = list?.getSnapshot;
|
|
21883
|
+
if (typeof getSnapshot !== "function") return void 0;
|
|
21884
|
+
try {
|
|
21885
|
+
return getSnapshot.call(list);
|
|
21886
|
+
} catch {
|
|
21887
|
+
return;
|
|
21888
|
+
}
|
|
21889
|
+
}
|
|
21890
|
+
/**
|
|
21891
|
+
* 读当前会话作用域(服务面取值 + 三级链)。
|
|
21892
|
+
* @author ddj 2026年09月18号
|
|
21893
|
+
* @param ctx 客户端根上下文(只需 `get`)
|
|
21894
|
+
* @param fallbackSessionId 槽位注入的 sessionId(可选)
|
|
21895
|
+
* @returns 会话作用域
|
|
21896
|
+
*/
|
|
21897
|
+
function readSessionScope(ctx, fallbackSessionId) {
|
|
21898
|
+
return pickSessionScope({
|
|
21899
|
+
uiSession: uiCurrentSnapshot(ctx),
|
|
21900
|
+
list: listSnapshot(ctx),
|
|
21901
|
+
fallbackSessionId
|
|
21902
|
+
});
|
|
21903
|
+
}
|
|
21904
|
+
/**
|
|
21905
|
+
* 订阅会话切换(同时挂 `uiSession.current` 与新/旧 `sessions.list`)。
|
|
21906
|
+
* 旧版无 `uiSession`,新版 `list` 不再变(切会话只动 uiSession),故两条都要挂;
|
|
21907
|
+
* 回调可能因两条源先后触发而重复,调用方按上一次 sessionId 自行去重。
|
|
21908
|
+
* @author ddj 2026年09月18号
|
|
21909
|
+
* @param ctx 客户端根上下文(只需 `get`)
|
|
21910
|
+
* @param listener 变更回调
|
|
21911
|
+
* @returns 取消订阅函数(幂等)
|
|
21912
|
+
*/
|
|
21913
|
+
function subscribeScope(ctx, listener) {
|
|
21914
|
+
const disposers = [];
|
|
21915
|
+
const attach = (owner) => {
|
|
21916
|
+
const subscribe = asRecord(owner)?.subscribe;
|
|
21917
|
+
if (typeof subscribe !== "function") return;
|
|
21918
|
+
try {
|
|
21919
|
+
const disposer = subscribe.call(owner, listener);
|
|
21920
|
+
if (typeof disposer === "function") disposers.push(disposer);
|
|
21921
|
+
} catch {}
|
|
21922
|
+
};
|
|
21923
|
+
attach(asRecord(asRecord(safeGet(ctx, "uiSession"))?.current));
|
|
21924
|
+
attach(asRecord(asRecord(safeGet(ctx, "sessions"))?.list));
|
|
21925
|
+
return () => {
|
|
21926
|
+
for (const disposer of disposers.splice(0)) try {
|
|
21927
|
+
disposer();
|
|
21928
|
+
} catch {}
|
|
21929
|
+
};
|
|
21930
|
+
}
|
|
21931
|
+
//#endregion
|
|
21479
21932
|
//#region src/client/index.ts
|
|
21480
21933
|
/**
|
|
21481
21934
|
* dsh-vscode-mode client — 浏览器半入口:slot 注册 + 装配。
|
|
@@ -21539,46 +21992,37 @@ window.__ModuleLoader__.load({
|
|
|
21539
21992
|
const workspaces = ctx.get("workspaces");
|
|
21540
21993
|
const sessions = ctx.get("sessions");
|
|
21541
21994
|
setupExtOpen(ctx);
|
|
21542
|
-
|
|
21543
|
-
if (typeof window !== "undefined" && monacoList && typeof monacoList.subscribe === "function") {
|
|
21544
|
-
const list = monacoList;
|
|
21995
|
+
if (typeof window !== "undefined") {
|
|
21545
21996
|
const schedulePreload = () => {
|
|
21546
21997
|
const preload = () => loadMonaco(() => {}).then((m) => setupLsp(m)).catch(() => {});
|
|
21547
21998
|
if (typeof window.requestIdleCallback === "function") window.requestIdleCallback(preload, { timeout: 2e3 });
|
|
21548
21999
|
else schedule(preload, 300);
|
|
21549
22000
|
};
|
|
21550
|
-
|
|
21551
|
-
|
|
21552
|
-
|
|
21553
|
-
setSession(
|
|
22001
|
+
let preloadedFor;
|
|
22002
|
+
const onScopeChange = () => {
|
|
22003
|
+
const scope = readSessionScope(ctx);
|
|
22004
|
+
setSession(scope.sessionId);
|
|
22005
|
+
if (!scope.sessionId || scope.sessionId === preloadedFor) return;
|
|
22006
|
+
preloadedFor = scope.sessionId;
|
|
21554
22007
|
schedulePreload();
|
|
21555
22008
|
};
|
|
21556
|
-
ctx.effect(() =>
|
|
21557
|
-
|
|
21558
|
-
}
|
|
21559
|
-
if (monacoList && typeof monacoList.subscribe === "function") {
|
|
21560
|
-
const list = monacoList;
|
|
21561
|
-
ctx.effect(() => list.subscribe(() => {
|
|
21562
|
-
setSession(list.getSnapshot()?.current);
|
|
21563
|
-
}), "vscode-mode: lsp session sync");
|
|
21564
|
-
}
|
|
21565
|
-
if (monacoList && typeof monacoList.subscribe === "function") {
|
|
21566
|
-
const list = monacoList;
|
|
21567
|
-
ctx.effect(() => {
|
|
21568
|
-
let lastCurrent = list.getSnapshot?.()?.current;
|
|
21569
|
-
const check = () => {
|
|
21570
|
-
const current = list.getSnapshot?.()?.current;
|
|
21571
|
-
if (!current || current === lastCurrent) return;
|
|
21572
|
-
lastCurrent = current;
|
|
21573
|
-
if (!isEditorTabActive() || officialService === void 0) return;
|
|
21574
|
-
try {
|
|
21575
|
-
officialService.service.openTabIn?.(current, OFFICIAL_TAB_KIND, {});
|
|
21576
|
-
} catch {}
|
|
21577
|
-
restoreEditorTab(officialService.service, schedule);
|
|
21578
|
-
};
|
|
21579
|
-
return list.subscribe(() => schedule(check, 0));
|
|
21580
|
-
}, "vscode-mode: editor tab restore");
|
|
22009
|
+
ctx.effect(() => subscribeScope(ctx, onScopeChange), "vscode-mode: monaco session trigger");
|
|
22010
|
+
onScopeChange();
|
|
21581
22011
|
}
|
|
22012
|
+
ctx.effect(() => {
|
|
22013
|
+
let lastCurrent = readSessionScope(ctx).sessionId;
|
|
22014
|
+
const check = () => {
|
|
22015
|
+
const current = readSessionScope(ctx).sessionId;
|
|
22016
|
+
if (!current || current === lastCurrent) return;
|
|
22017
|
+
lastCurrent = current;
|
|
22018
|
+
if (!isEditorTabActive() || officialService === void 0) return;
|
|
22019
|
+
try {
|
|
22020
|
+
officialService.service.openTabIn?.(current, OFFICIAL_TAB_KIND, {});
|
|
22021
|
+
} catch {}
|
|
22022
|
+
restoreEditorTab(officialService.service, schedule);
|
|
22023
|
+
};
|
|
22024
|
+
return subscribeScope(ctx, () => schedule(check, 0));
|
|
22025
|
+
}, "vscode-mode: editor tab restore");
|
|
21582
22026
|
const originalOpenPath = workspaces?.openPath;
|
|
21583
22027
|
const binder = pickSettingsBinder(ctx);
|
|
21584
22028
|
const settings = binder.scope;
|
|
@@ -21587,15 +22031,8 @@ window.__ModuleLoader__.load({
|
|
|
21587
22031
|
let remoteOpenInstalled = false;
|
|
21588
22032
|
/** 两条文件链接路由共用的路由日志(openPathRouter / remoteOpenRouter;统一走插件日志器)。 */
|
|
21589
22033
|
const routeLogger = (message) => log.warn(message);
|
|
21590
|
-
/** 两条路由共用的 FileOpenContext:当前会话 id 与工作区 cwd
|
|
21591
|
-
const openContext = () =>
|
|
21592
|
-
const current = sessions?.list?.getSnapshot?.();
|
|
21593
|
-
const sessionId = current?.current;
|
|
21594
|
-
return {
|
|
21595
|
-
sessionId,
|
|
21596
|
-
cwd: (sessionId ? current?.byId?.[sessionId] : void 0)?.cwd
|
|
21597
|
-
};
|
|
21598
|
-
};
|
|
22034
|
+
/** 两条路由共用的 FileOpenContext:当前会话 id 与工作区 cwd(跨版本取值链)。 */
|
|
22035
|
+
const openContext = () => readSessionScope(ctx);
|
|
21599
22036
|
/** 可选探测 betterSidebar 服务(不进 inject:缺失会让插件停靠等待,杀死回退路径)。 */
|
|
21600
22037
|
let sideService = detectSidebarService(ctx);
|
|
21601
22038
|
/** 可选探测官方右侧 Sidebar(DSH 0.1.5-alpha.1+;探测命中 ≡ 版本判定,不进 inject 同上)。 */
|
|
@@ -21849,12 +22286,11 @@ window.__ModuleLoader__.load({
|
|
|
21849
22286
|
sessions
|
|
21850
22287
|
})),
|
|
21851
22288
|
activeSession: () => {
|
|
21852
|
-
const
|
|
21853
|
-
|
|
21854
|
-
if (!sessionId) return void 0;
|
|
22289
|
+
const scope = readSessionScope(ctx);
|
|
22290
|
+
if (!scope.sessionId) return void 0;
|
|
21855
22291
|
return {
|
|
21856
|
-
sessionId,
|
|
21857
|
-
cwd:
|
|
22292
|
+
sessionId: scope.sessionId,
|
|
22293
|
+
cwd: scope.cwd
|
|
21858
22294
|
};
|
|
21859
22295
|
},
|
|
21860
22296
|
registerLegacyFallback: registerLegacyTab
|
|
@@ -21925,6 +22361,17 @@ window.__ModuleLoader__.load({
|
|
|
21925
22361
|
openerRegistry: registry,
|
|
21926
22362
|
compatSummary
|
|
21927
22363
|
})));
|
|
22364
|
+
ctx.effect(() => () => {
|
|
22365
|
+
try {
|
|
22366
|
+
disposeSnippets();
|
|
22367
|
+
} catch {}
|
|
22368
|
+
try {
|
|
22369
|
+
disposeAiInline();
|
|
22370
|
+
} catch {}
|
|
22371
|
+
try {
|
|
22372
|
+
disposeLsp();
|
|
22373
|
+
} catch {}
|
|
22374
|
+
}, "vscode-mode: monaco providers teardown");
|
|
21928
22375
|
}
|
|
21929
22376
|
//#endregion
|
|
21930
22377
|
exports.apply = apply;
|