dsh-vscode-mode 0.15.7 → 0.15.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/lib/client.js +65 -1
- package/lib/client.js.map +1 -1
- package/lib/index.js +15 -6
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/sessionError.ts +38 -0
- package/src/client/sidebar/panels/FileExplorer.ts +7 -1
- package/src/client/ui/EditorView.ts +13 -0
- package/src/lsp/rpc.ts +3 -2
- package/src/rpc.ts +3 -3
- package/src/shared/rpc.ts +11 -0
- package/src/svn.ts +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vscode-mode",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.8",
|
|
4
4
|
"description": "DSH 上的类 VSCode 编码体验:Monaco 编辑器(文件页签/QuickOpen/状态栏,可驻 DSH 0.1.5+ 官方右侧 Sidebar 与对话同屏)+ 命令栏(Ctrl+Shift+P)与可扩展指令系统 + VS Code 兼容代码片段(.code-snippets 全局/项目,IntelliSense 展开)+ Agent 编辑差异审查(整文件差异/采纳/拒绝/归档/回滚)+ SVN 集成(侧栏变更面板/日志弹窗/并排差异)+ 语言服务器(LSP)智能(跳转定义/引用/hover/大纲 + VSIX 扩展市场安装),状态持久化到工作区旁车,界面状态(页签/光标/侧栏/编辑 Tab/导航历史)镜像到 DSH home 跨重启恢复",
|
|
5
5
|
"icon": "assets/icon.svg",
|
|
6
6
|
"keywords": [
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — 会话缺失错误识别与友好文案。
|
|
3
|
+
* 跨端场景:桌面端与 Web 端各自运行独立 DSH 实例,会话列表跨实例可见,
|
|
4
|
+
* 但会话仅在一端激活;另一端发起的 edrv.* RPC 会被 host requireSession
|
|
5
|
+
* 以「会话不存在」拒绝。此处把该错误归类为终态提示(非反复重试可解),
|
|
6
|
+
* 并给出引导文案,替代裸错误卡死观感。
|
|
7
|
+
* 作者 ddj 2026年10月16号
|
|
8
|
+
*/
|
|
9
|
+
import { SESSION_MISSING_ERROR } from '../shared/rpc.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 判断错误文案是否为「会话不存在」类错误。
|
|
13
|
+
* @author ddj 2026年10月16号
|
|
14
|
+
* @param text 任意错误文本(host 业务 error、异常 message 等原始形态)
|
|
15
|
+
* @returns 命中规范文案(含拼接形态,如「会话不存在:路径」)返回 true;空值安全
|
|
16
|
+
*/
|
|
17
|
+
export function isSessionMissingError(text: unknown): boolean {
|
|
18
|
+
return String(text ?? '').includes(SESSION_MISSING_ERROR)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 会话缺失提示标题(编辑区终态面板与侧栏红条共用)。
|
|
23
|
+
* @author ddj 2026年10月16号
|
|
24
|
+
* @returns 简短标题文案
|
|
25
|
+
*/
|
|
26
|
+
export function sessionMissingTitle(): string {
|
|
27
|
+
return '会话在当前端不可用'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 会话缺失提示详情:说明成因并给出引导(去持有端查看,或在本端打开会话后重试)。
|
|
32
|
+
* @author ddj 2026年10月16号
|
|
33
|
+
* @returns 多句详情文案
|
|
34
|
+
*/
|
|
35
|
+
export function sessionMissingDetail(): string {
|
|
36
|
+
return '该会话可能正被其他 DSH 实例(桌面端 / Web 端)占用,或尚未在当前实例激活。' +
|
|
37
|
+
'请在持有该会话的一端查看,或在本端打开该会话后重试。'
|
|
38
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import React from 'react'
|
|
14
14
|
import { IconFolderClose16, IconFolderOpen16, IconRefreshOutline16, fileIconEl } from '../../ui/icons.js'
|
|
15
15
|
import { rpc } from '../../rpc.js'
|
|
16
|
+
import { isSessionMissingError, sessionMissingDetail, sessionMissingTitle } from '../../sessionError.js'
|
|
16
17
|
import { ContextMenu } from '../../ui/ContextMenu.js'
|
|
17
18
|
import { buildTreeMenu } from '../contextMenu.js'
|
|
18
19
|
import { explorerLoad, explorerSave } from '../../state/explorerCache.js'
|
|
@@ -423,7 +424,12 @@ export function FileExplorer(props) {
|
|
|
423
424
|
const updateHint = !ctx?.svn?.svnCli ? 'SVN CLI 不可用' : !ctx?.openSvnUpdate ? 'SVN 更新窗口不可用' : '更新项目(打开 SVN 更新窗口)'
|
|
424
425
|
|
|
425
426
|
const errorList = Object.entries(errors)
|
|
426
|
-
|
|
427
|
+
// 跨端会话缺失:裸错误替换为友好标题 + 详情(重试仍保留,会话在本端可用后即恢复)
|
|
428
|
+
const rawErrorText = errorList.length ? errorList[0][1] : null
|
|
429
|
+
const sessionMissing = isSessionMissingError(rawErrorText)
|
|
430
|
+
const errorText = rawErrorText === null
|
|
431
|
+
? null
|
|
432
|
+
: sessionMissing ? sessionMissingTitle() + ':' + sessionMissingDetail() : rawErrorText
|
|
427
433
|
|
|
428
434
|
return React.createElement('div', { className: 'edrv-side-panel', onContextMenu: onPanelContext },
|
|
429
435
|
React.createElement('div', { className: 'edrv-side-head' },
|
|
@@ -118,6 +118,7 @@ import { buildBpMenu } from '../dap/bpMenu.js'
|
|
|
118
118
|
import { clampToolbarPosition, loadToolbarPosition, saveToolbarPosition } from '../dap/toolbarDrag.js'
|
|
119
119
|
import { editorModelPathOf } from '../dap/modelPath.js'
|
|
120
120
|
import { dapTrace } from '../dap/trace.js'
|
|
121
|
+
import { isSessionMissingError, sessionMissingDetail, sessionMissingTitle } from '../sessionError.js'
|
|
121
122
|
|
|
122
123
|
/**
|
|
123
124
|
* 只在 Lua 注解类型槽开启注释自动建议;跨文件/离开类型槽立即恢复默认。
|
|
@@ -4024,6 +4025,18 @@ export function EditorView(props) {
|
|
|
4024
4025
|
body = React.createElement('div', { className: 'edrv-empty' },
|
|
4025
4026
|
React.createElement('div', null, '暂无打开的文件'),
|
|
4026
4027
|
React.createElement('div', { style: { fontSize: 12 } }, '使用右上搜索框 (' + (chordOf('edrv.quickOpen') ?? 'Ctrl+P') + ') 打开工作区文件;' + (chordOf('edrv.showCommands') ?? 'Ctrl+Shift+P') + ' 打开命令栏;agent 修改文件后顶部会出现差异角标'))
|
|
4028
|
+
} else if (content === null && loadError && isSessionMissingError(loadError)) {
|
|
4029
|
+
// 跨端会话缺失:终态提示而非加载失败卡死(会话仅在一端激活,本端 RPC 均被拒)
|
|
4030
|
+
body = React.createElement('div', { className: 'edrv-empty edrv-loading', 'aria-live': 'polite' },
|
|
4031
|
+
React.createElement('div', { className: 'edrv-loading-title' }, sessionMissingTitle()),
|
|
4032
|
+
React.createElement('div', { style: { fontSize: 12, maxWidth: 420, lineHeight: 1.6 } }, sessionMissingDetail()),
|
|
4033
|
+
React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', onClick: () => {
|
|
4034
|
+
setLoadError(null)
|
|
4035
|
+
setContent(null)
|
|
4036
|
+
setContentPath(null)
|
|
4037
|
+
setLoadStage({ progress: 10, message: '重新读取文件…' })
|
|
4038
|
+
loadContent(active, sessionId)
|
|
4039
|
+
} }, '重试'))
|
|
4027
4040
|
} else if (content === null && loadError) {
|
|
4028
4041
|
body = loadingBody('文件加载失败:' + loadError, 0, () => {
|
|
4029
4042
|
setLoadError(null)
|
package/src/lsp/rpc.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { onRuntimeProvisioned, envInstallStates } from './dotnetProvision.js'
|
|
|
15
15
|
import { envRequirementsFor, installRequirement } from './envRequirements.js'
|
|
16
16
|
import type { LspManager } from './manager.js'
|
|
17
17
|
import type { RpcHandlerMap } from '../shared/rpc.js'
|
|
18
|
+
import { SESSION_MISSING_ERROR, SESSION_NO_WORKSPACE_ERROR } from '../shared/rpc.js'
|
|
18
19
|
import type { LspLocation, LspServerStatus } from '../shared/lsp.js'
|
|
19
20
|
import { installFromMarket, installVsixFile, listInstalled, marketGet, marketSearch, uninstall, updateExtension, type ExtInfo } from './extmgr.js'
|
|
20
21
|
import { toWorkspacePath } from './uri.js'
|
|
@@ -152,9 +153,9 @@ export function createLspRpc(deps: LspRpcDeps): { handlers: Partial<RpcHandlerMa
|
|
|
152
153
|
/** 解析会话与工作区根(失败 → null + error)。 */
|
|
153
154
|
const rootOf = async (sessionId: string | undefined): Promise<{ root: string; sessionId: string } | { err: string }> => {
|
|
154
155
|
const session = sessionOf(ctx, sessionId)
|
|
155
|
-
if (!session) return { err:
|
|
156
|
+
if (!session) return { err: SESSION_MISSING_ERROR }
|
|
156
157
|
const cwd = cwdOf(session)
|
|
157
|
-
if (!cwd) return { err:
|
|
158
|
+
if (!cwd) return { err: SESSION_NO_WORKSPACE_ERROR }
|
|
158
159
|
try {
|
|
159
160
|
const root = await searchRoot(ctx, session)
|
|
160
161
|
const sid = typeof session.id === 'string' && session.id ? session.id : sessionId ?? ''
|
package/src/rpc.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 作者 ddj 2026-08-20
|
|
6
6
|
*/
|
|
7
7
|
import type { DecideItem, DecideResult, RpcHandlerMap, RpcMethod, RpcRequestMap, RpcResult, RpcScope } from './shared/rpc.js'
|
|
8
|
-
import { binaryMimeOf } from './shared/rpc.js'
|
|
8
|
+
import { binaryMimeOf, SESSION_MISSING_ERROR, SESSION_NO_WORKSPACE_ERROR } from './shared/rpc.js'
|
|
9
9
|
import type { DiffRecord, RecordView } from './shared/types.js'
|
|
10
10
|
import type { Ctx, Session } from './store.js'
|
|
11
11
|
import {
|
|
@@ -98,9 +98,9 @@ async function requireSession(ctx: Ctx, sessionId: string | undefined): Promise<
|
|
|
98
98
|
{ session: Session; cwd: string } | { err: string }
|
|
99
99
|
> {
|
|
100
100
|
const session = sessionOf(ctx, sessionId)
|
|
101
|
-
if (!session) return { err:
|
|
101
|
+
if (!session) return { err: SESSION_MISSING_ERROR }
|
|
102
102
|
const cwd = cwdOf(session)
|
|
103
|
-
if (!cwd) return { err:
|
|
103
|
+
if (!cwd) return { err: SESSION_NO_WORKSPACE_ERROR }
|
|
104
104
|
return { session, cwd }
|
|
105
105
|
}
|
|
106
106
|
|
package/src/shared/rpc.ts
CHANGED
|
@@ -62,6 +62,17 @@ export function imageMimeOf(path: string): string {
|
|
|
62
62
|
/** PDF 统一 MIME(host base64 响应与 client PDF 面板同源,避免两处漂移)。 */
|
|
63
63
|
export const PDF_MIME = 'application/pdf'
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* 会话级前置错误的规范文案:host requireSession 系与 client 错误识别共用同一常量,
|
|
67
|
+
* 避免文案漂移导致 client 端分类失效。跨端场景(桌面端/Web 端各自运行独立 DSH 实例、
|
|
68
|
+
* 会话列表互通但会话仅在一端激活)下,本端 host 取不到会话即返回「会话不存在」。
|
|
69
|
+
* @author ddj 2026年10月16号
|
|
70
|
+
*/
|
|
71
|
+
export const SESSION_MISSING_ERROR = '会话不存在'
|
|
72
|
+
|
|
73
|
+
/** 会话存在但无工作区 cwd 时的规范文案(与 SESSION_MISSING_ERROR 同源共用)。 */
|
|
74
|
+
export const SESSION_NO_WORKSPACE_ERROR = '会话无工作区'
|
|
75
|
+
|
|
65
76
|
/**
|
|
66
77
|
* 路径 → 二进制预览 MIME:PDF 特判 {@link PDF_MIME},其余走图片表回退 octet-stream。
|
|
67
78
|
* @author ddj 2026年09月22号
|
package/src/svn.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { tmpdir } from 'node:os'
|
|
|
12
12
|
import type { Ctx, Session } from './store.js'
|
|
13
13
|
import { cwdOf, sessionOf } from './registry.js'
|
|
14
14
|
import type { SvnAction, SvnAiPlan, SvnChangeEntry, SvnConflictArtifact, SvnFeature, SvnIgnoreItem, SvnItemStatus, SvnLogEntry, SvnRemoteOutdatedEntry, SvnStatusPayload, SvnSumEntry } from './shared/svn.js'
|
|
15
|
+
import { SESSION_MISSING_ERROR, SESSION_NO_WORKSPACE_ERROR } from './shared/rpc.js'
|
|
15
16
|
import { AI_PLAN_DIFF_CAP, AI_PLAN_PATHS_CAP, BATCH_PATHS_CAP, FILE_SIZES_CAP, SVN_CLI_DEFAULT, TORTOISE_EXE, contentGuardOf, normalizeAiPlan, svnChangelistNameErrorOf, tortoiseLaunchArgv } from './shared/svn.js'
|
|
16
17
|
import type { AiConfigView } from './shared/ai.js'
|
|
17
18
|
import { buildAiPrompt, svnAiPlanOf } from './ai/svnTriage.js'
|
|
@@ -461,9 +462,9 @@ async function requireSvnSession(
|
|
|
461
462
|
sessionId?: string,
|
|
462
463
|
): Promise<{ cwd: string } | { err: string }> {
|
|
463
464
|
const session: Session | undefined = sessionOf(ctx, sessionId)
|
|
464
|
-
if (!session) return { err:
|
|
465
|
+
if (!session) return { err: SESSION_MISSING_ERROR }
|
|
465
466
|
const cwd = cwdOf(session)
|
|
466
|
-
if (!cwd) return { err:
|
|
467
|
+
if (!cwd) return { err: SESSION_NO_WORKSPACE_ERROR }
|
|
467
468
|
return { cwd }
|
|
468
469
|
}
|
|
469
470
|
|