From 9de11ccabb7f0b8c6ecd34ede1a0a2e48720d0d1 Mon Sep 17 00:00:00 2001 From: equence <1336543924@qq.com> Date: Wed, 10 Jun 2026 15:45:18 +0800 Subject: [PATCH] feat(serial-monitor): add local echo setting and update related UI components --- .gitignore | 3 +- src/services/builtinSerialMonitorService.ts | 5 +++ src/services/workspaceStateService.ts | 14 ++++++ src/test/vueWebviewAssets.test.ts | 10 ++++- src/utils/vueWebviewAssets.ts | 5 ++- .../src/components/layout/AppShell.vue | 2 +- webview-vue/src/i18n/locales/en.json | 2 + webview-vue/src/i18n/locales/zh.json | 2 + webview-vue/src/types/index.ts | 1 + webview-vue/src/views/SerialMonitorPage.vue | 44 ++++++++++++++++++- 10 files changed, 82 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 3ebc869..b2725b0 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ designs/ .codegraph/ .mcp.json -.claude/ \ No newline at end of file +.claude/ +.planning/ \ No newline at end of file diff --git a/src/services/builtinSerialMonitorService.ts b/src/services/builtinSerialMonitorService.ts index 2e1677a..69aa866 100644 --- a/src/services/builtinSerialMonitorService.ts +++ b/src/services/builtinSerialMonitorService.ts @@ -78,6 +78,7 @@ export interface SerialReadResult { export interface SerialMonitorSettings { showTimestamp: boolean; renderAnsi: boolean; + localEcho: boolean; logBaudRate: number; } @@ -770,6 +771,9 @@ export class BuiltinSerialMonitorService { if (typeof settings.renderAnsi === 'boolean') { await this.workspaceStateService.setSerialMonitorRenderAnsi(settings.renderAnsi); } + if (typeof settings.localEcho === 'boolean') { + await this.workspaceStateService.setSerialMonitorLocalEcho(settings.localEcho); + } const nextBaudRate = this.resolveLogBaudRate(settings.logBaudRate); if (nextBaudRate === undefined) { @@ -956,6 +960,7 @@ export class BuiltinSerialMonitorService { return { showTimestamp: this.workspaceStateService.getSerialMonitorShowTimestamp(), renderAnsi: this.workspaceStateService.getSerialMonitorRenderAnsi(), + localEcho: this.workspaceStateService.getSerialMonitorLocalEcho(), logBaudRate: this.workspaceStateService.getMonitorBaudRate(), }; } diff --git a/src/services/workspaceStateService.ts b/src/services/workspaceStateService.ts index fc9ac61..49d7c9e 100644 --- a/src/services/workspaceStateService.ts +++ b/src/services/workspaceStateService.ts @@ -28,6 +28,8 @@ export interface WorkspaceState { serialMonitorShowTimestamp?: boolean; // 串口监视器是否渲染 VT-100/ANSI 颜色 serialMonitorRenderAnsi?: boolean; + // 串口监视器终端模式是否本地回显输入 + serialMonitorLocalEcho?: boolean; } // 工作区状态的 key 常量 @@ -43,6 +45,7 @@ export const WORKSPACE_STATE_KEYS = { WORKFLOW_SHELL_APPROVALS: 'workflowShellApprovals', SERIAL_MONITOR_SHOW_TIMESTAMP: 'serialMonitorShowTimestamp', SERIAL_MONITOR_RENDER_ANSI: 'serialMonitorRenderAnsi', + SERIAL_MONITOR_LOCAL_ECHO: 'serialMonitorLocalEcho', } as const; // 默认值 @@ -58,6 +61,7 @@ const DEFAULT_VALUES: Required = { workflowShellApprovals: {}, serialMonitorShowTimestamp: true, serialMonitorRenderAnsi: true, + serialMonitorLocalEcho: false, }; /** @@ -134,6 +138,7 @@ export class WorkspaceStateService { workflowShellApprovals: this.get('workflowShellApprovals'), serialMonitorShowTimestamp: this.get('serialMonitorShowTimestamp'), serialMonitorRenderAnsi: this.get('serialMonitorRenderAnsi'), + serialMonitorLocalEcho: this.get('serialMonitorLocalEcho'), }; } @@ -277,4 +282,13 @@ export class WorkspaceStateService { public async setSerialMonitorRenderAnsi(value: boolean): Promise { await this.set('serialMonitorRenderAnsi', value); } + + // serialMonitorLocalEcho + public getSerialMonitorLocalEcho(): boolean { + return this.get('serialMonitorLocalEcho') ?? DEFAULT_VALUES.serialMonitorLocalEcho; + } + + public async setSerialMonitorLocalEcho(value: boolean): Promise { + await this.set('serialMonitorLocalEcho', value); + } } diff --git a/src/test/vueWebviewAssets.test.ts b/src/test/vueWebviewAssets.test.ts index 01e30e2..eb27948 100644 --- a/src/test/vueWebviewAssets.test.ts +++ b/src/test/vueWebviewAssets.test.ts @@ -25,10 +25,16 @@ describe('vueWebviewAssets', () => { return dir; } - it('loads only the default entry css for the main Vue webview', () => { + it('loads the default entry css and shared xterm css for the main Vue webview', () => { const distPath = createDistWithCssFiles(['index.css', 'buildLogs.css', 'xterm.css']); - assert.deepStrictEqual(resolveVueWebviewCssFiles(distPath), ['assets/index.css']); + assert.deepStrictEqual(resolveVueWebviewCssFiles(distPath), ['assets/index.css', 'assets/xterm.css']); + }); + + it('keeps unrelated entry css out of the main Vue webview', () => { + const distPath = createDistWithCssFiles(['index.css', 'buildLogs.css', 'memoryMap.css', 'xterm.css']); + + assert.deepStrictEqual(resolveVueWebviewCssFiles(distPath), ['assets/index.css', 'assets/xterm.css']); }); it('uses explicit css files for the build logs webview', () => { diff --git a/src/utils/vueWebviewAssets.ts b/src/utils/vueWebviewAssets.ts index bd56d0a..73d9a6e 100644 --- a/src/utils/vueWebviewAssets.ts +++ b/src/utils/vueWebviewAssets.ts @@ -15,6 +15,9 @@ export function resolveVueWebviewCssFiles( const scriptName = path.posix.basename(normalizedScriptFile, path.posix.extname(normalizedScriptFile)); const entryCssFile = path.posix.join(scriptDir, `${scriptName}.css`); const entryCssPath = path.join(vueDistPath, ...entryCssFile.split('/')); + const sharedCssFiles = ['xterm.css'] + .map(file => path.posix.join(scriptDir, file)) + .filter(file => file !== entryCssFile && fs.existsSync(path.join(vueDistPath, ...file.split('/')))); - return fs.existsSync(entryCssPath) ? [entryCssFile] : []; + return fs.existsSync(entryCssPath) ? [entryCssFile, ...sharedCssFiles] : sharedCssFiles; } diff --git a/webview-vue/src/components/layout/AppShell.vue b/webview-vue/src/components/layout/AppShell.vue index 0f6ad15..bc0c110 100644 --- a/webview-vue/src/components/layout/AppShell.vue +++ b/webview-vue/src/components/layout/AppShell.vue @@ -57,7 +57,7 @@ const isStandaloneRoute = computed( const showBackButton = computed(() => route.path !== '/' && !isStandaloneRoute.value); const rootClass = computed(() => isViewportLockedRoute.value - ? 'h-screen min-h-0 overflow-hidden p-0 text-vscode-foreground' + ? 'h-full min-h-0 overflow-hidden p-0 text-vscode-foreground' : 'min-h-screen p-0 text-vscode-foreground' ); const shellClass = computed(() => diff --git a/webview-vue/src/i18n/locales/en.json b/webview-vue/src/i18n/locales/en.json index cd94d61..3a62c31 100644 --- a/webview-vue/src/i18n/locales/en.json +++ b/webview-vue/src/i18n/locales/en.json @@ -597,6 +597,8 @@ "description": "These preferences are saved in the current workspace and apply when the monitor is reopened.", "logBaudRate": "Log baud rate", "logBaudRateDescription": "Used by serial logs and MCP sessions. It does not change the status bar COM download baud rate.", + "localEcho": "Local echo terminal input", + "localEchoDescription": "Show typed characters immediately in terminal mode. Keep this off when the device echoes input itself.", "renderAnsi": "Render VT-100 colors", "renderAnsiDescription": "Parse ANSI SGR color sequences. Turn it off to show logs as plain text.", "showTimestamp": "Show timestamps", diff --git a/webview-vue/src/i18n/locales/zh.json b/webview-vue/src/i18n/locales/zh.json index 7946c19..955f7cb 100644 --- a/webview-vue/src/i18n/locales/zh.json +++ b/webview-vue/src/i18n/locales/zh.json @@ -597,6 +597,8 @@ "description": "这些偏好会保存在当前工作区,重新打开监视器后仍会生效。", "logBaudRate": "日志波特率", "logBaudRateDescription": "用于串口日志和 MCP 会话,不影响状态栏 COM 下载波特率。", + "localEcho": "终端输入本地回显", + "localEchoDescription": "开启后,在终端模式中输入的字符会立即显示;如果设备自身会回显,请保持关闭。", "renderAnsi": "渲染 VT-100 颜色", "renderAnsiDescription": "解析 ANSI SGR 颜色序列,关闭后会以普通文本显示日志。", "showTimestamp": "显示时间戳", diff --git a/webview-vue/src/types/index.ts b/webview-vue/src/types/index.ts index f0912dc..bb565f3 100644 --- a/webview-vue/src/types/index.ts +++ b/webview-vue/src/types/index.ts @@ -323,6 +323,7 @@ export interface SerialMonitorSnapshot { settings: { showTimestamp: boolean; renderAnsi: boolean; + localEcho: boolean; logBaudRate: number; }; } diff --git a/webview-vue/src/views/SerialMonitorPage.vue b/webview-vue/src/views/SerialMonitorPage.vue index f01b007..ec40ba5 100644 --- a/webview-vue/src/views/SerialMonitorPage.vue +++ b/webview-vue/src/views/SerialMonitorPage.vue @@ -164,6 +164,18 @@ + +