dsh-vscode-mode 0.1.23 → 0.1.26
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 +8 -5
- package/lib/client.js +409 -30
- package/lib/client.js.map +1 -1
- package/lib/index.js +23 -2
- package/lib/index.js.map +1 -1
- package/package.json +4 -1
- package/src/client/monaco/diffRender.ts +3 -0
- package/src/client/sidebar/panels/FileExplorer.ts +42 -2
- package/src/client/state/explorerCache.ts +112 -0
- package/src/client/state/viewStateCache.ts +119 -0
- package/src/client/styles/editor.css +1 -0
- package/src/client/ui/ContextMenu.ts +37 -12
- package/src/client/ui/EditorView.ts +88 -4
- package/src/client/ui/menuPosition.ts +63 -0
- package/src/routes.ts +18 -2
- package/src/rpc.ts +7 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — 右键菜单 viewport 定位纯函数。
|
|
3
|
+
* 将目标行锚点与视口边界计算从 React 渲染层拆出,便于复用和单测。
|
|
4
|
+
* 作者 ddj 2026-08-28
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface MenuPosition {
|
|
8
|
+
left: number
|
|
9
|
+
top: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface MenuAnchorRect {
|
|
13
|
+
top: number
|
|
14
|
+
right: number
|
|
15
|
+
width: number
|
|
16
|
+
height: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 将菜单坐标限制在 viewport 内。
|
|
21
|
+
* @author ddj 2026年08月28号
|
|
22
|
+
* @param x 期望的 viewport x 坐标
|
|
23
|
+
* @param y 期望的 viewport y 坐标
|
|
24
|
+
* @param viewportWidth viewport 宽度
|
|
25
|
+
* @param viewportHeight viewport 高度
|
|
26
|
+
* @param menuWidth 菜单估算宽度
|
|
27
|
+
* @param menuHeight 菜单估算高度
|
|
28
|
+
* @returns 限制后的菜单位置
|
|
29
|
+
*/
|
|
30
|
+
export function clampMenuPosition(
|
|
31
|
+
x: number,
|
|
32
|
+
y: number,
|
|
33
|
+
viewportWidth: number,
|
|
34
|
+
viewportHeight: number,
|
|
35
|
+
menuWidth: number,
|
|
36
|
+
menuHeight: number,
|
|
37
|
+
): MenuPosition {
|
|
38
|
+
const maxLeft = Math.max(4, viewportWidth - menuWidth)
|
|
39
|
+
const maxTop = Math.max(4, viewportHeight - menuHeight)
|
|
40
|
+
return {
|
|
41
|
+
left: Math.max(4, Math.min(x, maxLeft)),
|
|
42
|
+
top: Math.max(4, Math.min(y, maxTop)),
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 将文件树行右键菜单锚定到目标行右侧;无法测量时回退到鼠标坐标。
|
|
48
|
+
* @author ddj 2026年08月28号
|
|
49
|
+
* @param rect 目标行的布局矩形
|
|
50
|
+
* @param fallbackX 鼠标 viewport x 坐标
|
|
51
|
+
* @param fallbackY 鼠标 viewport y 坐标
|
|
52
|
+
* @returns 菜单初始位置
|
|
53
|
+
*/
|
|
54
|
+
export function rowMenuPosition(
|
|
55
|
+
rect: MenuAnchorRect | null | undefined,
|
|
56
|
+
fallbackX: number,
|
|
57
|
+
fallbackY: number,
|
|
58
|
+
): MenuPosition {
|
|
59
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
60
|
+
return { left: fallbackX, top: fallbackY }
|
|
61
|
+
}
|
|
62
|
+
return { left: rect.right + 4, top: rect.top }
|
|
63
|
+
}
|
package/src/routes.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 迁移自原 src/index.ts 的路由部分,路径/行为一字不改。
|
|
4
4
|
* 作者 ddj 2026-08-20
|
|
5
5
|
*/
|
|
6
|
-
import { readFile } from 'node:fs/promises'
|
|
6
|
+
import { readFile, stat } from 'node:fs/promises'
|
|
7
7
|
import { dirname, extname, join, normalize, resolve, sep } from 'node:path'
|
|
8
8
|
import { fileURLToPath } from 'node:url'
|
|
9
9
|
import { ROUTE_PREFIX, noteOwnRoute, resetOwnRoutes, routeConflict } from './compat.js'
|
|
@@ -124,10 +124,11 @@ export function registerRoutes(
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
// Monaco Editor AMD 构建:/edrv/vendor/* → assets/vendor/*(路径穿越防护 + 按扩展名 MIME)
|
|
127
|
+
// ETag 协商缓存:弱 etag(mtime+size),配合 max-age 让浏览器 304 复用,插件升级内容变化自动失效。
|
|
127
128
|
ctx.effect(() => guarded({
|
|
128
129
|
kind: 'prefix',
|
|
129
130
|
path: VENDOR_PREFIX,
|
|
130
|
-
handler: async (req: { method?: string; url?: string }, res: { statusCode?: number; setHeader: (k: string, v: string) => void; end: (b?: string | Uint8Array) => void }) => {
|
|
131
|
+
handler: async (req: { method?: string; url?: string; headers?: Record<string, string | string[] | undefined> }, res: { statusCode?: number; setHeader: (k: string, v: string) => void; end: (b?: string | Uint8Array) => void }) => {
|
|
131
132
|
try {
|
|
132
133
|
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
133
134
|
res.statusCode = 405
|
|
@@ -143,10 +144,25 @@ export function registerRoutes(
|
|
|
143
144
|
res.end()
|
|
144
145
|
return
|
|
145
146
|
}
|
|
147
|
+
const info = await stat(target).catch(() => null)
|
|
148
|
+
if (!info || !info.isFile()) { res.statusCode = 404; res.end(); return }
|
|
149
|
+
const etag = 'W/"' + info.mtimeMs.toString(16) + '-' + info.size.toString(16) + '"'
|
|
150
|
+
const reqEtag = (() => {
|
|
151
|
+
const raw = req.headers?.['if-none-match']
|
|
152
|
+
if (Array.isArray(raw)) return raw[0]
|
|
153
|
+
return raw
|
|
154
|
+
})()
|
|
155
|
+
if (reqEtag === etag) {
|
|
156
|
+
res.statusCode = 304
|
|
157
|
+
res.setHeader('etag', etag)
|
|
158
|
+
res.end()
|
|
159
|
+
return
|
|
160
|
+
}
|
|
146
161
|
const body = await readFile(target)
|
|
147
162
|
res.statusCode = 200
|
|
148
163
|
res.setHeader('content-type', VENDOR_MIME[extname(target)] ?? 'application/octet-stream')
|
|
149
164
|
res.setHeader('cache-control', 'public, max-age=3600')
|
|
165
|
+
res.setHeader('etag', etag)
|
|
150
166
|
res.end(body)
|
|
151
167
|
} catch (error) {
|
|
152
168
|
res.statusCode = 404
|
package/src/rpc.ts
CHANGED
|
@@ -34,6 +34,8 @@ import { revealInExplorer } from './reveal.js'
|
|
|
34
34
|
|
|
35
35
|
/** cwd → Promise 链:串行化 debug 日志追加(fs read+write 非原子,避免并发丢行)。 */
|
|
36
36
|
const debugWriteQueues = new Map<string, Promise<void>>()
|
|
37
|
+
/** debug 日志单文件上限:超限截断保留尾部,防文件无限增长拖慢每次追加。 */
|
|
38
|
+
const DEBUG_LOG_CAP = 512 * 1024
|
|
37
39
|
|
|
38
40
|
/** 记录 → 客户端视图(不含 before 全文,仅长度)。 */
|
|
39
41
|
function recView(record: DiffRecord): RecordView {
|
|
@@ -300,7 +302,11 @@ export function buildHandlers(ctx: Ctx, registry: Registry, searcher = newSearch
|
|
|
300
302
|
const target = await fs.resolve('.dsh-edit-review-debug.log', { cwd: sc.cwd })
|
|
301
303
|
const old = await fs.readText(target).catch(() => '')
|
|
302
304
|
const line = new Date().toISOString() + ' ' + text + '\n'
|
|
303
|
-
|
|
305
|
+
// 超上限时截断保留尾部:单文件体积有界,每次追加的读改写成本不随会话时长增长
|
|
306
|
+
const next = old.length + line.length > DEBUG_LOG_CAP
|
|
307
|
+
? old.slice(-Math.floor(DEBUG_LOG_CAP / 2)) + line
|
|
308
|
+
: old + line
|
|
309
|
+
await fs.writeText(target, next, void 0, void 0, policyOf(ctx, sc.session))
|
|
304
310
|
} catch (e) { /* 写日志失败忽略 */ }
|
|
305
311
|
})
|
|
306
312
|
debugWriteQueues.set(sc.cwd, task)
|