dsh-vscode-mode 0.1.30 → 0.1.35
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/LICENSE +21 -0
- package/README.md +30 -0
- package/lib/client.js +1439 -46
- package/lib/client.js.map +1 -1
- package/lib/index.js +3519 -222
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/capture.ts +3 -0
- package/src/client/events.ts +11 -0
- package/src/client/index.ts +20 -2
- package/src/client/monaco/lsp/index.ts +30 -0
- package/src/client/monaco/lsp/lspClient.ts +192 -0
- package/src/client/monaco/lsp/providers.ts +229 -0
- package/src/client/outline/OutlinePanel.tsx +1 -1
- package/src/client/outline/lspSource.ts +49 -0
- package/src/client/outline/parse.ts +31 -1
- package/src/client/outline/types.ts +2 -0
- package/src/client/paths.ts +36 -0
- package/src/client/sidebar/panels/FileExplorer.ts +95 -30
- package/src/client/sidebar/panels/SearchPanel.ts +11 -2
- package/src/client/state/explorerCache.ts +3 -1
- package/src/client/state/explorerEntriesCache.ts +172 -0
- package/src/client/state/viewStateCache.ts +3 -1
- package/src/client/styles/editor.css +16 -0
- package/src/client/styles/mcp.css +63 -0
- package/src/client/ui/EditorView.ts +61 -6
- package/src/client/ui/LspSettings.ts +268 -0
- package/src/client/ui/McpSettings.ts +5 -1
- package/src/client/ui/PerfSettings.ts +242 -0
- package/src/client/ui/PerfSizeBadge.ts +54 -0
- package/src/compat.ts +3 -2
- package/src/fileOpenSettings.ts +1 -0
- package/src/index.ts +25 -2
- package/src/lsp/client.ts +205 -0
- package/src/lsp/config.ts +107 -0
- package/src/lsp/extmgr.ts +208 -0
- package/src/lsp/jsonrpc.ts +86 -0
- package/src/lsp/manager.ts +133 -0
- package/src/lsp/providers.ts +235 -0
- package/src/lsp/rpc.ts +334 -0
- package/src/lsp/server.ts +346 -0
- package/src/lsp/settings.ts +44 -0
- package/src/lsp/transport.ts +143 -0
- package/src/lsp/uri.ts +62 -0
- package/src/lsp/zip.ts +82 -0
- package/src/paths.ts +284 -0
- package/src/perf.ts +340 -0
- package/src/perfPatch.ts +76 -0
- package/src/routes.ts +7 -12
- package/src/rpc.ts +184 -36
- package/src/search/content.ts +14 -7
- package/src/search/ripgrep.ts +54 -1
- package/src/search/types.ts +4 -1
- package/src/shared/lsp.ts +138 -0
- package/src/shared/rpc.ts +103 -2
- package/src/store.ts +2 -2
- package/src/treeIndex.ts +397 -0
- package/src/treeRead.ts +87 -0
|
@@ -22,6 +22,9 @@ import { revealInExplorer as revealPathInExplorer } from '../fileReveal.js'
|
|
|
22
22
|
import { setSidePending, SIDEBAR_INSTALL_CMD } from '../sidebarBridge.js'
|
|
23
23
|
import { upsertViewState, viewStatesLoad, viewStatesSave } from '../state/viewStateCache.js'
|
|
24
24
|
import { bindingOf, chordOf, matchEvent, useKeybindingsVersion } from '../keybindings.js'
|
|
25
|
+
import { CACHE_KEY } from '../paths.js'
|
|
26
|
+
import { bindLspEditor, runGoToDefinition, runFindReferences, hideReferencesOverlay } from '../monaco/lsp/providers.js'
|
|
27
|
+
import { onLspProgress, refreshStatus } from '../monaco/lsp/index.js'
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
30
|
* 中央编辑区:文件页签(脏点/关闭/打开路径)+ Ctrl+P 搜索 + Monaco 编辑器 +
|
|
@@ -46,6 +49,7 @@ export function EditorView(props) {
|
|
|
46
49
|
const [content, setContent] = React.useState(null)
|
|
47
50
|
const [contentPath, setContentPath] = React.useState(null)
|
|
48
51
|
const [status, setStatus] = React.useState('')
|
|
52
|
+
const [lspServers, setLspServers] = React.useState([])
|
|
49
53
|
const [error, setError] = React.useState(null)
|
|
50
54
|
const [loadError, setLoadError] = React.useState(null)
|
|
51
55
|
const [loadStage, setLoadStage] = React.useState({ progress: 0, message: '准备加载编辑器…' })
|
|
@@ -64,7 +68,7 @@ export function EditorView(props) {
|
|
|
64
68
|
const [focusRequest, setFocusRequest] = React.useState(0) // 外部差异聚焦请求版本
|
|
65
69
|
const kbVersion = useKeybindingsVersion() // 快捷键配置版本(tooltip 文案随键位刷新)
|
|
66
70
|
const [hintDismissed, setHintDismissed] = React.useState(() => {
|
|
67
|
-
try { return localStorage.getItem(
|
|
71
|
+
try { return localStorage.getItem(CACHE_KEY.sideHint) === '1' } catch { return false }
|
|
68
72
|
}) // 侧边栏引导条是否已关闭
|
|
69
73
|
const editorRef = React.useRef(null)
|
|
70
74
|
const viewRootRef = React.useRef(null)
|
|
@@ -264,6 +268,14 @@ export function EditorView(props) {
|
|
|
264
268
|
return () => { clearInterval(t); window.removeEventListener('edrv:refresh', onRefresh) }
|
|
265
269
|
}, [sessionId])
|
|
266
270
|
|
|
271
|
+
React.useEffect(() => {
|
|
272
|
+
const publishLsp = (servers) => setLspServers(Array.isArray(servers) ? servers : [])
|
|
273
|
+
const unsubscribe = onLspProgress(publishLsp)
|
|
274
|
+
void refreshStatus(true).then(publishLsp)
|
|
275
|
+
const timer = setInterval(() => { void refreshStatus(true).then(publishLsp) }, 1500)
|
|
276
|
+
return () => { unsubscribe(); clearInterval(timer) }
|
|
277
|
+
}, [sessionId])
|
|
278
|
+
|
|
267
279
|
React.useEffect(() => {
|
|
268
280
|
const onOpen = (e) => {
|
|
269
281
|
const p = e?.detail?.path
|
|
@@ -271,6 +283,13 @@ export function EditorView(props) {
|
|
|
271
283
|
if (e?.detail?.focusDiff === true) {
|
|
272
284
|
pendingFocusRef.current = { path: p, region: null }
|
|
273
285
|
setFocusRequest((value) => value + 1)
|
|
286
|
+
addTab(p, true)
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
if (e?.detail?.line != null) {
|
|
290
|
+
// LSP/搜索跳转:打开并定位到行列
|
|
291
|
+
openFileAt(p, e?.detail?.line, e?.detail?.column)
|
|
292
|
+
return
|
|
274
293
|
}
|
|
275
294
|
addTab(p, true)
|
|
276
295
|
}
|
|
@@ -281,9 +300,12 @@ export function EditorView(props) {
|
|
|
281
300
|
}
|
|
282
301
|
window.addEventListener('edrv:open-editor', onOpen)
|
|
283
302
|
window.addEventListener('edrv:show-launcher', onShowLauncher)
|
|
303
|
+
const onStatus = (e) => { if (e?.detail?.text) setStatus(e.detail.text) }
|
|
304
|
+
window.addEventListener('edrv:status', onStatus)
|
|
284
305
|
return () => {
|
|
285
306
|
window.removeEventListener('edrv:open-editor', onOpen)
|
|
286
307
|
window.removeEventListener('edrv:show-launcher', onShowLauncher)
|
|
308
|
+
window.removeEventListener('edrv:status', onStatus)
|
|
287
309
|
}
|
|
288
310
|
}, [sessionId])
|
|
289
311
|
|
|
@@ -356,7 +378,7 @@ export function EditorView(props) {
|
|
|
356
378
|
bootRef.current = true
|
|
357
379
|
viewStatesRef.current = viewStatesLoad(sessionId)
|
|
358
380
|
try {
|
|
359
|
-
const raw = localStorage.getItem(
|
|
381
|
+
const raw = localStorage.getItem(CACHE_KEY.editor + String(sessionId))
|
|
360
382
|
if (raw) {
|
|
361
383
|
const saved = JSON.parse(raw)
|
|
362
384
|
if (Array.isArray(saved.tabs) && saved.tabs.length) {
|
|
@@ -369,12 +391,12 @@ export function EditorView(props) {
|
|
|
369
391
|
|
|
370
392
|
React.useEffect(() => {
|
|
371
393
|
if (!sessionId) return
|
|
372
|
-
try { localStorage.setItem(
|
|
394
|
+
try { localStorage.setItem(CACHE_KEY.editor + String(sessionId), JSON.stringify({ tabs: tabs.map((t) => t.path), active })) }
|
|
373
395
|
catch (e) { /* 忽略 */ }
|
|
374
396
|
}, [tabs, active, sessionId])
|
|
375
397
|
|
|
376
398
|
// 侧边栏状态:恢复(显隐/宽度/激活面板);侧栏形态独立键(默认收起,不共享页签形态偏好)
|
|
377
|
-
const sidebarKey =
|
|
399
|
+
const sidebarKey = CACHE_KEY.sidebar + (layout === 'side' ? 'side.' : '') + String(sessionId)
|
|
378
400
|
React.useEffect(() => {
|
|
379
401
|
if (!sessionId) return
|
|
380
402
|
try {
|
|
@@ -542,6 +564,7 @@ export function EditorView(props) {
|
|
|
542
564
|
flushSave()
|
|
543
565
|
saveViewState(active)
|
|
544
566
|
diffRendererRef.current?.dispose?.()
|
|
567
|
+
hideReferencesOverlay()
|
|
545
568
|
if (editorRef.current) { editorRef.current.dispose(); editorRef.current = null }
|
|
546
569
|
for (const m of modelsRef.current.values()) m.dispose()
|
|
547
570
|
modelsRef.current.clear()
|
|
@@ -628,6 +651,21 @@ export function EditorView(props) {
|
|
|
628
651
|
precondition: 'editorTextFocus',
|
|
629
652
|
run: (edx) => menuHandlers()?.openInExplorer(pathOf(edx)),
|
|
630
653
|
})
|
|
654
|
+
// 语言智能(LSP):转到定义 / 查找所有引用(右键 + F12/Shift+F12 键位)。
|
|
655
|
+
ed.addAction({
|
|
656
|
+
id: 'edrv.goToDefinition', label: '转到定义', contextMenuGroupId: '1_edrv',
|
|
657
|
+
keybindings: m.KeyMod.CtrlCmd | m.KeyCode.F12,
|
|
658
|
+
precondition: 'editorTextFocus',
|
|
659
|
+
run: (edx) => { void runGoToDefinition(edx) },
|
|
660
|
+
})
|
|
661
|
+
ed.addAction({
|
|
662
|
+
id: 'edrv.findReferences', label: '查找所有引用', contextMenuGroupId: '1_edrv',
|
|
663
|
+
keybindings: m.KeyMod.Shift | m.KeyCode.F12,
|
|
664
|
+
precondition: 'editorTextFocus',
|
|
665
|
+
run: (edx) => { void runFindReferences(edx) },
|
|
666
|
+
})
|
|
667
|
+
ed.addCommand(m.KeyCode.F12, () => { void runGoToDefinition(ed) })
|
|
668
|
+
bindLspEditor(ed)
|
|
631
669
|
// hover 差异块 → 浮出 Keep/Undo(req:鼠标移到编辑区差异块时显示)
|
|
632
670
|
// 防闪烁:① 区域不变不 setState(浮窗锚定差异块起始行,不跟随鼠标);② 延迟隐藏;
|
|
633
671
|
// ③ 浮窗自身 onMouseEnter 取消隐藏计时(鼠标在浮窗与编辑器间移动不闪)。
|
|
@@ -981,6 +1019,22 @@ export function EditorView(props) {
|
|
|
981
1019
|
(cursor ? React.createElement('span', { className: 'edrv-pb-meta' }, cursor) : null),
|
|
982
1020
|
(status ? React.createElement('span', { className: 'edrv-pb-meta edrv-pb-status' }, status) : null))
|
|
983
1021
|
|
|
1022
|
+
const lspLanguage = active ? langOf(active) : ''
|
|
1023
|
+
const lspServer = lspServers.find((item) => item.languageId === lspLanguage)
|
|
1024
|
+
const lspPhaseText = { idle: '未启动', starting: '启动中', ready: '已就绪', indexing: '解析中', unavailable: '不可用', stopped: '已停止' }
|
|
1025
|
+
const lspSourceText = { extension: '扩展', discover: '自动发现', manual: '手动配置', none: '未配置' }
|
|
1026
|
+
const lspLabel = lspServer
|
|
1027
|
+
? 'LSP ' + lspLanguage + ' · ' + (lspPhaseText[lspServer.phase] ?? lspServer.phase) + ' · ' + (lspSourceText[lspServer.source] ?? lspServer.source)
|
|
1028
|
+
: 'LSP ' + lspLanguage + ' · 未启动'
|
|
1029
|
+
const lspProgress = typeof lspServer?.progress === 'number' ? Math.round(lspServer.progress) : null
|
|
1030
|
+
const lspFooter = (lspLanguage === 'lua' || lspLanguage === 'csharp')
|
|
1031
|
+
? React.createElement('div', { className: 'edrv-statusbar', title: lspServer?.progressMessage || lspLabel },
|
|
1032
|
+
React.createElement('span', { className: 'edrv-lsp-status-dot ' + (lspServer?.phase === 'ready' ? 'ready' : lspServer?.phase === 'indexing' || lspServer?.phase === 'starting' ? 'busy' : 'idle') }),
|
|
1033
|
+
React.createElement('span', { className: 'edrv-sp-lsp' }, lspLabel),
|
|
1034
|
+
lspServer?.progressMessage ? React.createElement('span', { className: 'edrv-sp-progress-message' }, lspServer.progressMessage) : null,
|
|
1035
|
+
lspProgress !== null ? React.createElement('span', { className: 'edrv-sp-progress' }, lspProgress + '%') : null)
|
|
1036
|
+
: null
|
|
1037
|
+
|
|
984
1038
|
const tabRow = React.createElement('div', { style: { display: 'flex', alignItems: 'center', background: 'var(--dsw-alias-bg-layer-1,transparent)', flexShrink: 0 } },
|
|
985
1039
|
tabsEl,
|
|
986
1040
|
(sum.totalFiles > 0
|
|
@@ -1152,7 +1206,7 @@ export function EditorView(props) {
|
|
|
1152
1206
|
// 侧边栏引导条(仅旧页签形态且未装 betterSidebar 时显示;可复制安装命令、可关闭)
|
|
1153
1207
|
const dismissHint = () => {
|
|
1154
1208
|
setHintDismissed(true)
|
|
1155
|
-
try { localStorage.setItem(
|
|
1209
|
+
try { localStorage.setItem(CACHE_KEY.sideHint, '1') } catch (e) { /* 忽略 */ }
|
|
1156
1210
|
}
|
|
1157
1211
|
const copyInstallCmd = () => {
|
|
1158
1212
|
if (navigator.clipboard?.writeText) navigator.clipboard.writeText(SIDEBAR_INSTALL_CMD).catch(() => {})
|
|
@@ -1168,7 +1222,8 @@ export function EditorView(props) {
|
|
|
1168
1222
|
pathBar,
|
|
1169
1223
|
tabRow,
|
|
1170
1224
|
sideHintEl,
|
|
1171
|
-
editorArea
|
|
1225
|
+
editorArea,
|
|
1226
|
+
lspFooter)
|
|
1172
1227
|
// 编辑器根节点按 composer 顶部边界动态限高,底部对话区域继续由 DSH 原生渲染。
|
|
1173
1228
|
// 侧栏形态由面板容器给高(100%),不做 composer 几何同步。
|
|
1174
1229
|
const editorRow = React.createElement('div', { className: 'edrv-editor-row' },
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* dsh-vscode-mode client — 「语言服务器」设置子 Tab。
|
|
4
|
+
* 四视图:服务器(状态 + 每语言启用/命令/路径)|已安装(卸载/更新)|市场(Open VSX 搜索/安装 + 本地 vsix)|更新。
|
|
5
|
+
* 状态经 edrv.lsp.status,配置经 edrv.lsp.configGet/Update,扩展经 edrv.lsp.ext.*。
|
|
6
|
+
* 作者 ddj 2026-08-27
|
|
7
|
+
*/
|
|
8
|
+
import React from 'react'
|
|
9
|
+
import { rpc } from '../rpc.js'
|
|
10
|
+
import '../styles/mcp.css'
|
|
11
|
+
|
|
12
|
+
const LANGUAGES = [
|
|
13
|
+
{ id: 'lua', label: 'Lua(LuaLS)', hint: 'lua-language-server,EmmyLua 注解原生支持' },
|
|
14
|
+
{ id: 'csharp', label: 'C#(Roslyn / OmniSharp)', hint: '需 dotnet + ms-dotnettools.csharp 的 .roslyn 服务器,或手动指定' },
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
const PHASE_LABEL = {
|
|
18
|
+
idle: '未启动', starting: '启动中', ready: '就绪', indexing: '索引中', unavailable: '不可用', stopped: '已停止',
|
|
19
|
+
}
|
|
20
|
+
const SOURCE_LABEL = { extension: '扩展', discover: '自动发现', manual: '手动配置', none: '未配置' }
|
|
21
|
+
const TABS = [
|
|
22
|
+
{ id: 'servers', label: '服务器' },
|
|
23
|
+
{ id: 'installed', label: '已安装' },
|
|
24
|
+
{ id: 'market', label: '市场' },
|
|
25
|
+
{ id: 'updates', label: '更新' },
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
/** 语言卡片:状态 + 启用开关 + 命令/路径配置。 */
|
|
29
|
+
function LangCard({ lang, config, status, busy, onToggle, onSave }) {
|
|
30
|
+
const [pathDraft, setPathDraft] = React.useState(config?.path ?? '')
|
|
31
|
+
const [commandDraft, setCommandDraft] = React.useState(config?.command ?? '')
|
|
32
|
+
React.useEffect(() => { setPathDraft(config?.path ?? ''); setCommandDraft(config?.command ?? '') }, [config?.path, config?.command])
|
|
33
|
+
const phase = status?.phase ?? 'idle'
|
|
34
|
+
return React.createElement('article', { className: 'vsm-mcp-card' },
|
|
35
|
+
React.createElement('div', { className: 'vsm-mcp-card-head' },
|
|
36
|
+
React.createElement('div', { className: 'vsm-mcp-title' },
|
|
37
|
+
React.createElement('span', { className: 'vsm-mcp-dot ' + (phase === 'ready' || phase === 'indexing' ? 'connected' : phase === 'unavailable' ? 'error' : 'disabled') }),
|
|
38
|
+
lang.label),
|
|
39
|
+
React.createElement('div', { className: 'vsm-mcp-actions' },
|
|
40
|
+
React.createElement('button', { className: 'vsm-switch ' + (config?.enabled !== false ? 'on' : ''), onClick: () => onToggle(lang.id, config?.enabled !== false ? false : true), 'aria-label': '启用/禁用' }, config?.enabled !== false ? '●' : '○'))),
|
|
41
|
+
React.createElement('div', { className: 'vsm-mcp-meta' },
|
|
42
|
+
PHASE_LABEL[phase] ?? phase, ' · ', SOURCE_LABEL[status?.source] ?? status?.source,
|
|
43
|
+
(phase === 'ready' && status?.root) ? ' · ' + String(status.root).split(/[\\/]/).pop() : ''),
|
|
44
|
+
status?.reason ? React.createElement('div', { className: 'vsm-mcp-error' }, status.reason) : null,
|
|
45
|
+
React.createElement('div', { className: 'vsm-lsp-form' },
|
|
46
|
+
React.createElement('label', null, React.createElement('span', null, '可执行文件路径(绝对路径,优先)'), React.createElement('input', { value: pathDraft, placeholder: '如 C:/.../lua-language-server.exe', onChange: (e) => setPathDraft(e.target.value) })),
|
|
47
|
+
React.createElement('label', null, React.createElement('span', null, '命令名(PATH 内查找)'), React.createElement('input', { value: commandDraft, placeholder: '如 lua-language-server', onChange: (e) => setCommandDraft(e.target.value) })),
|
|
48
|
+
React.createElement('button', { className: 'vsm-primary', disabled: busy === lang.id, onClick: () => onSave(lang.id, pathDraft, commandDraft) }, busy === lang.id ? '保存中…' : '保存配置')),
|
|
49
|
+
React.createElement('div', { className: 'vsm-lsp-hint' }, lang.hint))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** 已装扩展卡片:清单 + 卸载/更新。 */
|
|
53
|
+
function InstalledCard({ ext, busy, onUninstall, onUpdate }) {
|
|
54
|
+
return React.createElement('article', { className: 'vsm-mcp-card' },
|
|
55
|
+
React.createElement('div', { className: 'vsm-mcp-card-head' },
|
|
56
|
+
React.createElement('div', { className: 'vsm-mcp-title' },
|
|
57
|
+
React.createElement('span', { className: 'vsm-mcp-dot connected' }),
|
|
58
|
+
React.createElement('span', null, ext.displayName || ext.name),
|
|
59
|
+
React.createElement('span', { className: 'vsm-ext-ver' }, ext.id + ' · v' + ext.version)),
|
|
60
|
+
React.createElement('div', { className: 'vsm-mcp-actions' },
|
|
61
|
+
React.createElement('button', { className: 'vsm-primary vsm-small', disabled: busy === ext.id, onClick: () => onUpdate(ext) }, '更新'),
|
|
62
|
+
React.createElement('button', { className: 'vsm-danger vsm-small', disabled: busy === ext.id, onClick: () => onUninstall(ext) }, '卸载'))),
|
|
63
|
+
ext.description ? React.createElement('div', { className: 'vsm-lsp-hint' }, ext.description) : null)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 市场条目卡片:安装/已装态。 */
|
|
67
|
+
function MarketCard({ item, installed, busy, onInstall }) {
|
|
68
|
+
const has = installed.some((e) => e.id === item.id)
|
|
69
|
+
return React.createElement('article', { className: 'vsm-mcp-card' },
|
|
70
|
+
React.createElement('div', { className: 'vsm-mcp-card-head' },
|
|
71
|
+
React.createElement('div', { className: 'vsm-mcp-title' },
|
|
72
|
+
React.createElement('span', { className: 'vsm-mcp-dot ' + (has ? 'connected' : 'disabled') }),
|
|
73
|
+
React.createElement('span', null, item.displayName || item.name),
|
|
74
|
+
React.createElement('span', { className: 'vsm-ext-ver' }, item.id + ' · v' + item.version)),
|
|
75
|
+
React.createElement('div', { className: 'vsm-mcp-actions' },
|
|
76
|
+
React.createElement('button', { className: 'vsm-primary vsm-small', disabled: has || busy === item.id, onClick: () => onInstall(item) }, has ? '已安装' : '安装'))),
|
|
77
|
+
item.description ? React.createElement('div', { className: 'vsm-lsp-hint' }, item.description) : null)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 语言服务器设置页:服务器状态/配置 + VSIX 扩展(安装/更新/卸载三视图)。
|
|
82
|
+
* @author ddj 2026年08月27号
|
|
83
|
+
*/
|
|
84
|
+
export function LspSettings() {
|
|
85
|
+
const [tab, setTab] = React.useState('servers')
|
|
86
|
+
const [config, setConfig] = React.useState({})
|
|
87
|
+
const [servers, setServers] = React.useState([])
|
|
88
|
+
const [installed, setInstalled] = React.useState([])
|
|
89
|
+
const [updates, setUpdates] = React.useState([])
|
|
90
|
+
const [market, setMarket] = React.useState([])
|
|
91
|
+
const [query, setQuery] = React.useState('')
|
|
92
|
+
const [vsixPath, setVsixPath] = React.useState('')
|
|
93
|
+
const [busy, setBusy] = React.useState('')
|
|
94
|
+
const [error, setError] = React.useState('')
|
|
95
|
+
const [loading, setLoading] = React.useState(true)
|
|
96
|
+
|
|
97
|
+
const refreshServers = React.useCallback(() => {
|
|
98
|
+
Promise.all([rpc('edrv.lsp.configGet', {}), rpc('edrv.lsp.status', {})])
|
|
99
|
+
.then(([cfg, st]) => {
|
|
100
|
+
if (cfg?.ok) setConfig(cfg.config ?? {})
|
|
101
|
+
if (st?.ok) setServers(st.servers ?? [])
|
|
102
|
+
setError('')
|
|
103
|
+
})
|
|
104
|
+
.catch((e) => setError(String(e)))
|
|
105
|
+
}, [])
|
|
106
|
+
|
|
107
|
+
const refreshExt = React.useCallback(() => {
|
|
108
|
+
Promise.all([rpc('edrv.lsp.ext.list', {}), rpc('edrv.lsp.ext.updates', {})])
|
|
109
|
+
.then(([l, u]) => {
|
|
110
|
+
if (l?.ok) setInstalled(l.extensions ?? [])
|
|
111
|
+
if (u?.ok) setUpdates(u.updates ?? [])
|
|
112
|
+
setError('')
|
|
113
|
+
})
|
|
114
|
+
.catch((e) => setError(String(e)))
|
|
115
|
+
}, [])
|
|
116
|
+
|
|
117
|
+
const refresh = React.useCallback(() => {
|
|
118
|
+
setLoading(true)
|
|
119
|
+
Promise.all([refreshServers(), refreshExt()]).finally(() => setLoading(false))
|
|
120
|
+
}, [refreshServers, refreshExt])
|
|
121
|
+
|
|
122
|
+
React.useEffect(() => { refresh() }, [refresh])
|
|
123
|
+
|
|
124
|
+
const switchTab = (next) => {
|
|
125
|
+
setTab(next)
|
|
126
|
+
setError('')
|
|
127
|
+
if (next === 'market' && !query) setQuery('lua language server')
|
|
128
|
+
if (next === 'installed' || next === 'updates') void refreshExt()
|
|
129
|
+
if (next === 'servers') void refreshServers()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const saveLang = (languageId, path, command) => {
|
|
133
|
+
setBusy(languageId)
|
|
134
|
+
setError('')
|
|
135
|
+
rpc('edrv.lsp.configUpdate', { languageId, path, command })
|
|
136
|
+
.then((res) => {
|
|
137
|
+
if (!res?.ok) { setError(res?.error ?? '保存失败'); return }
|
|
138
|
+
setConfig(res.config ?? {})
|
|
139
|
+
})
|
|
140
|
+
.catch((e) => setError(String(e)))
|
|
141
|
+
.finally(() => setBusy(''))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const toggleLang = (languageId, enabled) => {
|
|
145
|
+
setBusy(languageId)
|
|
146
|
+
rpc('edrv.lsp.configUpdate', { languageId, enabled })
|
|
147
|
+
.then((res) => {
|
|
148
|
+
if (res?.ok) { setConfig(res.config ?? {}); void refreshServers() }
|
|
149
|
+
else setError(res?.error ?? '切换失败')
|
|
150
|
+
})
|
|
151
|
+
.catch((e) => setError(String(e)))
|
|
152
|
+
.finally(() => setBusy(''))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const searchMarket = () => {
|
|
156
|
+
if (!query.trim()) return
|
|
157
|
+
setBusy('__market')
|
|
158
|
+
setError('')
|
|
159
|
+
rpc('edrv.lsp.ext.market', { query: query.trim(), size: 12 })
|
|
160
|
+
.then((res) => {
|
|
161
|
+
if (res?.ok) setMarket(res.extensions ?? [])
|
|
162
|
+
else setError(res?.error ?? '搜索失败')
|
|
163
|
+
})
|
|
164
|
+
.catch((e) => setError(String(e)))
|
|
165
|
+
.finally(() => setBusy(''))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const installMarket = (item) => {
|
|
169
|
+
setBusy(item.id)
|
|
170
|
+
setError('')
|
|
171
|
+
rpc('edrv.lsp.ext.install', { namespace: item.namespace, name: item.name })
|
|
172
|
+
.then((res) => {
|
|
173
|
+
if (!res?.ok) { setError(res?.error ?? '安装失败'); return }
|
|
174
|
+
void refreshExt()
|
|
175
|
+
})
|
|
176
|
+
.catch((e) => setError(String(e)))
|
|
177
|
+
.finally(() => setBusy(''))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const installLocal = () => {
|
|
181
|
+
if (!vsixPath.trim()) return
|
|
182
|
+
setBusy('__vsix')
|
|
183
|
+
setError('')
|
|
184
|
+
rpc('edrv.lsp.ext.install', { vsixPath: vsixPath.trim() })
|
|
185
|
+
.then((res) => {
|
|
186
|
+
if (!res?.ok) { setError(res?.error ?? '安装失败'); return }
|
|
187
|
+
setVsixPath('')
|
|
188
|
+
void refreshExt()
|
|
189
|
+
})
|
|
190
|
+
.catch((e) => setError(String(e)))
|
|
191
|
+
.finally(() => setBusy(''))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const uninstallExt = (ext) => {
|
|
195
|
+
if (!window.confirm('卸载扩展 ' + ext.id + '?')) return
|
|
196
|
+
setBusy(ext.id)
|
|
197
|
+
setError('')
|
|
198
|
+
rpc('edrv.lsp.ext.uninstall', { id: ext.id })
|
|
199
|
+
.then((res) => {
|
|
200
|
+
if (!res?.ok) setError(res?.error ?? '卸载失败')
|
|
201
|
+
void refreshExt()
|
|
202
|
+
})
|
|
203
|
+
.catch((e) => setError(String(e)))
|
|
204
|
+
.finally(() => setBusy(''))
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const updateExt = (ext) => {
|
|
208
|
+
setBusy(ext.id)
|
|
209
|
+
setError('')
|
|
210
|
+
rpc('edrv.lsp.ext.update', { id: ext.id })
|
|
211
|
+
.then((res) => {
|
|
212
|
+
if (!res?.ok) setError(res?.error ?? '更新失败')
|
|
213
|
+
void refreshExt()
|
|
214
|
+
})
|
|
215
|
+
.catch((e) => setError(String(e)))
|
|
216
|
+
.finally(() => setBusy(''))
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (loading) return React.createElement('div', { className: 'vsm-mcp-empty' }, '正在读取语言服务器状态…')
|
|
220
|
+
const nav = React.createElement('nav', { className: 'vsm-mcp-tabs' },
|
|
221
|
+
TABS.map((t) => React.createElement('button', { key: t.id, className: tab === t.id ? 'active' : '', onClick: () => switchTab(t.id) }, t.label)))
|
|
222
|
+
const header = React.createElement('div', { className: 'vsm-mcp-header' },
|
|
223
|
+
React.createElement('div', null,
|
|
224
|
+
React.createElement('h2', null, '语言服务器(LSP)'),
|
|
225
|
+
React.createElement('p', null, '代码索引 / 引用查找 / 动态跳转;可安装 VSIX 语言服务器扩展(Open VSX 市场 / 本地 .vsix)。')),
|
|
226
|
+
React.createElement('button', { className: 'vsm-primary', onClick: refresh }, '↻ 刷新'))
|
|
227
|
+
const banner = error ? React.createElement('div', { className: 'vsm-mcp-error vsm-mcp-banner' }, error) : null
|
|
228
|
+
|
|
229
|
+
let body = null
|
|
230
|
+
if (tab === 'servers') {
|
|
231
|
+
body = React.createElement('div', null,
|
|
232
|
+
LANGUAGES.map((lang) => React.createElement(LangCard, {
|
|
233
|
+
key: lang.id, lang,
|
|
234
|
+
config: config[lang.id] ?? {},
|
|
235
|
+
status: servers.find((s) => s.languageId === lang.id),
|
|
236
|
+
busy, onToggle: toggleLang, onSave: saveLang,
|
|
237
|
+
})),
|
|
238
|
+
React.createElement('div', { className: 'vsm-lsp-hint' }, '提示:打开 Lua/C# 文件即自动(惰性)启动对应语言服务器;未配置时编辑器功能不受影响,大纲回退内置解析。'))
|
|
239
|
+
} else if (tab === 'installed') {
|
|
240
|
+
body = React.createElement('div', null,
|
|
241
|
+
React.createElement('div', { className: 'vsm-lsp-hint' }, '已安装的语言服务器扩展(存于 ~/.dsh/dsh-vscode-mode/extensions/)。语言服务器被自动发现为「扩展」源。'),
|
|
242
|
+
installed.length ? installed.map((ext) => React.createElement(InstalledCard, { key: ext.id, ext, busy, onUninstall: uninstallExt, onUpdate: updateExt }))
|
|
243
|
+
: React.createElement('div', { className: 'vsm-mcp-empty' }, '还没有安装扩展(去「市场」或指定本地 .vsix)'))
|
|
244
|
+
} else if (tab === 'market') {
|
|
245
|
+
body = React.createElement('div', null,
|
|
246
|
+
React.createElement('div', { className: 'vsm-lsp-form' },
|
|
247
|
+
React.createElement('label', null, React.createElement('span', null, '搜索 Open VSX 市场'), React.createElement('input', { value: query, placeholder: '如 sumneko.lua / lua language server', onChange: (e) => setQuery(e.target.value), onKeyDown: (e) => { if (e.key === 'Enter') searchMarket() } })),
|
|
248
|
+
React.createElement('button', { className: 'vsm-primary', disabled: busy === '__market', onClick: searchMarket }, busy === '__market' ? '搜索中…' : '搜索')),
|
|
249
|
+
React.createElement('div', { className: 'vsm-lsp-form' },
|
|
250
|
+
React.createElement('label', null, React.createElement('span', null, '安装本地 .vsix 文件(服务器绝对路径)'), React.createElement('input', { value: vsixPath, placeholder: '如 C:/Downloads/sumneko.lua-3.19.1.vsix', onChange: (e) => setVsixPath(e.target.value) })),
|
|
251
|
+
React.createElement('button', { className: 'vsm-primary', disabled: busy === '__vsix', onClick: installLocal }, busy === '__vsix' ? '安装中…' : '安装本地 .vsix')),
|
|
252
|
+
React.createElement('div', { className: 'vsm-lsp-hint' }, '市场安装会下载并解包 VSIX 到扩展目录;含语言服务器(如 sumneko.lua)的扩展会被自动发现。'),
|
|
253
|
+
market.map((item) => React.createElement(MarketCard, { key: item.id, item, installed, busy, onInstall: installMarket })))
|
|
254
|
+
} else {
|
|
255
|
+
body = React.createElement('div', null,
|
|
256
|
+
React.createElement('div', { className: 'vsm-lsp-hint' }, '已装扩展与 Open VSX 最新版的差异。'),
|
|
257
|
+
updates.length ? updates.map((u) => React.createElement('article', { key: u.id, className: 'vsm-mcp-card' },
|
|
258
|
+
React.createElement('div', { className: 'vsm-mcp-card-head' },
|
|
259
|
+
React.createElement('div', { className: 'vsm-mcp-title' },
|
|
260
|
+
React.createElement('span', { className: 'vsm-mcp-dot error' }),
|
|
261
|
+
React.createElement('span', null, u.displayName || u.id),
|
|
262
|
+
React.createElement('span', { className: 'vsm-ext-ver' }, 'v' + u.current + ' → v' + u.latest)),
|
|
263
|
+
React.createElement('div', { className: 'vsm-mcp-actions' },
|
|
264
|
+
React.createElement('button', { className: 'vsm-primary vsm-small', disabled: busy === u.id, onClick: () => updateExt(u) }, busy === u.id ? '更新中…' : '更新')))))
|
|
265
|
+
: React.createElement('div', { className: 'vsm-mcp-empty' }, '全部已是最新'))
|
|
266
|
+
}
|
|
267
|
+
return React.createElement('div', null, header, nav, banner, body)
|
|
268
|
+
}
|
|
@@ -12,6 +12,8 @@ import '../styles/mcp.css'
|
|
|
12
12
|
import { availableOpeners, AUTO_OPEN_TOOL } from '../fileOpeners.js'
|
|
13
13
|
import { SettingsContext } from '../settingsContext.js'
|
|
14
14
|
import { KeybindingsPanel } from './KeybindingsPanel.js'
|
|
15
|
+
import { LspSettings } from './LspSettings.js'
|
|
16
|
+
import { PerfSettings } from './PerfSettings.js'
|
|
15
17
|
|
|
16
18
|
const EMPTY = { serverName: '', transport: 'stdio', command: '', args: '', cwd: '', url: '', headers: '' }
|
|
17
19
|
|
|
@@ -321,11 +323,13 @@ export function McpSettings(props) {
|
|
|
321
323
|
else if (tab === 'mine') body = React.createElement('div', null, servers.length ? servers.map((s) => React.createElement(ServerCard, { key: s.id, server: s, onRefresh: refreshGlobal, onToggle: toggleGlobal, onRemove: removeGlobal })) : React.createElement('div', { className: 'vsm-mcp-empty' }, '还没有配置全局 MCP'))
|
|
322
324
|
else if (tab === 'projects') body = projectBody
|
|
323
325
|
else if (tab === 'compat') body = React.createElement(CompatSection, { getSummary: compatSummary })
|
|
326
|
+
else if (tab === 'lsp') body = React.createElement(LspSettings, null)
|
|
327
|
+
else if (tab === 'perf') body = React.createElement(PerfSettings, null)
|
|
324
328
|
else body = marketBody
|
|
325
329
|
const form = showForm ? React.createElement(McpForm, { title: '添加全局 MCP', draft, busy, edit, save: saveGlobal, close: () => setShowForm(false) }) : projectForm ? React.createElement(McpForm, { title: '添加项目 MCP · ' + projectForm.title, draft, busy, edit, save: saveProject, close: () => setProjectForm(null) }) : null
|
|
326
330
|
return React.createElement('section', { className: 'vsm-mcp-page' },
|
|
327
331
|
React.createElement('header', { className: 'vsm-mcp-header' }, React.createElement('div', null, React.createElement('h2', null, 'VSCodeMode'), React.createElement('p', null, '管理当前 profile 与各项目的 Model Context Protocol 服务。')), React.createElement('button', { className: 'vsm-primary', onClick: () => { setDraft(EMPTY); setShowForm(true) } }, '+ 添加全局 MCP')),
|
|
328
|
-
React.createElement('nav', { className: 'vsm-mcp-tabs' }, React.createElement('button', { className: tab === 'general' ? 'active' : '', onClick: () => setTab('general') }, '通用'), React.createElement('button', { className: tab === 'keys' ? 'active' : '', onClick: () => setTab('keys') }, '快捷键'), React.createElement('button', { className: tab === 'mine' ? 'active' : '', onClick: () => setTab('mine') }, '我的 MCP'), React.createElement('button', { className: tab === 'projects' ? 'active' : '', onClick: () => setTab('projects') }, '项目 MCP'), React.createElement('button', { className: tab === 'market' ? 'active' : '', onClick: () => setTab('market') }, 'MCP 市场'), React.createElement('button', { className: tab === 'compat' ? 'active' : '', onClick: () => setTab('compat') }, '兼容性')),
|
|
332
|
+
React.createElement('nav', { className: 'vsm-mcp-tabs' }, React.createElement('button', { className: tab === 'general' ? 'active' : '', onClick: () => setTab('general') }, '通用'), React.createElement('button', { className: tab === 'keys' ? 'active' : '', onClick: () => setTab('keys') }, '快捷键'), React.createElement('button', { className: tab === 'mine' ? 'active' : '', onClick: () => setTab('mine') }, '我的 MCP'), React.createElement('button', { className: tab === 'projects' ? 'active' : '', onClick: () => setTab('projects') }, '项目 MCP'), React.createElement('button', { className: tab === 'lsp' ? 'active' : '', onClick: () => setTab('lsp') }, '语言服务器'), React.createElement('button', { className: tab === 'perf' ? 'active' : '', onClick: () => setTab('perf') }, '性能优化'), React.createElement('button', { className: tab === 'market' ? 'active' : '', onClick: () => setTab('market') }, 'MCP 市场'), React.createElement('button', { className: tab === 'compat' ? 'active' : '', onClick: () => setTab('compat') }, '兼容性')),
|
|
329
333
|
error && React.createElement('div', { className: 'vsm-mcp-error vsm-mcp-banner' }, error),
|
|
330
334
|
body,
|
|
331
335
|
form,
|