dsh-vscode-mode 0.1.53 → 0.1.55

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.
Files changed (210) hide show
  1. package/README.md +11 -0
  2. package/assets/vendor/pdfjs/build/pdf.mjs +26370 -0
  3. package/assets/vendor/pdfjs/build/pdf.worker.mjs +61080 -0
  4. package/assets/vendor/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
  5. package/assets/vendor/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
  6. package/assets/vendor/pdfjs/cmaps/78-H.bcmap +0 -0
  7. package/assets/vendor/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
  8. package/assets/vendor/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
  9. package/assets/vendor/pdfjs/cmaps/78-V.bcmap +0 -0
  10. package/assets/vendor/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
  11. package/assets/vendor/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
  12. package/assets/vendor/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
  13. package/assets/vendor/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
  14. package/assets/vendor/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
  15. package/assets/vendor/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
  16. package/assets/vendor/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
  17. package/assets/vendor/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
  18. package/assets/vendor/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
  19. package/assets/vendor/pdfjs/cmaps/Add-H.bcmap +0 -0
  20. package/assets/vendor/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
  21. package/assets/vendor/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
  22. package/assets/vendor/pdfjs/cmaps/Add-V.bcmap +0 -0
  23. package/assets/vendor/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
  24. package/assets/vendor/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
  25. package/assets/vendor/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
  26. package/assets/vendor/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
  27. package/assets/vendor/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
  28. package/assets/vendor/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
  29. package/assets/vendor/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
  30. package/assets/vendor/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  31. package/assets/vendor/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
  32. package/assets/vendor/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
  33. package/assets/vendor/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
  34. package/assets/vendor/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
  35. package/assets/vendor/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
  36. package/assets/vendor/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
  37. package/assets/vendor/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  38. package/assets/vendor/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
  39. package/assets/vendor/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
  40. package/assets/vendor/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
  41. package/assets/vendor/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
  42. package/assets/vendor/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
  43. package/assets/vendor/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
  44. package/assets/vendor/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
  45. package/assets/vendor/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  46. package/assets/vendor/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
  47. package/assets/vendor/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
  48. package/assets/vendor/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
  49. package/assets/vendor/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  50. package/assets/vendor/pdfjs/cmaps/B5-H.bcmap +0 -0
  51. package/assets/vendor/pdfjs/cmaps/B5-V.bcmap +0 -0
  52. package/assets/vendor/pdfjs/cmaps/B5pc-H.bcmap +0 -0
  53. package/assets/vendor/pdfjs/cmaps/B5pc-V.bcmap +0 -0
  54. package/assets/vendor/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
  55. package/assets/vendor/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
  56. package/assets/vendor/pdfjs/cmaps/CNS1-H.bcmap +0 -0
  57. package/assets/vendor/pdfjs/cmaps/CNS1-V.bcmap +0 -0
  58. package/assets/vendor/pdfjs/cmaps/CNS2-H.bcmap +0 -0
  59. package/assets/vendor/pdfjs/cmaps/CNS2-V.bcmap +3 -0
  60. package/assets/vendor/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
  61. package/assets/vendor/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
  62. package/assets/vendor/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
  63. package/assets/vendor/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
  64. package/assets/vendor/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
  65. package/assets/vendor/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
  66. package/assets/vendor/pdfjs/cmaps/EUC-H.bcmap +0 -0
  67. package/assets/vendor/pdfjs/cmaps/EUC-V.bcmap +0 -0
  68. package/assets/vendor/pdfjs/cmaps/Ext-H.bcmap +0 -0
  69. package/assets/vendor/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
  70. package/assets/vendor/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
  71. package/assets/vendor/pdfjs/cmaps/Ext-V.bcmap +0 -0
  72. package/assets/vendor/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
  73. package/assets/vendor/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
  74. package/assets/vendor/pdfjs/cmaps/GB-H.bcmap +4 -0
  75. package/assets/vendor/pdfjs/cmaps/GB-V.bcmap +0 -0
  76. package/assets/vendor/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
  77. package/assets/vendor/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
  78. package/assets/vendor/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
  79. package/assets/vendor/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
  80. package/assets/vendor/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
  81. package/assets/vendor/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
  82. package/assets/vendor/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
  83. package/assets/vendor/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
  84. package/assets/vendor/pdfjs/cmaps/GBT-H.bcmap +0 -0
  85. package/assets/vendor/pdfjs/cmaps/GBT-V.bcmap +0 -0
  86. package/assets/vendor/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
  87. package/assets/vendor/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
  88. package/assets/vendor/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
  89. package/assets/vendor/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
  90. package/assets/vendor/pdfjs/cmaps/H.bcmap +0 -0
  91. package/assets/vendor/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
  92. package/assets/vendor/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
  93. package/assets/vendor/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
  94. package/assets/vendor/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
  95. package/assets/vendor/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
  96. package/assets/vendor/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
  97. package/assets/vendor/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
  98. package/assets/vendor/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
  99. package/assets/vendor/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
  100. package/assets/vendor/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
  101. package/assets/vendor/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
  102. package/assets/vendor/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
  103. package/assets/vendor/pdfjs/cmaps/Hankaku.bcmap +0 -0
  104. package/assets/vendor/pdfjs/cmaps/Hiragana.bcmap +0 -0
  105. package/assets/vendor/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
  106. package/assets/vendor/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
  107. package/assets/vendor/pdfjs/cmaps/KSC-H.bcmap +0 -0
  108. package/assets/vendor/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
  109. package/assets/vendor/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
  110. package/assets/vendor/pdfjs/cmaps/KSC-V.bcmap +0 -0
  111. package/assets/vendor/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
  112. package/assets/vendor/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  113. package/assets/vendor/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  114. package/assets/vendor/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
  115. package/assets/vendor/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
  116. package/assets/vendor/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
  117. package/assets/vendor/pdfjs/cmaps/Katakana.bcmap +0 -0
  118. package/assets/vendor/pdfjs/cmaps/LICENSE +36 -0
  119. package/assets/vendor/pdfjs/cmaps/NWP-H.bcmap +0 -0
  120. package/assets/vendor/pdfjs/cmaps/NWP-V.bcmap +0 -0
  121. package/assets/vendor/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
  122. package/assets/vendor/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
  123. package/assets/vendor/pdfjs/cmaps/Roman.bcmap +0 -0
  124. package/assets/vendor/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  125. package/assets/vendor/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  126. package/assets/vendor/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  127. package/assets/vendor/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  128. package/assets/vendor/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  129. package/assets/vendor/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  130. package/assets/vendor/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  131. package/assets/vendor/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  132. package/assets/vendor/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
  133. package/assets/vendor/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
  134. package/assets/vendor/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
  135. package/assets/vendor/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
  136. package/assets/vendor/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
  137. package/assets/vendor/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
  138. package/assets/vendor/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
  139. package/assets/vendor/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
  140. package/assets/vendor/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  141. package/assets/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  142. package/assets/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  143. package/assets/vendor/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  144. package/assets/vendor/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  145. package/assets/vendor/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  146. package/assets/vendor/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  147. package/assets/vendor/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  148. package/assets/vendor/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  149. package/assets/vendor/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  150. package/assets/vendor/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  151. package/assets/vendor/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  152. package/assets/vendor/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  153. package/assets/vendor/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  154. package/assets/vendor/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  155. package/assets/vendor/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  156. package/assets/vendor/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  157. package/assets/vendor/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  158. package/assets/vendor/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  159. package/assets/vendor/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  160. package/assets/vendor/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  161. package/assets/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  162. package/assets/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  163. package/assets/vendor/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
  164. package/assets/vendor/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
  165. package/assets/vendor/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
  166. package/assets/vendor/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
  167. package/assets/vendor/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
  168. package/assets/vendor/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
  169. package/assets/vendor/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
  170. package/assets/vendor/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
  171. package/assets/vendor/pdfjs/cmaps/V.bcmap +0 -0
  172. package/assets/vendor/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
  173. package/assets/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
  174. package/assets/vendor/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
  175. package/assets/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
  176. package/assets/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  177. package/assets/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
  178. package/assets/vendor/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
  179. package/assets/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
  180. package/assets/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  181. package/assets/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
  182. package/assets/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
  183. package/assets/vendor/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
  184. package/assets/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION +102 -0
  185. package/assets/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
  186. package/assets/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  187. package/assets/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
  188. package/assets/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
  189. package/assets/vendor/pdfjs/web/pdf_viewer.css +6989 -0
  190. package/assets/vendor/pdfjs/web/pdf_viewer.mjs +9459 -0
  191. package/lib/client.js +852 -13
  192. package/lib/client.js.map +1 -1
  193. package/lib/index.js +137 -17
  194. package/lib/index.js.map +1 -1
  195. package/package.json +1 -1
  196. package/src/client/compat.ts +31 -0
  197. package/src/client/imagePreview.ts +45 -0
  198. package/src/client/index.ts +46 -8
  199. package/src/client/pdf/pdfLoader.ts +82 -0
  200. package/src/client/pdf/pdfPanel.ts +239 -0
  201. package/src/client/pdfPreview.ts +48 -0
  202. package/src/client/remoteOpenRouter.ts +78 -0
  203. package/src/client/styles/editor.css +17 -0
  204. package/src/client/ui/EditorView.ts +221 -2
  205. package/src/compat.ts +1 -1
  206. package/src/dshVersion.ts +1 -0
  207. package/src/routes.ts +3 -0
  208. package/src/rpc.ts +65 -13
  209. package/src/shared/rpc.ts +50 -2
  210. package/src/store.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-vscode-mode",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
4
4
  "description": "DSH 上的类 VSCode 编码体验:Monaco 编辑器(文件页签/QuickOpen/状态栏,可驻 dsh-better-sidebar 侧边栏与对话同屏)+ Agent 编辑差异审查(整文件差异/采纳/拒绝/归档/回滚)+ 语言服务器(LSP)智能(跳转定义/引用/hover/大纲 + VSIX 扩展市场安装),状态持久化到工作区旁车",
5
5
  "keywords": [
6
6
  "dsh",
@@ -92,3 +92,34 @@ export function patchMethod<T extends object, K extends keyof T>(
92
92
  if (owner[key] === patched) owner[key] = original
93
93
  }
94
94
  }
95
+
96
+ /** patchAccessor 的包装实现形状(original 为补丁前的实时实现)。 */
97
+ export type PatchWrapper = (original: (...args: never[]) => unknown, ...args: never[]) => unknown
98
+
99
+ /**
100
+ * accessor 感知的链式补丁:DSH 0.1.3+ 的 remote 命名空间方法(如
101
+ * remote.session.openWorkspacePath)是 getter-only accessor(Object.defineProperty
102
+ * 仅定义 get,直接赋值在严格模式下抛 TypeError),须整体替换属性描述符。
103
+ * 新 getter 每次访问现场调用原 getter 取最新一次性函数作 original,天然跟随
104
+ * DSH 内部 methods 表变化;data 属性降级走 patchMethod;属性缺失返回 null。
105
+ * @author ddj 2026年09月08号
106
+ * @param owner 目标对象
107
+ * @param key 属性名
108
+ * @param wrapper 包装实现(original 为补丁前的实时实现)
109
+ * @returns 恢复函数(幂等、带归属校验);属性缺失时返回 null
110
+ */
111
+ export function patchAccessor(owner: object, key: string, wrapper: PatchWrapper): (() => void) | null {
112
+ const descriptor = Object.getOwnPropertyDescriptor(owner, key)
113
+ if (!descriptor) return null
114
+ if (typeof descriptor.get !== 'function') return patchMethod(owner, key as never, wrapper as never)
115
+ const rawGet = descriptor.get as (this: object) => unknown
116
+ const patchedGet = function (): unknown {
117
+ const original = (...args: never[]) => (rawGet.call(owner) as (...args: never[]) => unknown)(...args)
118
+ return (...args: never[]) => wrapper(original, ...args)
119
+ }
120
+ Object.defineProperty(owner, key, { configurable: true, enumerable: descriptor.enumerable, get: patchedGet })
121
+ return () => {
122
+ const current = Object.getOwnPropertyDescriptor(owner, key)
123
+ if (current?.get === patchedGet) Object.defineProperty(owner, key, descriptor)
124
+ }
125
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * dsh-vscode-mode client — 图片文件判定与 data URL 组装(编辑区图片预览面板用)。
3
+ * 纯函数、无 DOM 依赖,便于单测;MIME 表收敛在 shared/rpc.ts(host base64 响应同源)。
4
+ * @author ddj 2026年09月08号
5
+ */
6
+ import { IMAGE_MIME } from '../shared/rpc.js'
7
+
8
+ /** 可预览的图片扩展名集合(小写、无点)。 */
9
+ const IMAGE_EXT_SET: Set<string> = new Set(Object.keys(IMAGE_MIME))
10
+
11
+ /**
12
+ * 判断路径是否为可预览的图片文件(取 basename 扩展名,大小写不敏感)。
13
+ * @author ddj 2026年09月08号
14
+ * @param path 文件路径(`/` 或 `\` 分隔均可)
15
+ * @returns 是否图片文件
16
+ */
17
+ export function isImagePath(path: string): boolean {
18
+ const base = String(path || '').split(/[\\/]/).pop() || ''
19
+ const dot = base.lastIndexOf('.')
20
+ if (dot <= 0) return false
21
+ return IMAGE_EXT_SET.has(base.slice(dot + 1).toLowerCase())
22
+ }
23
+
24
+ /**
25
+ * 判断路径是否为 SVG 文件(唯一提供「以文本打开」切换的图片格式)。
26
+ * @author ddj 2026年09月08号
27
+ * @param path 文件路径(`/` 或 `\` 分隔均可)
28
+ * @returns 是否 SVG
29
+ */
30
+ export function isSvgPath(path: string): boolean {
31
+ const base = String(path || '').split(/[\\/]/).pop() || ''
32
+ const dot = base.lastIndexOf('.')
33
+ return dot > 0 && base.slice(dot + 1).toLowerCase() === 'svg'
34
+ }
35
+
36
+ /**
37
+ * base64 文本 → 可直接赋给 `<img src>` 的 data URL。
38
+ * @author ddj 2026年09月08号
39
+ * @param base64 base64 文本(不含 data: 前缀)
40
+ * @param mime MIME 类型(host edrv.read base64 响应携带)
41
+ * @returns data URL 字符串
42
+ */
43
+ export function dataUrlOf(base64: string, mime: string): string {
44
+ return 'data:' + (mime || 'application/octet-stream') + ';base64,' + base64
45
+ }
@@ -25,6 +25,7 @@ import { loadMonaco } from './monaco/loader.js'
25
25
  import { createFileOpenerRegistry, scanSidebar, type FileOpenContext } from './fileOpeners.js'
26
26
  import type { FileOpenerRegistry } from './fileOpeners.js'
27
27
  import { installOpenPathRouter, vscodeOpener, autoValue } from './openPathRouter.js'
28
+ import { patchRemoteOpen, probeRemoteOpen } from './remoteOpenRouter.js'
28
29
  import { setupExtOpen } from './externalOpen.js'
29
30
  import { SettingsContext } from './settingsContext.js'
30
31
  import { SIDEBAR_PLUGIN, pickSettingsBinder, registerSlotSafely } from './compat.js'
@@ -98,6 +99,17 @@ export function apply(ctx: any): void {
98
99
  const binder = pickSettingsBinder(ctx)
99
100
  const settings = binder.scope
100
101
  let selected = autoValue('auto')
102
+ /** 0.1.3+ 会话文件链接路由(remote.session.openWorkspacePath)是否已安装(compatSummary 展示用)。 */
103
+ let remoteOpenInstalled = false
104
+ /** 两条文件链接路由共用的路由日志(openPathRouter / remoteOpenRouter)。 */
105
+ const routeLogger = (message: string): void => console.warn('[dsh-vscode-mode] ' + message)
106
+ /** 两条路由共用的 FileOpenContext:当前会话 id 与工作区 cwd。 */
107
+ const openContext = (): FileOpenContext => {
108
+ const current = sessions?.list?.getSnapshot?.()
109
+ const sessionId = current?.current
110
+ const summary = sessionId ? current?.byId?.[sessionId] : undefined
111
+ return { sessionId, cwd: summary?.cwd } as FileOpenContext
112
+ }
101
113
  /** 可选探测 betterSidebar 服务(不进 inject:缺失会让插件停靠等待,杀死回退路径)。 */
102
114
  let sideService = detectSidebarService(ctx)
103
115
  /** 「添加到对话」动作集:编辑区/Tab 右键菜单注入文件引用与代码块(conversation 服务缺失时各动作安全降级)。 */
@@ -106,7 +118,8 @@ export function apply(ctx: any): void {
106
118
  /** 客户端侧兼容摘要(与 host 报告合并展示;惰性求值保证 HMR 后仍新鲜)。 */
107
119
  const compatSummary = (): CompatAdapter[] => [
108
120
  { name: '设置桥', active: settings !== undefined, note: settings !== undefined ? '使用 ' + binder.service + ' 桥' : '未绑定设置服务(fileOpenTool 持久化不可用)' },
109
- { name: '文件打开路由(workspaces.openPath)', active: Boolean(workspaces?.openPath), note: 'vscode 打开器优先,失败回退系统打开' },
121
+ { name: '文件打开路由(workspaces.openPath)', active: Boolean(workspaces?.openPath), note: 'vscode 打开器优先,失败回退系统打开(0.1.2 及更早 DSH 的对话文件链接主路径)' },
122
+ { name: '会话文件链接路由(remote.session.openWorkspacePath)', active: remoteOpenInstalled, note: '0.1.3+ 对话文件引用/产物打开优先走插件打开器,失败回退系统打开' },
110
123
  { name: '侧边栏打开器(' + SIDEBAR_PLUGIN + ')', active: registry.get(SIDEBAR_PLUGIN) !== undefined, note: registry.get(SIDEBAR_PLUGIN) !== undefined ? '已注册(优先级 80)' : '未检测到侧边栏打开能力' },
111
124
  { name: '侧边栏编辑区(' + SIDEBAR_PLUGIN + ')', active: sideService !== undefined, note: sideService !== undefined ? '编辑区=侧边栏 Tab(对话+编辑同屏)' : '未检测到;安装 dsh-better-sidebar 后刷新启用侧边栏形态(' + SIDEBAR_INSTALL_CMD + ')' },
112
125
  ]
@@ -173,15 +186,40 @@ export function apply(ctx: any): void {
173
186
  workspaces,
174
187
  registry,
175
188
  selected: () => selected,
176
- context: () => {
177
- const current = sessions?.list?.getSnapshot?.()
178
- const sessionId = current?.current
179
- const summary = sessionId ? current?.byId?.[sessionId] : undefined
180
- return { sessionId, cwd: summary?.cwd } as FileOpenContext
181
- },
182
- logger: (message: string) => console.warn('[dsh-vscode-mode] ' + message),
189
+ context: openContext,
190
+ logger: routeLogger,
183
191
  }), 'vscode-mode: file link routing')
184
192
  }
193
+ // 0.1.3+ 会话文件链接主路径:dsh-client-ui-chat 的 openFile 直接调
194
+ // remote.session.openWorkspacePath(session/openWorkspacePath RPC),绕开
195
+ // workspaces.openPath;该 namespace 由 api-remotes 在 loader.await 后挂载,
196
+ // 晚于本插件 apply,且旧版 DSH 可能整段不存在——不加 inject(避免旧版停等
197
+ // 永不激活),改为定时探测有界重试(与侧栏服务探测同款节奏)。
198
+ let remoteRetries = 0
199
+ const retryRemoteOpen = (): void => {
200
+ if (remoteOpenInstalled || remoteRetries >= 15) {
201
+ if (!remoteOpenInstalled) console.warn('[dsh-vscode-mode] 未探测到 remote.session.openWorkspacePath,0.1.3+ 会话文件链接路由未安装')
202
+ return
203
+ }
204
+ remoteRetries += 1
205
+ schedule(() => {
206
+ if (remoteOpenInstalled) return
207
+ const service = probeRemoteOpen(ctx)
208
+ if (!service) {
209
+ retryRemoteOpen()
210
+ return
211
+ }
212
+ const disposer = patchRemoteOpen(service, { registry, selected: () => selected, context: openContext, logger: routeLogger })
213
+ if (!disposer) {
214
+ console.warn('[dsh-vscode-mode] remote.session 存在但 openWorkspacePath 不可补丁,会话文件链接路由未安装')
215
+ return
216
+ }
217
+ remoteOpenInstalled = true
218
+ ctx.effect(() => disposer, 'vscode-mode: remote file link routing')
219
+ console.info('[dsh-vscode-mode] 已安装 0.1.3+ 会话文件链接路由(remote.session.openWorkspacePath)')
220
+ }, 2000)
221
+ }
222
+ retryRemoteOpen()
185
223
 
186
224
  // 中央「文件编辑」页签(旧形态回退):类 VSCode 编辑器;侧边栏形态可用时编辑器住 betterSidebar Tab,
187
225
  // 本页签不注册(避免双实例:Monaco×2 + diff dock 每会话单源抢占)。
@@ -0,0 +1,82 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * dsh-vscode-mode client — pdf.js 加载器(vendor 产物离线随包,/edrv/vendor/pdfjs/*)。
4
+ * 加载链:module script 内按序动态 import build/pdf.mjs(自挂 globalThis.pdfjsLib)→
5
+ * web/pdf_viewer.mjs(从 globalThis.pdfjsLib 解构)→ window.__edrvPdfjs handoff。
6
+ * 用 script 注入而非打包内 import():客户端包是 CJS,bundler 会改写动态 import 破坏 URL 语义。
7
+ * 作者 ddj 2026-09-22
8
+ */
9
+
10
+ /** vendor 根(routes.ts /edrv/vendor 前缀路由分发 assets/vendor/*)。 */
11
+ export const PDFJS_BASE = '/edrv/vendor/pdfjs'
12
+ /** pdf.js Worker 地址(GlobalWorkerOptions.workerSrc)。 */
13
+ export const PDFJS_WORKER_SRC = PDFJS_BASE + '/build/pdf.worker.mjs'
14
+ /** CJK 字符映射目录(中文 PDF 渲染必需,尾斜杠固定)。 */
15
+ export const PDFJS_CMAP_URL = PDFJS_BASE + '/cmaps/'
16
+ /** 标准字体目录(未嵌字体 PDF 兜底,尾斜杠固定)。 */
17
+ export const PDFJS_FONT_URL = PDFJS_BASE + '/standard_fonts/'
18
+ /** 组件层样式(pdfViewer/textLayer/annotationEditor 布局)。 */
19
+ const PDFJS_CSS_URL = PDFJS_BASE + '/web/pdf_viewer.css'
20
+
21
+ let pdfjsPromise = null
22
+
23
+ /**
24
+ * 注入 pdf_viewer.css(幂等;vendor 路由带 etag 协商缓存,重复挂载走 304)。
25
+ * @author ddj 2026年09月22号
26
+ */
27
+ function ensurePdfCss() {
28
+ if (typeof document === 'undefined') return
29
+ if (document.querySelector('link[data-edrv-pdfjs-css]')) return
30
+ const link = document.createElement('link')
31
+ link.rel = 'stylesheet'
32
+ link.href = PDFJS_CSS_URL
33
+ link.dataset.edrvPdfjsCss = '1'
34
+ document.head.appendChild(link)
35
+ }
36
+
37
+ /**
38
+ * 加载 pdf.js(core + viewer 组件层):去重 promise,失败重置允许重试(对齐 monaco loader)。
39
+ * @author ddj 2026年09月22号
40
+ * @returns Promise<{lib, viewer}> lib=build/pdf.mjs(getDocument/GlobalWorkerOptions/编辑器枚举等),viewer=web/pdf_viewer.mjs(PDFViewer/EventBus 等)
41
+ */
42
+ export function loadPdfjs() {
43
+ if (!pdfjsPromise) {
44
+ pdfjsPromise = new Promise((resolve, reject) => {
45
+ ensurePdfCss()
46
+ const boot = () => {
47
+ const handoff = window.__edrvPdfjs
48
+ if (handoff && handoff.lib && handoff.viewer) {
49
+ try {
50
+ handoff.lib.GlobalWorkerOptions.workerSrc = PDFJS_WORKER_SRC
51
+ } catch (error) { /* worker 配置失败留给 getDocument 报错 */ }
52
+ resolve(handoff)
53
+ return
54
+ }
55
+ reject(new Error('pdf.js 模块加载失败:handoff 缺失'))
56
+ }
57
+ const fail = (error) => {
58
+ pdfjsPromise = null
59
+ reject(error instanceof Error ? error : new Error(String(error)))
60
+ }
61
+ const existing = document.querySelector('script[data-edrv-pdfjs-loader]')
62
+ if (existing) {
63
+ // 前一次注入仍在执行:等 handoff 出现(module 评估完即写入)
64
+ existing.addEventListener('edrv-pdfjs-ready', boot, { once: true })
65
+ if (window.__edrvPdfjs) boot()
66
+ return
67
+ }
68
+ const s = document.createElement('script')
69
+ s.type = 'module'
70
+ s.textContent =
71
+ 'import * as lib from "' + PDFJS_BASE + '/build/pdf.mjs";\n' +
72
+ 'import * as viewer from "' + PDFJS_BASE + '/web/pdf_viewer.mjs";\n' +
73
+ 'window.__edrvPdfjs = { lib, viewer };\n' +
74
+ 'document.querySelector("script[data-edrv-pdfjs-loader]")?.dispatchEvent(new Event("edrv-pdfjs-ready"));'
75
+ s.dataset.edrvPdfjsLoader = '1'
76
+ s.onload = boot
77
+ s.onerror = () => fail(new Error('pdf.js loader 脚本加载失败(检查 assets/vendor/pdfjs 完整 / /edrv/vendor 路由可达)'))
78
+ document.head.appendChild(s)
79
+ })
80
+ }
81
+ return pdfjsPromise
82
+ }
@@ -0,0 +1,239 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * dsh-vscode-mode client — PDF 面板控制器(命令式 DOM,React 只渲染外壳 div)。
4
+ * 浏览:PDFViewer 组件(虚拟化渲染 + 文本层);编辑:内置注释编辑器
5
+ * (FreeText 文本框 / Ink 画笔 / Highlight 高亮),保存经 pdfDocument.saveDocument()
6
+ * → base64 → edrv.saveBinary 回写原文件。接线方式对齐官方 viewer(web/app.js)。
7
+ * 作者 ddj 2026-09-22
8
+ */
9
+ import { rpc } from '../rpc.js'
10
+ import { bytesToBase64 } from '../pdfPreview.js'
11
+ import { loadPdfjs, PDFJS_CMAP_URL, PDFJS_FONT_URL } from './pdfLoader.js'
12
+
13
+ /** 无障碍占位 l10n:组件内 translate 为 fire-and-forget,stub 避免触发 locale 404。 */
14
+ const L10N_STUB = {
15
+ get: async (key) => key,
16
+ translate: async () => {},
17
+ getDirection: () => 'ltr',
18
+ }
19
+
20
+ /**
21
+ * PDF 面板控制器工厂。
22
+ * @author ddj 2026年09月22号
23
+ * @param deps 依赖:sessionId/path(保存回写目标)+ 状态栏回调
24
+ * @returns 控制器:mount/destroy/savePdf/setMode/isDirty
25
+ */
26
+ export function createPdfPanel(deps) {
27
+ const status = (t) => { try { deps.setStatus(t) } catch { /* 状态栏回调异常忽略 */ } }
28
+
29
+ let stage = null
30
+ let pageLabel = null
31
+ let eventBus = null
32
+ let busAbort = null
33
+ let editorTypes = null
34
+ let pdfViewer = null
35
+ let pdfDocument = null
36
+ let loadingTask = null
37
+ let modeButtons = null
38
+ let destroyed = false
39
+ let dirty = false
40
+ let saving = false
41
+
42
+ /** 脏状态上抛(tab 脏点)+ 本地记忆。 */
43
+ const setDirty = (value) => {
44
+ dirty = value
45
+ if (modeButtons) modeButtons.save.classList.toggle('edrv-pdf-dirty', value)
46
+ try { deps.onDirtyChange(value) } catch { /* 脏点回调异常忽略 */ }
47
+ }
48
+
49
+ /** 页码指示更新(pagechanging/pagesinit 事件)。 */
50
+ const updatePageLabel = () => {
51
+ if (!pageLabel || !pdfViewer) return
52
+ pageLabel.textContent = pdfViewer.currentPageNumber + ' / ' + pdfViewer.pagesCount
53
+ }
54
+
55
+ /** 编辑模式按钮高亮同步(annotationeditormodechanged 事件;evt.mode 为枚举数值)。 */
56
+ const syncModeButtons = (mode) => {
57
+ if (!modeButtons || !editorTypes) return
58
+ const byValue = { [editorTypes.NONE]: 'none', [editorTypes.FREETEXT]: 'freetext', [editorTypes.INK]: 'ink', [editorTypes.HIGHLIGHT]: 'highlight' }
59
+ const key = byValue[mode] ?? 'none'
60
+ for (const k of Object.keys(modeButtons.modes)) {
61
+ modeButtons.modes[k].classList.toggle('edrv-pdf-mode-on', k === key)
62
+ }
63
+ }
64
+
65
+ /** 编辑模式符号键 → AnnotationEditorType 数值(版本间枚举值有差异,禁止硬编码)。 */
66
+ const modeValueOf = (key) => {
67
+ if (!editorTypes) return null
68
+ return { none: editorTypes.NONE, freetext: editorTypes.FREETEXT, ink: editorTypes.INK, highlight: editorTypes.HIGHLIGHT }[key] ?? null
69
+ }
70
+
71
+ /**
72
+ * 切换编辑模式(进入注释编辑器某模式或退出回 NONE)。
73
+ * @author ddj 2026年09月22号
74
+ * @param key 模式符号键:none/freetext/ink/highlight
75
+ */
76
+ const applyMode = (key) => {
77
+ if (!pdfViewer || !pdfDocument) { status('PDF 未就绪'); return }
78
+ const mode = modeValueOf(key)
79
+ if (mode === null) return
80
+ try {
81
+ pdfViewer.annotationEditorMode = { mode }
82
+ status(key === 'none' ? '已退出编辑' : '编辑模式:' + (key === 'freetext' ? '文本框' : key === 'ink' ? '画笔' : '高亮'))
83
+ } catch (error) {
84
+ status('切换编辑模式失败:' + String(error))
85
+ }
86
+ }
87
+
88
+ /**
89
+ * 保存编辑:saveDocument 导出 → base64 → edrv.saveBinary 回写。
90
+ * @author ddj 2026年09月22号
91
+ * @returns Promise<boolean> 是否成功
92
+ */
93
+ const savePdf = async () => {
94
+ if (saving) return false
95
+ if (!pdfDocument) { status('PDF 未就绪'); return false }
96
+ if (!dirty) { status('PDF 无未保存修改'); return true }
97
+ saving = true
98
+ status('正在保存 PDF…')
99
+ try {
100
+ const data = await pdfDocument.saveDocument()
101
+ const res = await rpc('edrv.saveBinary', { sessionId: deps.sessionId, path: deps.path, content: bytesToBase64(data), encoding: 'base64' })
102
+ if (res && res.ok) {
103
+ setDirty(false)
104
+ try { pdfDocument.annotationStorage.resetModified() } catch { /* 重置失败仅影响下次保存判定 */ }
105
+ status('已保存 ' + new Date().toTimeString().slice(0, 8))
106
+ return true
107
+ }
108
+ status('保存失败')
109
+ try { deps.setError(res?.error ? String(res.error) : '保存失败') } catch { /* 错误回调异常忽略 */ }
110
+ return false
111
+ } catch (error) {
112
+ status('保存失败')
113
+ try { deps.setError('PDF 保存异常:' + String(error)) } catch { /* 错误回调异常忽略 */ }
114
+ return false
115
+ } finally {
116
+ saving = false
117
+ }
118
+ }
119
+
120
+ /** 工具条 DOM(页码/缩放/编辑模式/保存/刷新)。 */
121
+ const buildToolbar = (root) => {
122
+ const bar = document.createElement('div')
123
+ bar.className = 'edrv-pdf-bar'
124
+ const mkBtn = (text, title, onClick, cls) => {
125
+ const b = document.createElement('button')
126
+ b.className = cls || 'edrv-pill edrv-pill-ghost'
127
+ b.textContent = text
128
+ b.title = title
129
+ b.addEventListener('click', onClick)
130
+ bar.appendChild(b)
131
+ return b
132
+ }
133
+ mkBtn('◀', '上一页', () => { if (pdfViewer) pdfViewer.currentPageNumber = Math.max(1, pdfViewer.currentPageNumber - 1) })
134
+ pageLabel = document.createElement('span')
135
+ pageLabel.className = 'edrv-pdf-page'
136
+ pageLabel.textContent = '…'
137
+ bar.appendChild(pageLabel)
138
+ mkBtn('▶', '下一页', () => { if (pdfViewer) pdfViewer.currentPageNumber = Math.min(pdfViewer.pagesCount, pdfViewer.currentPageNumber + 1) })
139
+ mkBtn('−', '缩小', () => { try { pdfViewer.decreaseScale() } catch { /* 缩放失败忽略 */ } }, 'edrv-pill edrv-pill-ghost edrv-pdf-zoom')
140
+ mkBtn('+', '放大', () => { try { pdfViewer.increaseScale() } catch { /* 缩放失败忽略 */ } }, 'edrv-pill edrv-pill-ghost edrv-pdf-zoom')
141
+ bar.appendChild(document.createElement('span')).className = 'edrv-pdf-sep'
142
+ const modes = {}
143
+ modes.freetext = mkBtn('✎ 文本框', '插入文本框注释', () => applyMode('freetext'))
144
+ modes.ink = mkBtn('🖌 画笔', '手绘画笔注释', () => applyMode('ink'))
145
+ modes.highlight = mkBtn('🖍 高亮', '高亮选中文本', () => applyMode('highlight'))
146
+ modes.none = mkBtn('☐ 选择', '退出编辑模式', () => applyMode('none'))
147
+ modeButtons = { modes, save: null }
148
+ bar.appendChild(document.createElement('span')).className = 'edrv-pdf-sep'
149
+ mkBtn('⟳', '重新加载文件', () => { try { deps.onReload() } catch { /* 刷新回调异常忽略 */ } })
150
+ modeButtons.save = mkBtn('💾 保存', '保存修改到文件 (Ctrl+S)', () => { void savePdf() })
151
+ root.appendChild(bar)
152
+ }
153
+
154
+ /**
155
+ * 挂载面板并加载 PDF 字节。
156
+ * @author ddj 2026年09月22号
157
+ * @param host React 渲染的外壳 div(挂载后保持引用稳定)
158
+ * @param bytes PDF 原始字节
159
+ * @returns Promise<void> 渲染就绪或失败(失败置 deps.setError)
160
+ */
161
+ const mount = async (host, bytes) => {
162
+ try {
163
+ const pdfjs = await loadPdfjs()
164
+ if (destroyed) return
165
+ editorTypes = pdfjs.lib.AnnotationEditorType
166
+ busAbort = new AbortController()
167
+ const onBus = (name, fn) => eventBus.on(name, fn, { signal: busAbort.signal })
168
+ const root = document.createElement('div')
169
+ root.className = 'edrv-pdf'
170
+ buildToolbar(root)
171
+ stage = document.createElement('div')
172
+ stage.className = 'edrv-pdf-stage'
173
+ const viewerDiv = document.createElement('div')
174
+ viewerDiv.className = 'pdfViewer edrv-pdf-viewer'
175
+ stage.appendChild(viewerDiv)
176
+ root.appendChild(stage)
177
+ host.textContent = ''
178
+ host.appendChild(root)
179
+
180
+ eventBus = new pdfjs.viewer.EventBus()
181
+ const linkService = new pdfjs.viewer.PDFLinkService({ eventBus })
182
+ pdfViewer = new pdfjs.viewer.PDFViewer({
183
+ container: stage,
184
+ viewer: viewerDiv,
185
+ eventBus,
186
+ linkService,
187
+ l10n: L10N_STUB,
188
+ textLayerMode: 1,
189
+ annotationEditorMode: editorTypes.NONE,
190
+ })
191
+ linkService.setViewer(pdfViewer)
192
+ onBus('pagechanging', updatePageLabel)
193
+ onBus('pagesinit', () => {
194
+ try { pdfViewer.currentScaleValue = 'page-width' } catch { /* 初始缩放失败用默认值 */ }
195
+ updatePageLabel()
196
+ })
197
+ onBus('annotationeditormodechanged', (evt) => syncModeButtons(evt?.mode))
198
+
199
+ loadingTask = pdfjs.lib.getDocument({ data: bytes, cMapUrl: PDFJS_CMAP_URL, cMapPacked: true, standardFontDataUrl: PDFJS_FONT_URL })
200
+ pdfDocument = await loadingTask.promise
201
+ if (destroyed) { void loadingTask.destroy(); return }
202
+ const storage = pdfDocument.annotationStorage
203
+ storage.onSetModified = () => setDirty(true)
204
+ storage.onResetModified = () => setDirty(false)
205
+ pdfViewer.setDocument(pdfDocument)
206
+ linkService.setDocument(pdfDocument)
207
+ status('PDF 已就绪(' + pdfDocument.numPages + ' 页)')
208
+ } catch (error) {
209
+ if (destroyed) return
210
+ const message = String((error && (error.message || error.name)) || error)
211
+ const friendly = message.includes('password') ? '不支持加密 PDF' : 'PDF 加载失败:' + message
212
+ status('PDF 加载失败')
213
+ try { deps.setError(friendly) } catch { /* 错误回调异常忽略 */ }
214
+ }
215
+ }
216
+
217
+ /** 是否有未保存修改(EditorView Ctrl+S 分派用)。 */
218
+ const isDirty = () => dirty
219
+
220
+ /** 销毁:解绑事件、销毁文档与 DOM(重复调用安全)。 */
221
+ const destroy = () => {
222
+ destroyed = true
223
+ try { busAbort?.abort() } catch { /* 已中止 */ }
224
+ try { pdfViewer?.cleanup() } catch { /* 视图已销毁 */ }
225
+ try { void loadingTask?.destroy() } catch { /* 任务已销毁 */ }
226
+ try { void pdfDocument?.destroy() } catch { /* 文档已销毁 */ }
227
+ if (stage?.parentNode) stage.parentNode.removeChild(stage)
228
+ stage = null
229
+ eventBus = null
230
+ busAbort = null
231
+ editorTypes = null
232
+ pdfViewer = null
233
+ pdfDocument = null
234
+ loadingTask = null
235
+ modeButtons = null
236
+ }
237
+
238
+ return { mount, destroy, savePdf, isDirty }
239
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * dsh-vscode-mode client — PDF 文件判定与 base64 编解码(编辑区 PDF 面板用)。
3
+ * 纯函数、无 DOM 依赖,便于单测;PDF_MIME 收敛在 shared/rpc.ts(host base64 响应同源)。
4
+ * @author ddj 2026年09月22号
5
+ */
6
+ import { PDF_MIME } from '../shared/rpc.js'
7
+
8
+ export { PDF_MIME }
9
+
10
+ /**
11
+ * 判断路径是否为 PDF 文件(取 basename 扩展名,大小写不敏感)。
12
+ * @author ddj 2026年09月22号
13
+ * @param path 文件路径(`/` 或 `\` 分隔均可)
14
+ * @returns 是否 PDF 文件
15
+ */
16
+ export function isPdfPath(path: string): boolean {
17
+ const base = String(path || '').split(/[\\/]/).pop() || ''
18
+ const dot = base.lastIndexOf('.')
19
+ return dot > 0 && base.slice(dot + 1).toLowerCase() === 'pdf'
20
+ }
21
+
22
+ /**
23
+ * base64 文本 → 二进制字节(喂给 pdf.js getDocument 的 data 入参)。
24
+ * @author ddj 2026年09月22号
25
+ * @param base64 base64 文本(不含 data: 前缀,host edrv.read base64 响应携带)
26
+ * @returns 原始字节
27
+ */
28
+ export function base64ToBytes(base64: string): Uint8Array {
29
+ const bin = atob(String(base64 || ''))
30
+ const bytes = new Uint8Array(bin.length)
31
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)
32
+ return bytes
33
+ }
34
+
35
+ /**
36
+ * 二进制字节 → base64 文本(编辑保存回传 host 前的编码步骤)。
37
+ * @author ddj 2026年09月22号
38
+ * @param bytes 原始字节
39
+ * @returns base64 文本
40
+ */
41
+ export function bytesToBase64(bytes: Uint8Array): string {
42
+ let bin = ''
43
+ const chunk = 0x8000
44
+ for (let i = 0; i < bytes.length; i += chunk) {
45
+ bin += String.fromCharCode(...bytes.subarray(i, i + chunk))
46
+ }
47
+ return btoa(bin)
48
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * dsh-vscode-mode client — DSH 对话文件链接的 0.1.3+ 路由(remote.session 面)。
3
+ * 0.1.3 起 dsh-client-ui-chat 的 openFile 直接调 ctx.remote.session.openWorkspacePath
4
+ * (浏览器→host RPC session/openWorkspacePath),不再经过 workspaces.openPath,
5
+ * 旧路由补丁因此落空;该方法是 getter-only accessor,补丁经 compat.patchAccessor
6
+ * 换描述符安装。行为与 openPathRouter 对齐:插件打开器优先,"."(打开工作区文件夹)、
7
+ * 无可用打开器或打开失败时透传原实现(系统默认应用打开)。
8
+ * 纯逻辑,node 环境可单测(不依赖 DOM / events 模块)。
9
+ * 作者 ddj 2026年09月08号
10
+ */
11
+ import { patchAccessor } from './compat.js'
12
+ import { isAvailable, selectOpener, type FileOpenContext, type FileOpenerRegistry } from './fileOpeners.js'
13
+
14
+ /** session.openWorkspacePath 的结果形状(host 返回 { opened: true };客户端不做输出校验)。 */
15
+ export interface OpenPathResult {
16
+ ok: boolean
17
+ value?: { opened: boolean }
18
+ }
19
+
20
+ /** openWorkspacePath 的请求形状;path 为 "." 时表示打开工作区文件夹。 */
21
+ export interface OpenPathRequest {
22
+ path?: unknown
23
+ }
24
+
25
+ /** 补丁前 openWorkspacePath 的实时实现形状。 */
26
+ type RemoteOpen = (request: OpenPathRequest) => Promise<OpenPathResult>
27
+
28
+ /** 插件打开器接管成功时返回给调用方(chat 只看 ok,产物区读 value)。 */
29
+ const OPENED_RESULT: OpenPathResult = { ok: true, value: { opened: true } }
30
+
31
+ export interface RemoteOpenRouterOptions {
32
+ registry: FileOpenerRegistry
33
+ selected: () => string
34
+ context: () => FileOpenContext
35
+ logger?: (message: string) => void
36
+ }
37
+
38
+ /**
39
+ * 结构化探测 remote.session 命名空间服务及其 openWorkspacePath 方法。
40
+ * 旧版 DSH 无该 namespace、或 api-remotes 尚未挂载时返回 undefined。
41
+ * @author ddj 2026年09月08号
42
+ * @param ctx 客户端服务上下文
43
+ * @returns 命名空间服务实例;不可用返回 undefined
44
+ */
45
+ export function probeRemoteOpen(ctx: { get: (name: string) => unknown }): object | undefined {
46
+ try {
47
+ const service = ctx.get('remote.session') as { openWorkspacePath?: unknown } | undefined
48
+ if (!service || typeof service.openWorkspacePath !== 'function') return undefined
49
+ return service
50
+ } catch {
51
+ return undefined
52
+ }
53
+ }
54
+
55
+ /**
56
+ * 安装可卸载的 remote.session.openWorkspacePath 路由(0.1.3+ 会话文件链接主路径)。
57
+ * @author ddj 2026年09月08号
58
+ * @param service probeRemoteOpen 命中的命名空间服务
59
+ * @param options 路由依赖
60
+ * @returns 恢复函数;目标属性缺失时返回 null(调用方停止安装并告警)
61
+ */
62
+ export function patchRemoteOpen(service: object, options: RemoteOpenRouterOptions): (() => void) | null {
63
+ return patchAccessor(service, 'openWorkspacePath', async (original, ...args) => {
64
+ const fallback = original as unknown as RemoteOpen
65
+ const request = (args[0] ?? {}) as OpenPathRequest
66
+ const path = typeof request.path === 'string' ? request.path : ''
67
+ if (!path || path === '.') return fallback(request)
68
+ const opener = selectOpener(options.registry, options.selected())
69
+ if (!opener || !isAvailable(opener)) return fallback(request)
70
+ try {
71
+ await opener.open(path, options.context())
72
+ return OPENED_RESULT
73
+ } catch (error) {
74
+ options.logger?.('文件打开器 ' + opener.id + ' 失败,回退系统打开: ' + String(error))
75
+ return fallback(request)
76
+ }
77
+ })
78
+ }
@@ -58,6 +58,23 @@
58
58
  [data-edrv-view] .edrv-minus-item::before { content: ''; position: absolute; left: 4px; top: 50%; width: 8px; height: 2px; transform: translateY(-50%); border-radius: 2px; background: #d9534f; }
59
59
  [data-edrv-view] .edrv-monaco-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }
60
60
  [data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; min-width: 0; min-height: 0; }
61
+ [data-edrv-view] .edrv-imgview { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
62
+ [data-edrv-view] .edrv-imgview-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }
63
+ [data-edrv-view] .edrv-imgview-meta { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; }
64
+ [data-edrv-view] .edrv-imgview-stage { flex: 1 1 auto; min-height: 0; display: flex; align-items: center; justify-content: center; overflow: auto; padding: 14px; background-color: var(--dsw-alias-bg-layer-1, #f0f4f4); background-image: conic-gradient(rgba(128,128,128,.14) 25%, transparent 0 50%, rgba(128,128,128,.14) 0 75%, transparent 0); background-size: 16px 16px; }
65
+ [data-edrv-view] .edrv-imgview-img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 4px; box-shadow: 0 2px 12px rgba(31,41,51,.18); }
66
+ [data-edrv-view] .edrv-imgview-broken { display: flex; flex-direction: column; align-items: center; gap: 10px; font-size: 13px; color: var(--dsw-alias-label-tertiary, #777); }
67
+ [data-edrv-view] .edrv-pdf-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }
68
+ [data-edrv-view] .edrv-pdf-host > .edrv-pdf { position: absolute; inset: 0; }
69
+ [data-edrv-view] .edrv-pdf { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
70
+ [data-edrv-view] .edrv-pdf-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }
71
+ [data-edrv-view] .edrv-pdf-page { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; min-width: 52px; text-align: center; }
72
+ [data-edrv-view] .edrv-pdf-zoom { min-width: 26px; text-align: center; padding: 3px 7px; }
73
+ [data-edrv-view] .edrv-pdf-sep { width: 1px; height: 16px; background: var(--dsw-alias-border-l1, #e0e6e8); margin: 0 2px; }
74
+ [data-edrv-view] .edrv-pdf-mode-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-brand-primary, #0f9d58); border-color: var(--dsw-alias-brand-primary, #0f9d58); }
75
+ [data-edrv-view] .edrv-pdf-dirty { color: var(--dsw-alias-state-warn-primary, #d97706); border-color: var(--dsw-alias-state-warn-primary, #d97706); }
76
+ [data-edrv-view] .edrv-pdf-stage { position: absolute; top: 34px; left: 0; right: 0; bottom: 0; overflow: auto; }
77
+ [data-edrv-view] .edrv-pdf-stage .edrv-pdf-viewer { margin: 0 auto; }
61
78
  [data-edrv-view] .edrv-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; height: 100%; color: var(--dsw-alias-label-tertiary, #777); font-size: 13px; }
62
79
  [data-edrv-view] .edrv-diffbar { position: absolute; left: 0; right: 0; bottom: 10px; margin: 0 auto; z-index: 25; width: fit-content; max-width: calc(100% - 20px); border-radius: 12px; background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); box-shadow: none; overflow: visible; display: flex; flex-direction: column; }
63
80
  [data-edrv-view] .edrv-editor-area > .edrv-diffbar { max-height: calc(100% - 20px); }