dsh-vscode-mode 0.15.4 → 0.15.5
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 +5 -3
- package/lib/client.js +261 -99
- package/lib/client.js.map +1 -1
- package/lib/index.js +2 -1
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/compatSummary.ts +77 -0
- package/src/client/externalOpen.ts +21 -3
- package/src/client/index.ts +14 -10
- package/src/client/monaco/lsp/lspClient.ts +26 -5
- package/src/client/monaco/lsp/providers.ts +3 -1
- package/src/client/ui/EditorView.ts +2 -2
- package/src/client/ui/FloatingWindow.ts +61 -14
- package/src/client/ui/McpSettings.ts +1 -1
- package/src/client/ui/SvnLogDialog.ts +67 -24
- package/src/svn.ts +8 -1
package/README.md
CHANGED
|
@@ -465,13 +465,13 @@ profile 请替换为实际目标,不要把示例目录当成当前运行 profi
|
|
|
465
465
|
|
|
466
466
|
```bash
|
|
467
467
|
# ① Git 安装(clone + prepare 构建;推荐打固定 tag)
|
|
468
|
-
dsh plugin --profile web add github:Lenonss/DSH_VsCodeMode#v0.15.
|
|
468
|
+
dsh plugin --profile web add github:Lenonss/DSH_VsCodeMode#v0.15.5
|
|
469
469
|
|
|
470
470
|
# ② npm 注册表(发布到 npm 后)
|
|
471
|
-
dsh plugin --profile web add dsh-vscode-mode@0.15.
|
|
471
|
+
dsh plugin --profile web add dsh-vscode-mode@0.15.5
|
|
472
472
|
|
|
473
473
|
# ③ GitHub Release tgz 直装(Release asset 可用后)
|
|
474
|
-
dsh plugin --profile web add https://github.com/Lenonss/DSH_VsCodeMode/releases/download/v0.15.
|
|
474
|
+
dsh plugin --profile web add https://github.com/Lenonss/DSH_VsCodeMode/releases/download/v0.15.5/dsh-vscode-mode-0.15.5.tgz
|
|
475
475
|
```
|
|
476
476
|
|
|
477
477
|
> **DSH 版本政策(0.14.10 起)**:六项 DSH peer 统一声明为 `*`,不再维护逐版
|
|
@@ -809,6 +809,8 @@ Keep All / Undo All 却是亮的。修复:单文件 Keep / Undo 覆盖冲突
|
|
|
809
809
|
完整变更见 [GitHub Releases](https://github.com/Lenonss/DSH_VsCodeMode/releases)。
|
|
810
810
|
近期关键版本:
|
|
811
811
|
|
|
812
|
+
- **v0.15.5**:**浮窗性能与客户端适配**——拖动/缩放改为逐帧直写几何、结束单次提交,多窗口共享视口监听;SVN 日志行表 memo 与选中集优化,冲突状态读取按批 4 并发。LSP 去抖发送前惰性读取全文,隐藏页面心跳目标周期降为 15 秒;兼容性摘要按版本、安装事实与用户选择显示适用诊断,补充搜索刷新回归。保留 0.15.4 全部功能;本次真实 GUI 未重测,用户已接受此发布缺口,未安装/重启当前实例,详见 [0.15.5 发布说明](docs/release-0.15.5.md)。
|
|
813
|
+
|
|
812
814
|
- **v0.15.4**:**SVN 非模态浮窗与独立文件对比**——更新、日志、统计、补丁、目录对比及智能整理采用与文件编辑相同的页面内浮窗,支持移动、调整尺寸、点击置顶与后台 DSH 交互。文件对比不再占据主编辑区,保留二进制/编码提示和来源任务返回逻辑;确认类弹窗保持模态。真实 GUI 交互尚未验收,用户已接受此发布缺口;资源管理器“目录不存在”原因未定位,本版不宣称修复该提示。
|
|
813
815
|
|
|
814
816
|
- **v0.15.3**:**搜索结果强制刷新**——目标文件内容被改动后,工作区搜索不再残留过期匹配:内容变更(编辑器保存、外部改动或结构变更)会自动重新执行当前查询,同关键词重搜不再命中 60 秒结果缓存(`edrv.searchContent` 新增 `force` 选项,仅本次调用跳过缓存读取、仍写入新结果)。更新后需重启 DSH Host;真实 GUI 现场重测尚未执行,发布不代表现场验收完成,详见 [0.15.3 发布说明](docs/release-0.15.3.md)。
|
package/lib/client.js
CHANGED
|
@@ -1258,32 +1258,38 @@ window.__ModuleLoader__.load({
|
|
|
1258
1258
|
//#endregion
|
|
1259
1259
|
//#region src/client/ui/FloatingWindow.ts
|
|
1260
1260
|
/** Shared in-page window chrome, without a mask or focus trap. @author ddj 2026年10月14号 */
|
|
1261
|
-
/** @private @author ddj 2026年10月14号 @param start Originating press. @param rect Initial bounds. @param sizing Resize mode. @param apply Commit bounds. @param done Gesture cleanup notification. @returns Gesture cancellation. */
|
|
1262
|
-
function beginGesture(start, rect, sizing, apply, done) {
|
|
1261
|
+
/** @private @author ddj 2026年10月14号 @param start Originating press. @param rect Initial bounds. @param sizing Resize mode. @param draw Paint live bounds without a render. @param apply Commit bounds once. @param done Gesture cleanup notification. @returns Gesture cancellation. */
|
|
1262
|
+
function beginGesture(start, rect, sizing, draw, apply, done) {
|
|
1263
1263
|
let frame = null;
|
|
1264
1264
|
let next = rect;
|
|
1265
|
-
|
|
1265
|
+
let moved = false;
|
|
1266
|
+
/** @private @author ddj 2026年10月14号 Paint no more than once per frame. */
|
|
1266
1267
|
function paint() {
|
|
1267
1268
|
frame = null;
|
|
1268
|
-
|
|
1269
|
+
draw(next);
|
|
1269
1270
|
}
|
|
1270
1271
|
/** @private @author ddj 2026年10月14号 @param event Pointer motion. Ignore unrelated pointers. */
|
|
1271
1272
|
function move(event) {
|
|
1272
1273
|
if (event.pointerId !== start.pointerId) return;
|
|
1273
1274
|
event.preventDefault();
|
|
1275
|
+
moved = true;
|
|
1274
1276
|
next = moveFloat(rect, event.clientX - start.clientX, event.clientY - start.clientY, sizing, window.innerWidth, window.innerHeight);
|
|
1275
1277
|
if (frame === null) frame = requestAnimationFrame(paint);
|
|
1276
1278
|
}
|
|
1277
|
-
/** @private @author ddj 2026年10月14号 Remove listeners
|
|
1279
|
+
/** @private @author ddj 2026年10月14号 Remove listeners, then commit the settled geometry exactly once. */
|
|
1278
1280
|
function stop() {
|
|
1279
1281
|
if (frame !== null) {
|
|
1280
1282
|
cancelAnimationFrame(frame);
|
|
1281
|
-
|
|
1283
|
+
frame = null;
|
|
1282
1284
|
}
|
|
1283
1285
|
window.removeEventListener("pointermove", move, true);
|
|
1284
1286
|
window.removeEventListener("pointerup", end, true);
|
|
1285
1287
|
window.removeEventListener("pointercancel", end, true);
|
|
1286
1288
|
window.removeEventListener("blur", stop);
|
|
1289
|
+
if (moved) {
|
|
1290
|
+
draw(next);
|
|
1291
|
+
apply(next);
|
|
1292
|
+
}
|
|
1287
1293
|
done();
|
|
1288
1294
|
}
|
|
1289
1295
|
/** @private @author ddj 2026年10月14号 @param event Pointer release. Only end originating gesture. */
|
|
@@ -1296,6 +1302,31 @@ window.__ModuleLoader__.load({
|
|
|
1296
1302
|
window.addEventListener("blur", stop);
|
|
1297
1303
|
return stop;
|
|
1298
1304
|
}
|
|
1305
|
+
const resizeSinks = /* @__PURE__ */ new Set();
|
|
1306
|
+
let resizeFrame = null;
|
|
1307
|
+
/** @private @author ddj 2026年10月14号 Re-clamp every window at most once per frame. */
|
|
1308
|
+
function onViewportResize() {
|
|
1309
|
+
if (resizeFrame !== null) return;
|
|
1310
|
+
resizeFrame = requestAnimationFrame(() => {
|
|
1311
|
+
resizeFrame = null;
|
|
1312
|
+
for (const sink of [...resizeSinks]) sink();
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
/** @private @author ddj 2026年10月14号 @param sink Per-window re-clamp callback. @returns Unsubscribe. */
|
|
1316
|
+
function subscribeResize(sink) {
|
|
1317
|
+
if (resizeSinks.size === 0) window.addEventListener("resize", onViewportResize);
|
|
1318
|
+
resizeSinks.add(sink);
|
|
1319
|
+
return () => {
|
|
1320
|
+
resizeSinks.delete(sink);
|
|
1321
|
+
if (resizeSinks.size === 0) {
|
|
1322
|
+
window.removeEventListener("resize", onViewportResize);
|
|
1323
|
+
if (resizeFrame !== null) {
|
|
1324
|
+
cancelAnimationFrame(resizeFrame);
|
|
1325
|
+
resizeFrame = null;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1299
1330
|
/** @public @author ddj 2026年10月14号 @param props Window contents and lifecycle callbacks. @returns Movable, resizable nonmodal portal. */
|
|
1300
1331
|
function FloatingWindow(props) {
|
|
1301
1332
|
const id = react.default.useId();
|
|
@@ -1306,29 +1337,40 @@ window.__ModuleLoader__.load({
|
|
|
1306
1337
|
const current = react.default.useRef(props.rect ?? bounds);
|
|
1307
1338
|
current.current = props.rect ?? bounds;
|
|
1308
1339
|
const cleanup = react.default.useRef(null);
|
|
1340
|
+
const gesture = react.default.useRef(null);
|
|
1309
1341
|
react.default.useSyncExternalStore(watchFloats, floatVersion, floatVersion);
|
|
1310
1342
|
/** @private @author ddj 2026年10月14号 @param rect Next geometry. Notify controlled owner or retain local geometry. */
|
|
1311
1343
|
function apply(rect) {
|
|
1312
1344
|
if (latest.current.rect) latest.current.onBounds?.(rect);
|
|
1313
1345
|
else setBounds(rect);
|
|
1314
1346
|
}
|
|
1347
|
+
/** @private @author ddj 2026年10月14号 @param rect Live gesture geometry. Write the DOM and hold it until the commit render lands. */
|
|
1348
|
+
function draw(rect) {
|
|
1349
|
+
gesture.current = rect;
|
|
1350
|
+
const node = root.current;
|
|
1351
|
+
if (!node) return;
|
|
1352
|
+
node.style.left = rect.left + "px";
|
|
1353
|
+
node.style.top = rect.top + "px";
|
|
1354
|
+
node.style.width = rect.width + "px";
|
|
1355
|
+
node.style.height = rect.height + "px";
|
|
1356
|
+
}
|
|
1315
1357
|
/** @private @author ddj 2026年10月14号 End any active gesture. */
|
|
1316
1358
|
function cancel() {
|
|
1317
1359
|
cleanup.current?.();
|
|
1318
1360
|
cleanup.current = null;
|
|
1319
1361
|
}
|
|
1320
|
-
/** @private @author ddj 2026年10月14号 Re-clamp after viewport resize. */
|
|
1362
|
+
/** @private @author ddj 2026年10月14号 Re-clamp after viewport resize, preferring the gesture that just settled. */
|
|
1321
1363
|
function resize() {
|
|
1322
1364
|
cancel();
|
|
1323
|
-
apply(clampFloat(current.current, window.innerWidth, window.innerHeight));
|
|
1365
|
+
apply(clampFloat(gesture.current ?? current.current, window.innerWidth, window.innerHeight));
|
|
1324
1366
|
}
|
|
1325
1367
|
react.default.useEffect(() => {
|
|
1326
1368
|
raiseFloat(id);
|
|
1327
|
-
|
|
1369
|
+
const off = subscribeResize(resize);
|
|
1328
1370
|
resize();
|
|
1329
1371
|
return () => {
|
|
1372
|
+
off();
|
|
1330
1373
|
cancel();
|
|
1331
|
-
window.removeEventListener("resize", resize);
|
|
1332
1374
|
dropFloat(id);
|
|
1333
1375
|
};
|
|
1334
1376
|
}, [id]);
|
|
@@ -1347,11 +1389,12 @@ window.__ModuleLoader__.load({
|
|
|
1347
1389
|
if (event.button !== 0 || !event.isPrimary) return;
|
|
1348
1390
|
cancel();
|
|
1349
1391
|
event.preventDefault();
|
|
1350
|
-
cleanup.current = beginGesture(event, current.current, sizing, apply, clear);
|
|
1392
|
+
cleanup.current = beginGesture(event, gesture.current ?? current.current, sizing, draw, apply, clear);
|
|
1351
1393
|
}
|
|
1352
|
-
/** @private @author ddj 2026年10月14号
|
|
1394
|
+
/** @private @author ddj 2026年10月14号 Release the settled geometry once React has committed it. */
|
|
1353
1395
|
function clear() {
|
|
1354
1396
|
cleanup.current = null;
|
|
1397
|
+
gesture.current = null;
|
|
1355
1398
|
}
|
|
1356
1399
|
/** @private @author ddj 2026年10月14号 @param event Title press. Preserve title action buttons. */
|
|
1357
1400
|
function moveStart(event) {
|
|
@@ -1372,7 +1415,8 @@ window.__ModuleLoader__.load({
|
|
|
1372
1415
|
}
|
|
1373
1416
|
props.onKeyDown?.(event);
|
|
1374
1417
|
}
|
|
1375
|
-
const
|
|
1418
|
+
const base = clampFloat(props.rect ?? bounds, window.innerWidth, window.innerHeight);
|
|
1419
|
+
const rect = gesture.current ?? base;
|
|
1376
1420
|
return (0, react_dom.createPortal)(react.default.createElement("div", {
|
|
1377
1421
|
ref: root,
|
|
1378
1422
|
tabIndex: -1,
|
|
@@ -13177,19 +13221,21 @@ window.__ModuleLoader__.load({
|
|
|
13177
13221
|
/**
|
|
13178
13222
|
* 立即同步某文档到 host(首次打开/查询前保证服务器已认识该文档)。
|
|
13179
13223
|
* sessionId 缺失时仍发起请求:host 对唯一活跃会话有兜底解析,弃发会白白丢失文档归属。
|
|
13180
|
-
* @author ddj 2026年08月27号 / 2026年09月11号
|
|
13224
|
+
* @author ddj 2026年08月27号 / 2026年09月11号 / 2026年10月15号
|
|
13181
13225
|
* @param path 工作区相对路径
|
|
13182
|
-
* @param text
|
|
13226
|
+
* @param text 文档全文;传函数表示**发送前才求值**(击键路径用它,避免为被去抖丢弃的中间态取全文)
|
|
13183
13227
|
* @param immediate true 立即发送,false 去抖
|
|
13184
13228
|
*/
|
|
13185
13229
|
async function syncDoc(path, text, immediate = false) {
|
|
13186
|
-
if (!path || typeof text !== "string") return;
|
|
13230
|
+
if (!path || typeof text !== "string" && typeof text !== "function") return;
|
|
13187
13231
|
if (immediate) {
|
|
13188
13232
|
clearPending(path);
|
|
13233
|
+
const value = resolveText(text);
|
|
13234
|
+
if (value === null) return;
|
|
13189
13235
|
await rpc("edrv.lsp.sync", {
|
|
13190
13236
|
sessionId: sessionId$1,
|
|
13191
13237
|
path,
|
|
13192
|
-
text,
|
|
13238
|
+
text: value,
|
|
13193
13239
|
version: nextDocVer(path)
|
|
13194
13240
|
}).catch(() => {});
|
|
13195
13241
|
return;
|
|
@@ -13197,14 +13243,32 @@ window.__ModuleLoader__.load({
|
|
|
13197
13243
|
clearPending(path);
|
|
13198
13244
|
syncTimers.set(path, setTimeout(() => {
|
|
13199
13245
|
syncTimers.delete(path);
|
|
13246
|
+
const value = resolveText(text);
|
|
13247
|
+
if (value === null) return;
|
|
13200
13248
|
rpc("edrv.lsp.sync", {
|
|
13201
13249
|
sessionId: sessionId$1,
|
|
13202
13250
|
path,
|
|
13203
|
-
text,
|
|
13251
|
+
text: value,
|
|
13204
13252
|
version: nextDocVer(path)
|
|
13205
13253
|
}).catch(() => {});
|
|
13206
13254
|
}, SYNC_DEBOUNCE_MS));
|
|
13207
13255
|
}
|
|
13256
|
+
/**
|
|
13257
|
+
* 取本次要同步的文本:字符串原样返回,函数形态在发送前求值。
|
|
13258
|
+
* 取值抛错(model 已销毁)时返回 null 表示**放弃本次同步**——不能退回空串,
|
|
13259
|
+
* 那会把 host 侧文档内容清掉,比漏一次同步严重得多(关闭路径由 closeDoc 负责)。
|
|
13260
|
+
* @author ddj 2026年10月15号
|
|
13261
|
+
* @param text 字符串或惰性取值函数
|
|
13262
|
+
* @returns 待发送文本;无法取值时为 null
|
|
13263
|
+
*/
|
|
13264
|
+
function resolveText(text) {
|
|
13265
|
+
if (typeof text === "string") return text;
|
|
13266
|
+
try {
|
|
13267
|
+
return String(text());
|
|
13268
|
+
} catch (error) {
|
|
13269
|
+
return null;
|
|
13270
|
+
}
|
|
13271
|
+
}
|
|
13208
13272
|
function clearPending(path) {
|
|
13209
13273
|
const timer = syncTimers.get(path);
|
|
13210
13274
|
if (timer) {
|
|
@@ -13677,7 +13741,7 @@ window.__ModuleLoader__.load({
|
|
|
13677
13741
|
if (!model || model.uri.scheme !== "edrv") return;
|
|
13678
13742
|
const path = pathOfModel(model);
|
|
13679
13743
|
if (!path) return;
|
|
13680
|
-
const contentDisposer = model.onDidChangeContent(() => syncDoc(path, model.getValue()));
|
|
13744
|
+
const contentDisposer = model.onDidChangeContent(() => syncDoc(path, () => model.getValue()));
|
|
13681
13745
|
const disposeDisposer = model.onWillDispose(() => {
|
|
13682
13746
|
closeDoc(path);
|
|
13683
13747
|
contentDisposer.dispose();
|
|
@@ -19045,10 +19109,38 @@ window.__ModuleLoader__.load({
|
|
|
19045
19109
|
}, item.copyFrom ? item.copyFrom + "@" + (item.copyFromRev ?? "?") : ""), buttons);
|
|
19046
19110
|
}
|
|
19047
19111
|
/**
|
|
19112
|
+
* 单个数据行:浅比较包装(回调身份由父级 ref 转发保持稳定,故 memo 才可能命中)。
|
|
19113
|
+
* @author ddj 2026年10月14号
|
|
19114
|
+
*/
|
|
19115
|
+
const LogItemRow = react.default.memo(LogItem);
|
|
19116
|
+
/**
|
|
19117
|
+
* 数据行容器(P0-14 上段)。独立 memo:只在条目列表/选中集/工作副本版号变化时重建行元素;
|
|
19118
|
+
* 上段高度拖拽与弹窗缩放只改 LogList 的 height,不再逐帧重建整表(原先每帧 logRowsOf + N 个元素)。
|
|
19119
|
+
* @author ddj 2026年10月14号
|
|
19120
|
+
* @param props.list 过滤排序后的条目(非空)
|
|
19121
|
+
* @param props.selectedRevs 可见选中的 revision 集合
|
|
19122
|
+
* @param props.wcRev 工作副本版号(null = 不加粗)
|
|
19123
|
+
* @param props.onPick 选中回调(event, item)
|
|
19124
|
+
* @param props.onMenu 右键回调(event, item)
|
|
19125
|
+
* @returns 行元素
|
|
19126
|
+
*/
|
|
19127
|
+
const LogRows = react.default.memo(function LogRows(props) {
|
|
19128
|
+
const { list, selectedRevs, wcRev, onPick, onMenu } = props;
|
|
19129
|
+
return react.default.createElement(react.default.Fragment, null, logRowsOf(list).map((row) => react.default.createElement(LogItemRow, {
|
|
19130
|
+
key: row.item.revision + ":" + row.level,
|
|
19131
|
+
item: row.item,
|
|
19132
|
+
on: selectedRevs.has(row.item.revision),
|
|
19133
|
+
wc: wcRev != null && row.item.revision === wcRev,
|
|
19134
|
+
level: row.level,
|
|
19135
|
+
onPick,
|
|
19136
|
+
onMenu
|
|
19137
|
+
})));
|
|
19138
|
+
});
|
|
19139
|
+
/**
|
|
19048
19140
|
* 上窗格列表(列头行 + 数据行 + 「加载更多」;P0-13 宽度受拖拽控制)。
|
|
19049
19141
|
* @author ddj 2026年09月16号 / 2026年09月17号
|
|
19050
19142
|
* @param props.list 过滤排序后的条目(null = 未加载)
|
|
19051
|
-
* @param props.
|
|
19143
|
+
* @param props.selectedRevs 可见选中的 revision 集合(P1-3 多选;由父级按 revision 维护)
|
|
19052
19144
|
* @param props.keyword 过滤词(决定空态文案)
|
|
19053
19145
|
* @param props.busy 加载中
|
|
19054
19146
|
* @param props.truncated 是否可能还有更多
|
|
@@ -19061,19 +19153,17 @@ window.__ModuleLoader__.load({
|
|
|
19061
19153
|
* @returns 列表元素
|
|
19062
19154
|
*/
|
|
19063
19155
|
function LogList(props) {
|
|
19064
|
-
const { list,
|
|
19156
|
+
const { list, selectedRevs, keyword, busy, truncated, sortKey, sortDir, onSort, onSortReset, onPick, onLoadMore, onItemMenu, height, wcRev } = props;
|
|
19065
19157
|
let inner;
|
|
19066
19158
|
if (list === null) inner = react.default.createElement("div", { className: "edrv-tree-loading" }, busy ? "读取日志中…" : "尚未加载");
|
|
19067
19159
|
else if (!list.length) inner = react.default.createElement("div", { className: "edrv-tree-loading" }, keyword ? "无匹配条目" : "无提交历史");
|
|
19068
|
-
else inner =
|
|
19069
|
-
|
|
19070
|
-
|
|
19071
|
-
|
|
19072
|
-
wc: wcRev != null && row.item.revision === wcRev,
|
|
19073
|
-
level: row.level,
|
|
19160
|
+
else inner = react.default.createElement(LogRows, {
|
|
19161
|
+
list,
|
|
19162
|
+
selectedRevs,
|
|
19163
|
+
wcRev,
|
|
19074
19164
|
onPick,
|
|
19075
19165
|
onMenu: onItemMenu
|
|
19076
|
-
})
|
|
19166
|
+
});
|
|
19077
19167
|
const more = truncated ? react.default.createElement("div", { className: "edrv-svnlog-more" }, react.default.createElement("button", {
|
|
19078
19168
|
className: "edrv-svn-act",
|
|
19079
19169
|
disabled: busy,
|
|
@@ -19393,7 +19483,9 @@ window.__ModuleLoader__.load({
|
|
|
19393
19483
|
sortKey,
|
|
19394
19484
|
sortDir
|
|
19395
19485
|
]);
|
|
19396
|
-
const
|
|
19486
|
+
const sortedRevs = react.default.useMemo(() => sorted ? new Set(sorted.map((x) => x.revision)) : null, [sorted]);
|
|
19487
|
+
const visibleSelected = react.default.useMemo(() => sortedRevs ? selected.filter((item) => sortedRevs.has(item.revision)) : selected, [sortedRevs, selected]);
|
|
19488
|
+
const selectedRevs = react.default.useMemo(() => new Set(visibleSelected.map((item) => item.revision)), [visibleSelected]);
|
|
19397
19489
|
const current = visibleSelected.length ? visibleSelected[0] : sorted && sorted.length ? sorted[0] : null;
|
|
19398
19490
|
/** 点列头:同键切换升降,异键回落升序;复位入口在列头行(G10)。 */
|
|
19399
19491
|
const onSort = (key) => {
|
|
@@ -19492,24 +19584,26 @@ window.__ModuleLoader__.load({
|
|
|
19492
19584
|
* @param wantList 目标上段(修订列表)高度
|
|
19493
19585
|
* @param wantMsg 目标中段(提交信息)高度
|
|
19494
19586
|
*/
|
|
19587
|
+
/** @private @author ddj 2026年10月14号 @param nextList 目标上段高度 @param nextMsg 目标中段高度 只在值真正变化时提交。 */
|
|
19588
|
+
const applyFit = (nextList, nextMsg) => {
|
|
19589
|
+
if (nextList !== listH) setListH(nextList);
|
|
19590
|
+
if (nextMsg !== msgH) setMsgH(nextMsg);
|
|
19591
|
+
};
|
|
19495
19592
|
const fitHeights = (wantList, wantMsg) => {
|
|
19496
19593
|
const total = bodyRef.current?.clientHeight ?? 0;
|
|
19497
19594
|
if (!total) {
|
|
19498
|
-
|
|
19499
|
-
setMsgH(wantMsg);
|
|
19595
|
+
applyFit(wantList, wantMsg);
|
|
19500
19596
|
return;
|
|
19501
19597
|
}
|
|
19502
19598
|
const paneOcc = paneRef.current?.offsetHeight ?? -1;
|
|
19503
19599
|
const chrome = paneOcc >= 0 ? Math.max(0, total - listH - msgH - paneOcc) : HSPLIT_TOTAL_PX;
|
|
19504
19600
|
const budget = total - PANE_KEEP_MIN - chrome;
|
|
19505
19601
|
if (budget < 260) {
|
|
19506
|
-
|
|
19507
|
-
setMsgH(Math.min(wantMsg, MSG_H_MIN));
|
|
19602
|
+
applyFit(Math.min(wantList, LIST_H_MIN), Math.min(wantMsg, MSG_H_MIN));
|
|
19508
19603
|
return;
|
|
19509
19604
|
}
|
|
19510
19605
|
if (wantList + wantMsg <= budget) {
|
|
19511
|
-
|
|
19512
|
-
setMsgH(wantMsg);
|
|
19606
|
+
applyFit(wantList, wantMsg);
|
|
19513
19607
|
return;
|
|
19514
19608
|
}
|
|
19515
19609
|
const scale = budget / (wantList + wantMsg);
|
|
@@ -19522,8 +19616,7 @@ window.__ModuleLoader__.load({
|
|
|
19522
19616
|
nextMsg = MSG_H_MIN;
|
|
19523
19617
|
nextList = Math.min(nextList, budget - nextMsg);
|
|
19524
19618
|
}
|
|
19525
|
-
|
|
19526
|
-
setMsgH(nextMsg);
|
|
19619
|
+
applyFit(nextList, nextMsg);
|
|
19527
19620
|
};
|
|
19528
19621
|
const fitRef = react.default.useRef(null);
|
|
19529
19622
|
fitRef.current = () => fitHeights(listH, msgH);
|
|
@@ -19617,6 +19710,15 @@ window.__ModuleLoader__.load({
|
|
|
19617
19710
|
].filter(Boolean)
|
|
19618
19711
|
});
|
|
19619
19712
|
};
|
|
19713
|
+
/** @author ddj 2026年10月14号 */
|
|
19714
|
+
const rowCbs = react.default.useRef({
|
|
19715
|
+
pick: null,
|
|
19716
|
+
menu: null
|
|
19717
|
+
});
|
|
19718
|
+
rowCbs.current.pick = pickWithMods;
|
|
19719
|
+
rowCbs.current.menu = onItemMenu;
|
|
19720
|
+
const pickStable = react.default.useCallback((event, item) => rowCbs.current.pick?.(event, item), []);
|
|
19721
|
+
const menuStable = react.default.useCallback((event, item) => rowCbs.current.menu?.(event, item), []);
|
|
19620
19722
|
/** 下窗格路径行右键菜单(P0-8;G8 目录不可开差异,G9 仓库外不可打开)。 */
|
|
19621
19723
|
const onPathMenu = (event, item, revision) => {
|
|
19622
19724
|
event.preventDefault();
|
|
@@ -19654,7 +19756,7 @@ window.__ModuleLoader__.load({
|
|
|
19654
19756
|
ref: bodyRef
|
|
19655
19757
|
}, react.default.createElement(LogList, {
|
|
19656
19758
|
list: sorted,
|
|
19657
|
-
|
|
19759
|
+
selectedRevs,
|
|
19658
19760
|
keyword: query.trim(),
|
|
19659
19761
|
busy,
|
|
19660
19762
|
truncated,
|
|
@@ -19662,9 +19764,9 @@ window.__ModuleLoader__.load({
|
|
|
19662
19764
|
sortDir,
|
|
19663
19765
|
onSort,
|
|
19664
19766
|
onSortReset,
|
|
19665
|
-
onPick:
|
|
19767
|
+
onPick: pickStable,
|
|
19666
19768
|
onLoadMore,
|
|
19667
|
-
onItemMenu,
|
|
19769
|
+
onItemMenu: menuStable,
|
|
19668
19770
|
height: listH,
|
|
19669
19771
|
wcRev
|
|
19670
19772
|
}), react.default.createElement(Splitter, {
|
|
@@ -26578,17 +26680,7 @@ window.__ModuleLoader__.load({
|
|
|
26578
26680
|
"aria-label": "关闭提示",
|
|
26579
26681
|
onClick: dismissHint
|
|
26580
26682
|
}, "×")) : null;
|
|
26581
|
-
const fullView = (0, react_dom.createPortal)(react.default.createElement("
|
|
26582
|
-
"data-edrv-view": "1",
|
|
26583
|
-
style: {
|
|
26584
|
-
display: "flex",
|
|
26585
|
-
flexDirection: "column",
|
|
26586
|
-
flex: 1,
|
|
26587
|
-
minWidth: 0,
|
|
26588
|
-
minHeight: 0,
|
|
26589
|
-
overflow: "hidden"
|
|
26590
|
-
}
|
|
26591
|
-
}, pathBar, sideHintEl, diskBanner(), editorArea, statusBar), fullHost, "edrv-full-editor");
|
|
26683
|
+
const fullView = (0, react_dom.createPortal)(react.default.createElement(react.default.Fragment, null, pathBar, sideHintEl, diskBanner(), editorArea, statusBar), fullHost, "edrv-full-editor");
|
|
26592
26684
|
const mainCol = react.default.createElement("div", {
|
|
26593
26685
|
className: "edrv-main-col edrv-workbench",
|
|
26594
26686
|
style: {
|
|
@@ -26609,13 +26701,7 @@ window.__ModuleLoader__.load({
|
|
|
26609
26701
|
onBounds: floatBounds,
|
|
26610
26702
|
onFocus: selectGroup,
|
|
26611
26703
|
onReturn: returnPane
|
|
26612
|
-
}, renderGroup(item.group))), dragPreview ? (0, react_dom.createPortal)(react.default.createElement("div", {
|
|
26613
|
-
"data-edrv-view": "1",
|
|
26614
|
-
style: {
|
|
26615
|
-
display: "contents",
|
|
26616
|
-
pointerEvents: "none"
|
|
26617
|
-
}
|
|
26618
|
-
}, dragPreview.rect ? react.default.createElement("div", {
|
|
26704
|
+
}, renderGroup(item.group))), dragPreview ? (0, react_dom.createPortal)(react.default.createElement(react.default.Fragment, null, dragPreview.rect ? react.default.createElement("div", {
|
|
26619
26705
|
className: "edrv-drag-overlay",
|
|
26620
26706
|
style: dragPreview.rect
|
|
26621
26707
|
}, react.default.createElement("span", { className: "edrv-drop-label" }, dragPreview.label)) : null, react.default.createElement("div", {
|
|
@@ -29853,7 +29939,7 @@ window.__ModuleLoader__.load({
|
|
|
29853
29939
|
const warnings = report?.warnings ?? [];
|
|
29854
29940
|
const devForm = report?.devForm;
|
|
29855
29941
|
const versionText = report?.dshVersion ? "(DSH " + report.dshVersion + " · 插件 " + (report?.pluginVersion ?? "") + ")" : report?.pluginVersion ? "(插件版本 " + report.pluginVersion + ")" : "";
|
|
29856
|
-
return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "兼容性"), react.default.createElement("p", null, "检测与其他插件 / DSH 版本的适配状态与已知问题。" + versionText), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), devForm?.enabled && group("开发形态", react.default.createElement("div", { className: "vsm-compat-item ok" }, react.default.createElement("span", { className: "vsm-compat-name" }, "开发形态开启(工作区链接安装)"), react.default.createElement("span", { className: "vsm-compat-note" }, devForm.path))), group("版本适配", renderItems(report?.adapters)), group("外部插件与服务", renderItems(report?.external)), group("客户端适配", renderItems(getSummary?.() ?? [])), group("护栏", renderItems(report?.guards)), group("警告", warnings.length ? warnings.map((w, index) => react.default.createElement("div", {
|
|
29942
|
+
return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "兼容性"), react.default.createElement("p", null, "检测与其他插件 / DSH 版本的适配状态与已知问题。" + versionText), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), devForm?.enabled && group("开发形态", react.default.createElement("div", { className: "vsm-compat-item ok" }, react.default.createElement("span", { className: "vsm-compat-name" }, "开发形态开启(工作区链接安装)"), react.default.createElement("span", { className: "vsm-compat-note" }, devForm.path))), group("版本适配", renderItems(report?.adapters)), group("外部插件与服务", renderItems(report?.external)), group("客户端适配", renderItems(getSummary?.(report?.dshVersion ?? "") ?? [])), group("护栏", renderItems(report?.guards)), group("警告", warnings.length ? warnings.map((w, index) => react.default.createElement("div", {
|
|
29857
29943
|
key: index,
|
|
29858
29944
|
className: "vsm-mcp-error vsm-mcp-banner"
|
|
29859
29945
|
}, w)) : react.default.createElement("div", { className: "vsm-compat-item ok" }, react.default.createElement("span", { className: "vsm-compat-name" }, "未检测到兼容性问题"), react.default.createElement("span", { className: "vsm-compat-note" }, ""))));
|
|
@@ -30741,6 +30827,12 @@ window.__ModuleLoader__.load({
|
|
|
30741
30827
|
/** 深链移交轮询周期(兼活跃心跳)。 */
|
|
30742
30828
|
const HANDOFF_POLL_MS = 3e3;
|
|
30743
30829
|
/**
|
|
30830
|
+
* 页面隐藏时的降频倍数(3s × 5 = 15s)。
|
|
30831
|
+
* 必须仍短于 host 租约(src/externalHandoff.ts 的 LEASE_MS = 30s):心跳一旦超期,
|
|
30832
|
+
* host 判定该页面已死,launcher 会重复开新页——比多几次 RPC 更糟。
|
|
30833
|
+
*/
|
|
30834
|
+
const HIDDEN_POLL_EVERY = 5;
|
|
30835
|
+
/**
|
|
30744
30836
|
* 装配外部深链:深链参数缺失时仅保持移交轮询(零额外开销)。
|
|
30745
30837
|
* @author ddj 2026年09月07号
|
|
30746
30838
|
* @param ctx 客户端根上下文(sessions/workspaces 服务)
|
|
@@ -30763,9 +30855,10 @@ window.__ModuleLoader__.load({
|
|
|
30763
30855
|
return dispose;
|
|
30764
30856
|
}
|
|
30765
30857
|
/**
|
|
30766
|
-
*
|
|
30858
|
+
* 深链移交轮询:可见时每 3s 向 host 领取待打开请求(兼活跃心跳,launcher 据此决定是否开新页);
|
|
30859
|
+
* 页面隐藏时降到 15s 一次——无人看页面时移交不必保持 3s 精度,但心跳不能停(超期会让 host 判页面已死)。
|
|
30767
30860
|
* 页面级单例(window 标记防 HMR 重复);领取到即执行打开规则。
|
|
30768
|
-
* @author ddj 2026年09月08号
|
|
30861
|
+
* @author ddj 2026年09月08号 / 2026年10月15号
|
|
30769
30862
|
* @param ctx 客户端根上下文
|
|
30770
30863
|
*/
|
|
30771
30864
|
function startPresencePoll(ctx) {
|
|
@@ -30776,6 +30869,20 @@ window.__ModuleLoader__.load({
|
|
|
30776
30869
|
let disposed = false;
|
|
30777
30870
|
let polling = false;
|
|
30778
30871
|
let executing = false;
|
|
30872
|
+
let ticks = 0;
|
|
30873
|
+
/**
|
|
30874
|
+
* 页面隐藏时按倍数跳轮;可见时每次都跑并清零计数。
|
|
30875
|
+
* @author ddj 2026年10月15号
|
|
30876
|
+
* @returns 是否跳过本轮
|
|
30877
|
+
*/
|
|
30878
|
+
function hiddenSkip() {
|
|
30879
|
+
if (typeof document === "undefined" || document.visibilityState !== "hidden") {
|
|
30880
|
+
ticks = 0;
|
|
30881
|
+
return false;
|
|
30882
|
+
}
|
|
30883
|
+
ticks += 1;
|
|
30884
|
+
return ticks % HIDDEN_POLL_EVERY !== 0;
|
|
30885
|
+
}
|
|
30779
30886
|
/** @private @author ddj 2026年09月28号 Open all requested paths before acknowledging this exact lease. */
|
|
30780
30887
|
async function execute(open) {
|
|
30781
30888
|
executing = true;
|
|
@@ -30807,6 +30914,7 @@ window.__ModuleLoader__.load({
|
|
|
30807
30914
|
}
|
|
30808
30915
|
}
|
|
30809
30916
|
const timer = window.setInterval(() => {
|
|
30917
|
+
if (hiddenSkip()) return;
|
|
30810
30918
|
tick().catch(() => {});
|
|
30811
30919
|
}, HANDOFF_POLL_MS);
|
|
30812
30920
|
/** @private @author ddj 2026年09月28号 Stop heartbeats and fail outstanding editor waits on plugin disposal. */
|
|
@@ -30882,6 +30990,80 @@ window.__ModuleLoader__.load({
|
|
|
30882
30990
|
setTimeout(() => toast.remove(), TOAST_MS);
|
|
30883
30991
|
}
|
|
30884
30992
|
//#endregion
|
|
30993
|
+
//#region src/client/compatSummary.ts
|
|
30994
|
+
/**
|
|
30995
|
+
* Classify the API generation without importing the host's node-only version probe.
|
|
30996
|
+
* @private @author ddj 2026年10月13号
|
|
30997
|
+
* @param input Running DSH version from the compat RPC.
|
|
30998
|
+
* @param patch First supported 0.1.x patch (alpha.1).
|
|
30999
|
+
* @returns Whether that API generation is expected; undefined for unknown versions.
|
|
31000
|
+
*/
|
|
31001
|
+
function apiExpected(input, patch) {
|
|
31002
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z]+(?:\.[0-9A-Za-z]+)*))?(?:\+[0-9A-Za-z]+(?:\.[0-9A-Za-z]+)*)?$/.exec(input.trim());
|
|
31003
|
+
if (!match) return void 0;
|
|
31004
|
+
const major = Number(match[1]);
|
|
31005
|
+
const minor = Number(match[2]);
|
|
31006
|
+
const current = Number(match[3]);
|
|
31007
|
+
if (major !== 0) return major > 0;
|
|
31008
|
+
if (minor !== 1) return minor > 1;
|
|
31009
|
+
if (current !== patch) return current > patch;
|
|
31010
|
+
const pre = match[4];
|
|
31011
|
+
if (!pre) return true;
|
|
31012
|
+
const parts = pre.split(".");
|
|
31013
|
+
if (/^\d+$/.test(parts[0])) return false;
|
|
31014
|
+
if (parts[0] !== "alpha") return parts[0] > "alpha";
|
|
31015
|
+
return parts.length > 1 && (/^\d+$/.test(parts[1]) ? Number(parts[1]) >= 1 : true);
|
|
31016
|
+
}
|
|
31017
|
+
/**
|
|
31018
|
+
* Build only applicable diagnostics; explicit legacy choices still expose missing capabilities.
|
|
31019
|
+
* @public @author ddj 2026年10月13号
|
|
31020
|
+
* @param state Actual client installation facts and user selection.
|
|
31021
|
+
* @param version Running DSH version; unknown versions retain conservative diagnostics.
|
|
31022
|
+
* @returns Visible adapters, without changing routing or installation.
|
|
31023
|
+
*/
|
|
31024
|
+
function clientSummary(state, version = "") {
|
|
31025
|
+
const remoteExpected = apiExpected(version, 3);
|
|
31026
|
+
const officialExpected = apiExpected(version, 5);
|
|
31027
|
+
const official = state.officialSidebar;
|
|
31028
|
+
const legacyChosen = state.selected === SIDEBAR_PLUGIN;
|
|
31029
|
+
const items = [{
|
|
31030
|
+
name: "设置桥",
|
|
31031
|
+
active: state.settingsReady,
|
|
31032
|
+
note: state.settingsReady ? "使用 " + state.settingsService + " 桥" : "未绑定设置服务(持久化不可用,晚到重试窗口内自愈)"
|
|
31033
|
+
}];
|
|
31034
|
+
if (remoteExpected !== true && !state.remoteReady && !official) items.push({
|
|
31035
|
+
name: "文件打开路由(workspaces.openPath)",
|
|
31036
|
+
active: state.openPathReady,
|
|
31037
|
+
note: "旧版对话文件链接路由,失败回退系统打开"
|
|
31038
|
+
});
|
|
31039
|
+
if (remoteExpected !== false || state.remoteReady) items.push({
|
|
31040
|
+
name: "会话文件链接路由(remote.session.openWorkspacePath)",
|
|
31041
|
+
active: state.remoteReady,
|
|
31042
|
+
note: state.remoteReady ? "会话文件链接走插件打开器,失败回退系统打开" : "适用路由未安装(服务缺失或补丁失败)"
|
|
31043
|
+
});
|
|
31044
|
+
if (legacyChosen || state.sidebarOpener || !official && officialExpected !== true) items.push({
|
|
31045
|
+
name: "侧边栏打开器(dsh-better-sidebar)",
|
|
31046
|
+
active: state.sidebarOpener,
|
|
31047
|
+
note: state.sidebarOpener ? "已注册(优先级 80)" : "未检测到侧边栏打开能力"
|
|
31048
|
+
});
|
|
31049
|
+
if (official || officialExpected !== false) items.push({
|
|
31050
|
+
name: "侧边栏编辑区(官方 Sidebar)",
|
|
31051
|
+
active: official,
|
|
31052
|
+
note: official ? "编辑区=官方右侧 Sidebar Tab(对话+编辑同屏)" : "官方侧边栏服务未探测到"
|
|
31053
|
+
});
|
|
31054
|
+
if (!official && (state.legacySidebar || officialExpected !== true)) items.push({
|
|
31055
|
+
name: "侧边栏编辑区(dsh-better-sidebar,归档)",
|
|
31056
|
+
active: state.legacySidebar,
|
|
31057
|
+
note: state.legacySidebar ? "编辑区=旧版侧边栏 Tab" : "旧版侧边栏回退能力未检测到"
|
|
31058
|
+
});
|
|
31059
|
+
if (state.fileClaim || state.claimWanted && (official || officialExpected !== false)) items.push({
|
|
31060
|
+
name: "文件链接官方认领(dsh-resource://file)",
|
|
31061
|
+
active: state.fileClaim,
|
|
31062
|
+
note: state.fileClaim ? "聊天文件链接转发进单一编辑器页签" : "官方文件链接认领未安装"
|
|
31063
|
+
});
|
|
31064
|
+
return items;
|
|
31065
|
+
}
|
|
31066
|
+
//#endregion
|
|
30885
31067
|
//#region src/client/ui/SideEditorTab.ts
|
|
30886
31068
|
/**
|
|
30887
31069
|
* dsh-vscode-mode client — SideEditorTab:betterSidebar「文件编辑」Tab 的组件包装。
|
|
@@ -37841,44 +38023,24 @@ window.__ModuleLoader__.load({
|
|
|
37841
38023
|
let claimDisposer = null;
|
|
37842
38024
|
/** 「添加到对话」动作集:编辑区/Tab 右键菜单注入文件引用与代码块(conversation 服务缺失时各动作安全降级)。 */
|
|
37843
38025
|
const addToConversation = createAddToConversation(ctx);
|
|
37844
|
-
/**
|
|
37845
|
-
|
|
37846
|
-
|
|
37847
|
-
|
|
37848
|
-
|
|
37849
|
-
|
|
37850
|
-
|
|
37851
|
-
|
|
37852
|
-
|
|
37853
|
-
|
|
37854
|
-
|
|
37855
|
-
|
|
37856
|
-
|
|
37857
|
-
|
|
37858
|
-
|
|
37859
|
-
|
|
37860
|
-
|
|
37861
|
-
|
|
37862
|
-
name: "侧边栏打开器(dsh-better-sidebar)",
|
|
37863
|
-
active: registry.get(SIDEBAR_PLUGIN) !== void 0,
|
|
37864
|
-
note: registry.get("dsh-better-sidebar") !== void 0 ? "已注册(优先级 80)" : "未检测到侧边栏打开能力"
|
|
37865
|
-
},
|
|
37866
|
-
{
|
|
37867
|
-
name: "侧边栏编辑区(官方 Sidebar)",
|
|
37868
|
-
active: officialService !== void 0,
|
|
37869
|
-
note: officialService !== void 0 ? "编辑区=官方右侧 Sidebar Tab(对话+编辑同屏,DSH 0.1.5+)" : "官方侧边栏服务未探测到(DSH < 0.1.5-alpha.1 时属预期)"
|
|
37870
|
-
},
|
|
37871
|
-
{
|
|
37872
|
-
name: "侧边栏编辑区(dsh-better-sidebar,归档)",
|
|
37873
|
-
active: sideService !== void 0,
|
|
37874
|
-
note: sideService !== void 0 ? "编辑区=侧边栏 Tab(旧版 DSH 回退形态)" : "未检测到;DSH ≥ 0.1.5 优先官方侧边栏,旧版可安装(" + SIDEBAR_INSTALL_CMD + ")"
|
|
37875
|
-
},
|
|
37876
|
-
{
|
|
37877
|
-
name: "文件链接官方认领(dsh-resource://file)",
|
|
37878
|
-
active: claimDisposer !== null,
|
|
37879
|
-
note: claimDisposer !== null ? "聊天文件链接转发进单一编辑器页签(文件分页归编辑器自带页签栏)" : "链接走官方查看器或旧版路由(未认领)"
|
|
37880
|
-
}
|
|
37881
|
-
];
|
|
38026
|
+
/**
|
|
38027
|
+
* Build applicable client diagnostics from live installation state.
|
|
38028
|
+
* @private @author ddj 2026年10月13号
|
|
38029
|
+
* @param version Running DSH version supplied by the compat RPC.
|
|
38030
|
+
* @returns Applicable adapters; routing behavior is unchanged.
|
|
38031
|
+
*/
|
|
38032
|
+
const compatSummary = (version = "") => clientSummary({
|
|
38033
|
+
settingsService: bridge.service(),
|
|
38034
|
+
settingsReady: bridge.scope() !== void 0,
|
|
38035
|
+
openPathReady: Boolean(workspaces?.openPath),
|
|
38036
|
+
remoteReady: remoteOpenInstalled,
|
|
38037
|
+
sidebarOpener: registry.get(SIDEBAR_PLUGIN) !== void 0,
|
|
38038
|
+
legacySidebar: sideService !== void 0,
|
|
38039
|
+
officialSidebar: officialService !== void 0,
|
|
38040
|
+
fileClaim: claimDisposer !== null,
|
|
38041
|
+
claimWanted: shouldClaimFiles(selected),
|
|
38042
|
+
selected
|
|
38043
|
+
}, version);
|
|
37882
38044
|
ctx.provide("fileOpeners", registry);
|
|
37883
38045
|
ctx.effect(() => registry.register(vscodeOpener()), "vscode-mode: file opener");
|
|
37884
38046
|
const sidebarPanels = createSidebarPanelRegistry();
|