如何在VSCode扩展中实现类似GitHub Copilot的内联表单UI?
实现VSCode扩展中类似GitHub Copilot的内联表单UI
我正在开发一款VSCode扩展,目标是给代码添加额外上下文信息。目前已经通过Code Actions实现了触发入口,但点击后弹出的是VSCode内置的快速输入框,无法满足需求——我需要一个类似GitHub Copilot的内联表单,包含两个输入框和一个提交按钮。
要实现这种内联式的自定义表单UI,你需要结合文本编辑器装饰(Text Editor Decorations)和Webview面板,而不是依赖内置的showInputBox。以下是完整的实现方案:
核心思路
- 用编辑器装饰在目标代码行附近标记表单的位置,预留显示空间
- 创建Webview面板并将其定位到目标行附近,模拟内联嵌入效果
- 在Webview中实现双输入框+提交按钮的表单,适配VSCode主题样式
- 通过Webview与扩展的消息通信,处理表单提交逻辑,完成代码插入后清理资源
修改后的完整代码
import * as vscode from 'vscode'; export function activate(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.languages.registerCodeActionsProvider('typescript', new AddContextCodeActionProvider(), { providedCodeActionKinds: AddContextCodeActionProvider.providedCodeActionKinds }) ); context.subscriptions.push( vscode.commands.registerCommand('extension.addContext', (line: number) => { showInlineForm(line); }) ); } class AddContextCodeActionProvider implements vscode.CodeActionProvider { static readonly providedCodeActionKinds = [ vscode.CodeActionKind.QuickFix ]; provideCodeActions(document: vscode.TextDocument, range: vscode.Range): vscode.CodeAction[] { const addContextAction = this.createAddContextAction(document, range); return [addContextAction]; } private createAddContextAction(document: vscode.TextDocument, range: vscode.Range): vscode.CodeAction { const action = new vscode.CodeAction('Add Context', vscode.CodeActionKind.QuickFix); action.command = { command: 'extension.addContext', title: 'Add Context', arguments: [range.start.line] }; return action; } } function showInlineForm(targetLine: number) { const editor = vscode.window.activeTextEditor; if (!editor) { return; } // 创建装饰标记表单位置并预留空间 const decorationType = vscode.window.createTextEditorDecorationType({ after: { contentText: ' ', margin: '0 0 0 1rem', height: '2.5rem' } }); const range = new vscode.Range(targetLine, 0, targetLine, 0); editor.setDecorations(decorationType, [range]); // 创建Webview面板 const panel = vscode.window.createWebviewPanel( 'contextForm', '', { viewColumn: editor.viewColumn || vscode.ViewColumn.One, preserveFocus: true, enableFindWidget: false, retainContextWhenHidden: false }, { enableScripts: true } ); // 配置Webview样式与大小 panel.title = ''; panel.iconPath = undefined; panel.size = { width: 400, height: 120 }; panel.webview.html = getFormHtml(); // 监听Webview消息,处理表单提交/取消 panel.webview.onDidReceiveMessage( async (message) => { if (message.type === 'submit') { const { context1, context2 } = message.data; if (context1 && context2) { await editor.edit(editBuilder => { const insertPosition = new vscode.Position(targetLine, 0); editBuilder.insert(insertPosition, `// Context 1: ${context1}\n// Context 2: ${context2}\n`); }); } editor.setDecorations(decorationType, []); panel.dispose(); } else if (message.type === 'cancel') { editor.setDecorations(decorationType, []); panel.dispose(); } }, null, context.subscriptions ); // 面板关闭时清理装饰 panel.onDidDispose(() => { editor.setDecorations(decorationType, []); }); } // 生成适配VSCode主题的表单HTML function getFormHtml() { return ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> body { margin: 10px; padding: 0; font-family: var(--vscode-font-family); font-size: var(--vscode-font-size); background-color: var(--vscode-editor-background); color: var(--vscode-editor-foreground); } .form-group { margin-bottom: 10px; } label { display: block; margin-bottom: 4px; font-weight: 500; } input { width: 100%; padding: 6px 8px; border: 1px solid var(--vscode-input-border); border-radius: 3px; background-color: var(--vscode-input-background); color: var(--vscode-input-foreground); } input:focus { outline: none; border-color: var(--vscode-focusBorder); } .button-group { display: flex; gap: 8px; justify-content: flex-end; margin-top: 12px; } button { padding: 6px 12px; border: none; border-radius: 3px; cursor: pointer; font-family: inherit; font-size: inherit; } .submit-btn { background-color: var(--vscode-button-background); color: var(--vscode-button-foreground); } .submit-btn:hover { background-color: var(--vscode-button-hoverBackground); } .cancel-btn { background-color: var(--vscode-button-secondaryBackground); color: var(--vscode-button-secondaryForeground); } .cancel-btn:hover { background-color: var(--vscode-button-secondaryHoverBackground); } </style> </head> <body> <div class="form-group"> <label for="context1">上下文1</label> <input type="text" id="context1" placeholder="输入第一个上下文信息"> </div> <div class="form-group"> <label for="context2">上下文2</label> <input type="text" id="context2" placeholder="输入第二个上下文信息"> </div> <div class="button-group"> <button class="cancel-btn" onclick="handleCancel()">取消</button> <button class="submit-btn" onclick="handleSubmit()">提交</button> </div> <script> const vscode = acquireVsCodeApi(); function handleSubmit() { const context1 = document.getElementById('context1').value.trim(); const context2 = document.getElementById('context2').value.trim(); vscode.postMessage({ type: 'submit', data: { context1, context2 } }); } function handleCancel() { vscode.postMessage({ type: 'cancel' }); } // 支持回车提交表单 document.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSubmit(); } }); </script> </body> </html> `; }
关键细节说明
- 编辑器装饰:仅用于标记位置和预留显示空间,避免Webview遮挡代码
- Webview主题适配:使用VSCode内置的CSS变量,保证表单样式与当前编辑器主题完全一致
- 消息通信:通过
postMessage和onDidReceiveMessage实现Webview与扩展的双向数据传递,处理提交和取消逻辑 - 资源清理:无论表单提交还是关闭,都会自动清理编辑器装饰并销毁Webview面板
内容的提问来源于stack exchange,提问作者Apps
相关产品推荐
相关产品推荐

