dsh-vscode-mode 0.19.0 → 0.19.1
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 +6 -4
- package/lib/client.js +142 -29
- package/lib/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/externalOpen.ts +7 -1
- package/src/client/monaco/loader.ts +2 -0
- package/src/client/monaco/theme.ts +11 -0
- package/src/client/openReceipt.ts +4 -1
- package/src/client/requestId.ts +20 -0
- package/src/client/state/uiStatePersist.ts +84 -23
- package/src/client/ui/EditorView.ts +3 -5
- package/src/shared/snippets.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vscode-mode",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.1",
|
|
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": [
|
|
@@ -10,6 +10,7 @@ import { openDeepLink } from './openFlow.js'
|
|
|
10
10
|
import { disposeOpenWait } from './openReceipt.js'
|
|
11
11
|
import { rpc } from './rpc.js'
|
|
12
12
|
import { log } from './log.js'
|
|
13
|
+
import { createRequestId } from './requestId.js'
|
|
13
14
|
|
|
14
15
|
/** toast 自隐时长。 */
|
|
15
16
|
const TOAST_MS = 5000
|
|
@@ -68,7 +69,12 @@ function startPresencePoll(ctx: unknown): () => void {
|
|
|
68
69
|
if (typeof window === 'undefined') return () => {}
|
|
69
70
|
const marker = window as unknown as { __edrvExtPoll?: () => void }
|
|
70
71
|
if (typeof marker.__edrvExtPoll === 'function') marker.__edrvExtPoll()
|
|
71
|
-
const
|
|
72
|
+
const generatedId = createRequestId()
|
|
73
|
+
if (!generatedId) {
|
|
74
|
+
log.warn('安全随机源不可用,已停用外部打开心跳')
|
|
75
|
+
return () => {}
|
|
76
|
+
}
|
|
77
|
+
const clientId: string = generatedId
|
|
72
78
|
let disposed = false
|
|
73
79
|
let polling = false
|
|
74
80
|
let executing = false
|
|
@@ -65,6 +65,8 @@ export const LANG_BY_EXT = {
|
|
|
65
65
|
// 工程/配置文件(拿到正确语法分色,而非 plaintext)
|
|
66
66
|
csproj: 'xml', props: 'xml', targets: 'xml', plist: 'xml',
|
|
67
67
|
lua51: 'lua', luac: 'lua',
|
|
68
|
+
v: 'verilog', vh: 'verilog', sv: 'systemverilog', svh: 'systemverilog',
|
|
69
|
+
tcl: 'tcl', xdc: 'tcl', sdc: 'tcl', ucf: 'tcl',
|
|
68
70
|
gitattributes: 'ini', editorconfig: 'ini', env: 'ini', properties: 'ini',
|
|
69
71
|
json5: 'jsonc', log: 'plaintext', txt: 'plaintext',
|
|
70
72
|
// 代码片段文件按 JSON 高亮(VS Code 同款:.code-snippets 是带注释的 JSON)
|
|
@@ -291,6 +291,17 @@ const COLOR_TOKEN_MAP = [
|
|
|
291
291
|
/** 已应用的官方主题序号(模块级计数,保证主题 id 每次唯一)。 */
|
|
292
292
|
let officialSeq = 0
|
|
293
293
|
|
|
294
|
+
/** @public @author ddj 2026年10月23号
|
|
295
|
+
* Read the host content font on editor creation, preserving the original line density.
|
|
296
|
+
* @returns Monaco font options; invalid or unavailable CSS falls back to 16px.
|
|
297
|
+
*/
|
|
298
|
+
export function editorFont(): { fontSize: number; lineHeight: number } {
|
|
299
|
+
const raw = readCssVar('--dsh-content-font-size') ?? ''
|
|
300
|
+
const parsed = /^\d+(?:\.\d+)?px$/i.test(raw) ? Number(raw.slice(0, -2)) : NaN
|
|
301
|
+
const fontSize = Number.isFinite(parsed) && parsed > 0 ? parsed : 16
|
|
302
|
+
return { fontSize, lineHeight: Math.round(fontSize * 21 / 16) }
|
|
303
|
+
}
|
|
304
|
+
|
|
294
305
|
/**
|
|
295
306
|
* 读官方 CSS 变量(先 body 后文档根;未定义/空值/未解析 var() 返回 undefined)。
|
|
296
307
|
* 依据运行态实测:DSH 的 --dsw-alias-* 定义在 body 上,仅 --shiki-token-* 在根。
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { openEditorView } from './events.js'
|
|
2
|
+
import { createRequestId } from './requestId.js'
|
|
2
3
|
|
|
3
4
|
import type { PlanDocument } from './planState.js'
|
|
4
5
|
|
|
@@ -41,7 +42,9 @@ export function finishOpen(requestId: string, success: boolean, error?: string):
|
|
|
41
42
|
*/
|
|
42
43
|
export function requestOpen(path: string | null, line?: number, column?: number, sessionId = '', options: OpenOptions = {}): Promise<boolean> {
|
|
43
44
|
if (typeof window === 'undefined' || !sessionId || options.signal?.aborted) return Promise.resolve(false)
|
|
44
|
-
const
|
|
45
|
+
const requestId = createRequestId()
|
|
46
|
+
if (!requestId) return Promise.resolve(false)
|
|
47
|
+
const request: EditorRequest = { requestId, sessionId, path, line, column }
|
|
45
48
|
if (options.preview !== undefined) request.preview = options.preview
|
|
46
49
|
if (options.plan) request.plan = options.plan
|
|
47
50
|
return new Promise((resolve) => {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** @public @author ddj 2026年10月23号
|
|
2
|
+
* Generate an unpredictable UUID, including on insecure LAN HTTP origins.
|
|
3
|
+
* @returns UUID v4, or undefined when secure randomness is unavailable.
|
|
4
|
+
*/
|
|
5
|
+
export function createRequestId(): string | undefined {
|
|
6
|
+
try {
|
|
7
|
+
if (typeof globalThis.crypto?.randomUUID === 'function') return globalThis.crypto.randomUUID()
|
|
8
|
+
} catch { /* Fall through to the cryptographic API available on HTTP origins. */ }
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const bytes = new Uint8Array(16)
|
|
12
|
+
globalThis.crypto.getRandomValues(bytes)
|
|
13
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40
|
|
14
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80
|
|
15
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')
|
|
16
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
|
|
17
|
+
} catch {
|
|
18
|
+
return undefined
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -97,6 +97,8 @@ export interface UiStateMirrorDeps {
|
|
|
97
97
|
export interface UiStateMirror {
|
|
98
98
|
/** 包装 localStorage 写口(写口不可用时返回 false)。 */
|
|
99
99
|
install(): boolean
|
|
100
|
+
/** Release storage hooks and pending hydration work. */
|
|
101
|
+
dispose(): void
|
|
100
102
|
/** 取回 host 镜像并回填本地缺失键,返回回填条数。 */
|
|
101
103
|
hydrate(): Promise<number>
|
|
102
104
|
/** 立即上送待推送批次,返回最终丢弃的键数(0 = 全部落盘;供卸载与测试驱动)。 */
|
|
@@ -194,6 +196,8 @@ export function createUiStateMirror(deps: UiStateMirrorDeps): UiStateMirror {
|
|
|
194
196
|
let suppressed = 0
|
|
195
197
|
let done = false
|
|
196
198
|
let installed = false
|
|
199
|
+
let disposed = false
|
|
200
|
+
let restoreStorage: (() => void) | undefined
|
|
197
201
|
let lastError = ''
|
|
198
202
|
/** 水合尝试次数(失败重试;超过 `HYDRATE_RETRY_MAX` 放弃并解禁)。 */
|
|
199
203
|
let attempts = 0
|
|
@@ -203,14 +207,19 @@ export function createUiStateMirror(deps: UiStateMirrorDeps): UiStateMirror {
|
|
|
203
207
|
|
|
204
208
|
/** 记入待推送批次并安排去抖上送(水合期间或非镜像键 → 跳过)。 */
|
|
205
209
|
function queue(key: string, value: string | null): void {
|
|
206
|
-
if (suppressed > 0 || !isMirroredKey(key)) return
|
|
210
|
+
if (disposed || suppressed > 0 || !isMirroredKey(key)) return
|
|
207
211
|
const bucket = isGlobalKey(key) ? pending.globals : pending.keys
|
|
208
212
|
bucket[key] = value
|
|
209
213
|
if (timer !== null) cancel(timer)
|
|
210
|
-
|
|
214
|
+
try {
|
|
215
|
+
timer = schedule(() => {
|
|
216
|
+
timer = null
|
|
217
|
+
if (!disposed) void flush()
|
|
218
|
+
}, FLUSH_DEBOUNCE_MS)
|
|
219
|
+
} catch (error) {
|
|
211
220
|
timer = null
|
|
212
|
-
|
|
213
|
-
}
|
|
221
|
+
log('界面状态调度失败: ' + String(error))
|
|
222
|
+
}
|
|
214
223
|
}
|
|
215
224
|
|
|
216
225
|
/** 上送一批补丁(失败重试一次)。 */
|
|
@@ -273,7 +282,7 @@ export function createUiStateMirror(deps: UiStateMirrorDeps): UiStateMirror {
|
|
|
273
282
|
|
|
274
283
|
/** @private @author ddj 2026年10月12号 @returns Durable refresh snapshot. @throws When state is unreadable, unsettled or rejected by Host. @sideEffects Resends current local state after waiting for inflight writes. */
|
|
275
284
|
async function flushStrict(): Promise<void> {
|
|
276
|
-
if (!settleDone || !installed) throw new Error('界面状态镜像尚未就绪')
|
|
285
|
+
if (disposed || !settleDone || !installed) throw new Error('界面状态镜像尚未就绪')
|
|
277
286
|
if (inflight) await inflight
|
|
278
287
|
// Read directly: the lenient key scanner intentionally hides storage errors.
|
|
279
288
|
for (let index = 0; index < storage.length; index++) {
|
|
@@ -338,7 +347,7 @@ export function createUiStateMirror(deps: UiStateMirrorDeps): UiStateMirror {
|
|
|
338
347
|
* 未结束前界面不得写存档,否则空初值会覆盖 host 上的真实状态。
|
|
339
348
|
*/
|
|
340
349
|
function settle(): void {
|
|
341
|
-
if (settleDone) return
|
|
350
|
+
if (disposed || settleDone) return
|
|
342
351
|
settleDone = true
|
|
343
352
|
emitReady()
|
|
344
353
|
report('水合阶段结束(就绪;' + (done ? '已回填' : '未回填,退回纯 localStorage') + ')')
|
|
@@ -349,9 +358,11 @@ export function createUiStateMirror(deps: UiStateMirrorDeps): UiStateMirror {
|
|
|
349
358
|
* @returns 本次回填键数(失败/重试中为 0)
|
|
350
359
|
*/
|
|
351
360
|
async function hydrate(): Promise<number> {
|
|
361
|
+
if (disposed) return 0
|
|
352
362
|
try {
|
|
353
363
|
report('水合开始(origin=' + originOf() + ',第 ' + (attempts + 1) + ' 次)')
|
|
354
364
|
const snapshot = await deps.fetch(deps.sessionIdOf?.())
|
|
365
|
+
if (disposed) return 0
|
|
355
366
|
// 先搬运本 origin 既有状态(只含换 origin 前就存在的键),再回填缺失键:
|
|
356
367
|
// 顺序反过来会把刚回填的键再回送一遍(值与 host 完全相同,纯属多余流量)。
|
|
357
368
|
const seeded = seedLocal()
|
|
@@ -362,19 +373,24 @@ export function createUiStateMirror(deps: UiStateMirrorDeps): UiStateMirror {
|
|
|
362
373
|
if (seeded > 0) {
|
|
363
374
|
const lost = await flush()
|
|
364
375
|
report('首次搬运输送完成:丢弃 ' + lost + ' 键')
|
|
365
|
-
if (lost === 0) markSeeded()
|
|
376
|
+
if (!disposed && lost === 0) markSeeded()
|
|
366
377
|
}
|
|
367
378
|
settle()
|
|
368
379
|
return filled
|
|
369
380
|
} catch (error) {
|
|
381
|
+
if (disposed) return 0
|
|
370
382
|
attempts++
|
|
371
383
|
if (attempts < HYDRATE_RETRY_MAX) {
|
|
372
384
|
report('水合失败(第 ' + attempts + ' 次,' + HYDRATE_RETRY_MS + 'ms 后重试): ' + String(error), 'warn')
|
|
373
|
-
|
|
374
|
-
retryTimer =
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
385
|
+
try {
|
|
386
|
+
retryTimer = schedule(() => {
|
|
387
|
+
retryTimer = null
|
|
388
|
+
void hydrate()
|
|
389
|
+
}, HYDRATE_RETRY_MS)
|
|
390
|
+
return 0
|
|
391
|
+
} catch (scheduleError) {
|
|
392
|
+
report('水合重试调度失败: ' + String(scheduleError), 'warn')
|
|
393
|
+
}
|
|
378
394
|
}
|
|
379
395
|
report('水合放弃(重试 ' + attempts + ' 次仍失败): ' + String(error), 'warn')
|
|
380
396
|
settle()
|
|
@@ -382,26 +398,58 @@ export function createUiStateMirror(deps: UiStateMirrorDeps): UiStateMirror {
|
|
|
382
398
|
}
|
|
383
399
|
}
|
|
384
400
|
|
|
401
|
+
/** @private @author ddj 2026年10月23号 Release timers and only this instance's storage hooks. */
|
|
402
|
+
function dispose(): void {
|
|
403
|
+
if (disposed) return
|
|
404
|
+
disposed = true
|
|
405
|
+
if (timer !== null) cancel(timer)
|
|
406
|
+
if (retryTimer !== null) cancel(retryTimer)
|
|
407
|
+
timer = null
|
|
408
|
+
retryTimer = null
|
|
409
|
+
restoreStorage?.()
|
|
410
|
+
installed = false
|
|
411
|
+
}
|
|
412
|
+
|
|
385
413
|
return {
|
|
414
|
+
dispose,
|
|
386
415
|
install(): boolean {
|
|
416
|
+
if (disposed) return false
|
|
387
417
|
if (installed) return true
|
|
388
418
|
try {
|
|
389
419
|
const proto = Object.getPrototypeOf(storage) as Partial<UiStorage> | null
|
|
390
|
-
const rawSet = proto?.setItem
|
|
391
|
-
const rawRemove = proto?.removeItem
|
|
420
|
+
const rawSet = storage.setItem ?? proto?.setItem
|
|
421
|
+
const rawRemove = storage.removeItem ?? proto?.removeItem
|
|
392
422
|
if (typeof rawSet !== 'function' || typeof rawRemove !== 'function') return false
|
|
393
|
-
|
|
423
|
+
const setDescriptor = Object.getOwnPropertyDescriptor(storage, 'setItem')
|
|
424
|
+
const removeDescriptor = Object.getOwnPropertyDescriptor(storage, 'removeItem')
|
|
425
|
+
/** @private @author ddj 2026年10月23号 @param key Storage key. @param value Stored text. @sideEffects Queues a mirror update. */
|
|
426
|
+
function wrappedSet(key: string, value: string): void {
|
|
394
427
|
rawSet.call(storage, key, value)
|
|
395
428
|
queue(String(key), String(value))
|
|
396
429
|
}
|
|
397
|
-
|
|
430
|
+
/** @private @author ddj 2026年10月23号 @param key Storage key. @sideEffects Queues a mirror deletion. */
|
|
431
|
+
function wrappedRemove(key: string): void {
|
|
398
432
|
rawRemove.call(storage, key)
|
|
399
433
|
queue(String(key), null)
|
|
400
434
|
}
|
|
435
|
+
restoreStorage = () => {
|
|
436
|
+
if (storage.setItem === wrappedSet) {
|
|
437
|
+
if (setDescriptor) Object.defineProperty(storage, 'setItem', setDescriptor)
|
|
438
|
+
else delete (storage as Partial<UiStorage>).setItem
|
|
439
|
+
}
|
|
440
|
+
if (storage.removeItem === wrappedRemove) {
|
|
441
|
+
if (removeDescriptor) Object.defineProperty(storage, 'removeItem', removeDescriptor)
|
|
442
|
+
else delete (storage as Partial<UiStorage>).removeItem
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
storage.setItem = wrappedSet
|
|
446
|
+
storage.removeItem = wrappedRemove
|
|
401
447
|
installed = true
|
|
402
448
|
report('镜像已包装 localStorage 写口')
|
|
403
449
|
return true
|
|
404
450
|
} catch (error) {
|
|
451
|
+
restoreStorage?.()
|
|
452
|
+
installed = false
|
|
405
453
|
report('界面状态镜像包装失败(退回纯 localStorage): ' + String(error), 'warn')
|
|
406
454
|
return false
|
|
407
455
|
}
|
|
@@ -500,7 +548,7 @@ async function fetchViaRpc(sessionId?: string): Promise<UiStateSnapshot> {
|
|
|
500
548
|
* @returns 是否安装成功(存储不可用 → false,功能退回纯 localStorage)
|
|
501
549
|
*/
|
|
502
550
|
export function installUiStatePersist(
|
|
503
|
-
ctx: { get: (name: string) => unknown },
|
|
551
|
+
ctx: { get: (name: string) => unknown; effect: (setup: () => () => void, label?: string) => unknown },
|
|
504
552
|
schedule: (fn: () => void, ms: number) => unknown,
|
|
505
553
|
report?: (text: string, level?: string) => void,
|
|
506
554
|
): boolean {
|
|
@@ -518,6 +566,9 @@ export function installUiStatePersist(
|
|
|
518
566
|
fetch: fetchViaRpc,
|
|
519
567
|
sessionIdOf,
|
|
520
568
|
schedule,
|
|
569
|
+
cancel: (handle) => {
|
|
570
|
+
if (typeof handle === 'function') handle()
|
|
571
|
+
},
|
|
521
572
|
report: (text: string, level?: string) => reportUiStateEvent(text, level),
|
|
522
573
|
log: (text: string) => {
|
|
523
574
|
try { dbg(sessionIdOf(), 'ui-state: ' + text, 'warn') } catch (error) { /* 日志失败忽略 */ }
|
|
@@ -528,18 +579,28 @@ export function installUiStatePersist(
|
|
|
528
579
|
return false
|
|
529
580
|
}
|
|
530
581
|
singleton = mirror
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
582
|
+
const pageHost = typeof window === 'undefined' ? undefined : window
|
|
583
|
+
/** @private @author ddj 2026年10月23号 Persist pending changes when the page is hidden. */
|
|
584
|
+
function onPageHide(): void { void mirror.flush() }
|
|
585
|
+
ctx.effect(() => {
|
|
586
|
+
pageHost?.addEventListener('pagehide', onPageHide)
|
|
587
|
+
return () => {
|
|
588
|
+
pageHost?.removeEventListener('pagehide', onPageHide)
|
|
589
|
+
mirror.dispose()
|
|
590
|
+
if (singleton !== mirror) return
|
|
591
|
+
singleton = null
|
|
592
|
+
hydrating = null
|
|
593
|
+
uiReady = false
|
|
594
|
+
reporter = null
|
|
534
595
|
}
|
|
535
|
-
}
|
|
596
|
+
}, 'vscode-mode: ui state lifecycle')
|
|
536
597
|
hydrating = mirror.hydrate().then((filled) => {
|
|
537
|
-
if (filled > 0 || mirror.settled()) uiReady = true
|
|
598
|
+
if (singleton === mirror && (filled > 0 || mirror.settled())) uiReady = true
|
|
538
599
|
return filled
|
|
539
600
|
})
|
|
540
601
|
void hydrating.finally(() => {
|
|
541
602
|
// 首轮结束后:已结束水合阶段才允许后续 hydrateUiState() 重新发起(重试由镜像内部承担)
|
|
542
|
-
if (mirror.settled()) {
|
|
603
|
+
if (singleton === mirror && mirror.settled()) {
|
|
543
604
|
uiReady = true
|
|
544
605
|
hydrating = null
|
|
545
606
|
}
|
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
import { useFileWatch } from './useFileWatch.js'
|
|
50
50
|
import { createPdfPanel } from '../pdf/pdfPanel.js'
|
|
51
51
|
import { SvnDiffPanel } from './SvnDiffPanel.js'
|
|
52
|
-
import { applyOfficial, registerThemes, themeNameOf } from '../monaco/theme.js'
|
|
52
|
+
import { applyOfficial, editorFont, registerThemes, themeNameOf } from '../monaco/theme.js'
|
|
53
53
|
import { createDiffRenderer } from '../monaco/diffRender.js'
|
|
54
54
|
import { ST, callIdAttr, noopHunk, summarize } from '../state/records.js'
|
|
55
55
|
import { diffRegions } from '../state/regions.js'
|
|
@@ -2392,9 +2392,7 @@ export function EditorView(props) {
|
|
|
2392
2392
|
language: 'plaintext',
|
|
2393
2393
|
theme: themeNameOf(),
|
|
2394
2394
|
fontFamily: 'var(--ds-font-family-code, ui-monospace, monospace)',
|
|
2395
|
-
|
|
2396
|
-
fontSize: 16,
|
|
2397
|
-
lineHeight: 21,
|
|
2395
|
+
...editorFont(),
|
|
2398
2396
|
minimap: { enabled: !side, scale: 1 },
|
|
2399
2397
|
glyphMargin: true,
|
|
2400
2398
|
lineDecorationsWidth: 16,
|
|
@@ -3990,7 +3988,7 @@ export function EditorView(props) {
|
|
|
3990
3988
|
if (result.version) { revRef.current[path] = result.version; recordBaseline(scope, path, result.version) }
|
|
3991
3989
|
const editor = window.monaco.editor.create(host, { model, automaticLayout: true, minimap: { enabled: false }, scrollBeyondLastLine: false, glyphMargin: true,
|
|
3992
3990
|
// 与焦点编辑器(ensureEditor)同观感参数:字号/行高/顶部留白(side 形态 6,tab 形态 8)/装饰宽度,切分页行号位置不跳变(ddj 2026-10-16)
|
|
3993
|
-
fontFamily: 'var(--ds-font-family-code, ui-monospace, monospace)',
|
|
3991
|
+
fontFamily: 'var(--ds-font-family-code, ui-monospace, monospace)', ...editorFont(),
|
|
3994
3992
|
lineDecorationsWidth: 16, padding: { top: layoutRef.current === 'side' ? 6 : 8 } })
|
|
3995
3993
|
const unbindGutter = bindBpGutter(editor, host, path)
|
|
3996
3994
|
keeper = createViewKeeper(editor)
|
package/src/shared/snippets.ts
CHANGED
|
@@ -128,7 +128,7 @@ export const SNIPPET_LANGUAGES: readonly string[] = [
|
|
|
128
128
|
'c', 'cpp', 'csharp', 'css', 'dart', 'dockerfile', 'go', 'html', 'ini', 'java',
|
|
129
129
|
'javascript', 'json', 'jsonc', 'kotlin', 'less', 'lua', 'markdown', 'mdx', 'php',
|
|
130
130
|
'plaintext', 'powershell', 'python', 'ruby', 'rust', 'scss', 'shell', 'sql',
|
|
131
|
-
'swift', 'typescript', 'xml', 'yaml',
|
|
131
|
+
'swift', 'systemverilog', 'tcl', 'typescript', 'verilog', 'xml', 'yaml',
|
|
132
132
|
]
|
|
133
133
|
|
|
134
134
|
/** 语言 id → 展示名(仅收录写法与 id 明显不同的;未收录的原样返回)。 */
|