diff --git a/packages/vscode-ide-companion/assets/sidebar-icon.svg b/packages/vscode-ide-companion/assets/sidebar-icon.svg new file mode 100644 index 000000000..51cdab785 --- /dev/null +++ b/packages/vscode-ide-companion/assets/sidebar-icon.svg @@ -0,0 +1,6 @@ + + + diff --git a/packages/vscode-ide-companion/package.json b/packages/vscode-ide-companion/package.json index 37152f8b4..358aa018a 100644 --- a/packages/vscode-ide-companion/package.json +++ b/packages/vscode-ide-companion/package.json @@ -31,60 +31,60 @@ "onStartupFinished", "onView:qwenCode.chatView.panel", "onView:qwenCode.chatView.secondary", - "onCommand:qwen-code.setChatLocation.editor", - "onCommand:qwen-code.setChatLocation.panel", - "onCommand:qwen-code.setChatLocation.secondary", - "onCommand:qwen-code.openChat" + "onView:qwenCode.chatView.sidebar", + "onCommand:qwen-code.openChat", + "onCommand:qwen-code.focusChat", + "onCommand:qwen-code.newConversation", + "onCommand:qwen-code.showLogs" ], "contributes": { "configuration": { "title": "Qwen Code Companion", - "properties": { - "qwen-code.chat.location": { - "type": "string", - "default": "editor", - "enum": [ - "editor", - "panel", - "secondary" - ], - "description": "选择聊天界面放置的位置:编辑器标签页(默认)、底部面板或 Secondary Side Bar。修改后需重新打开聊天。" - } - } + "properties": {} }, "viewsContainers": { + "activitybar": [ + { + "id": "qwenCodeSidebar", + "title": "Qwen Code", + "icon": "assets/sidebar-icon.svg" + } + ], "panel": [ { "id": "qwenCodePanel", "title": "Qwen Code", - "icon": "assets/icon.png", - "when": "qwenCode.chatLocation == 'panel'" + "icon": "assets/icon.png" } ], "secondarySidebar": [ { "id": "qwenCodeSecondary", "title": "Qwen Code", - "icon": "assets/icon.png", - "when": "qwenCode.chatLocation == 'secondary'" + "icon": "assets/icon.png" } ] }, "views": { + "qwenCodeSidebar": [ + { + "type": "webview", + "id": "qwenCode.chatView.sidebar", + "name": "Qwen Code" + } + ], "qwenCodePanel": [ { "id": "qwenCode.chatView.panel", "name": "Qwen Code", - "icon": "assets/icon.png", - "when": "qwenCode.chatLocation == 'panel'" + "icon": "assets/icon.png" } ], "qwenCodeSecondary": [ { "id": "qwenCode.chatView.secondary", "name": "Qwen Code", - "icon": "assets/icon.png", - "when": "qwenCode.chatLocation == 'secondary'" + "icon": "assets/icon.png" } ] }, @@ -122,22 +122,16 @@ "title": "Qwen Code: Login" }, { - "command": "qwen-code.setChatLocation.editor", - "title": "Qwen Code: 将聊天放在编辑器标签页" + "command": "qwen-code.focusChat", + "title": "Qwen Code: Focus Chat Input" }, { - "command": "qwen-code.setChatLocation.panel", - "title": "Qwen Code: 将聊天放在底部面板" + "command": "qwen-code.newConversation", + "title": "Qwen Code: New Conversation" }, { - "command": "qwen-code.setChatLocation.secondary", - "title": "Qwen Code: 将聊天放在 Secondary Bar" - } - ], - "submenus": [ - { - "id": "qwenCode.chatLocationMenu", - "label": "Qwen Code: 聊天位置" + "command": "qwen-code.showLogs", + "title": "Qwen Code: Show Logs" } ], "menus": { @@ -169,27 +163,6 @@ { "command": "qwen-code.openChat", "group": "navigation" - }, - { - "submenu": "qwenCode.chatLocationMenu", - "group": "navigation@2" - } - ], - "qwenCode.chatLocationMenu": [ - { - "command": "qwen-code.setChatLocation.editor", - "group": "1_editor", - "when": "qwenCode.chatLocation != 'editor'" - }, - { - "command": "qwen-code.setChatLocation.panel", - "group": "2_panel", - "when": "qwenCode.chatLocation != 'panel'" - }, - { - "command": "qwen-code.setChatLocation.secondary", - "group": "3_secondary", - "when": "qwenCode.chatLocation != 'secondary'" } ] }, @@ -203,6 +176,11 @@ "command": "qwen.diff.accept", "key": "cmd+s", "when": "qwen.diff.isVisible" + }, + { + "command": "qwen-code.focusChat", + "key": "ctrl+shift+l", + "mac": "cmd+shift+l" } ] }, diff --git a/packages/vscode-ide-companion/src/commands/index.ts b/packages/vscode-ide-companion/src/commands/index.ts index 0a04fafe1..3af162bd3 100644 --- a/packages/vscode-ide-companion/src/commands/index.ts +++ b/packages/vscode-ide-companion/src/commands/index.ts @@ -6,40 +6,47 @@ import * as vscode from 'vscode'; import type { DiffManager } from '../diff-manager.js'; -import type { WebViewProvider } from '../webview/WebViewProvider.js'; +import type { WebViewProvider } from '../webview/providers/WebViewProvider.js'; +import { CHAT_VIEW_ID_SIDEBAR } from '../constants/viewIds.js'; type Logger = (message: string) => void; -export type ChatHostLocation = 'editor' | 'panel' | 'secondary'; export const runQwenCodeCommand = 'qwen-code.runQwenCode'; export const showDiffCommand = 'qwenCode.showDiff'; export const openChatCommand = 'qwen-code.openChat'; export const openNewChatTabCommand = 'qwenCode.openNewChatTab'; export const loginCommand = 'qwen-code.login'; -export const setChatLocationEditorCommand = 'qwen-code.setChatLocation.editor'; -export const setChatLocationPanelCommand = 'qwen-code.setChatLocation.panel'; -export const setChatLocationSecondaryCommand = - 'qwen-code.setChatLocation.secondary'; +export const focusChatCommand = 'qwen-code.focusChat'; +export const newConversationCommand = 'qwen-code.newConversation'; +export const showLogsCommand = 'qwen-code.showLogs'; +/** + * Register all Qwen Code chat-related commands. + * + * All chat positions (editor tab, sidebar, panel, secondary sidebar) are + * available simultaneously. `openChat` and `newConversation` always open an + * editor tab, while `focusChat` focuses the primary sidebar view. + * + * @param context - VS Code extension context for subscription management + * @param log - Logger function for debug output + * @param diffManager - Diff manager for showing file diffs + * @param getWebViewProviders - Returns all active editor-tab WebView providers + * @param createWebViewProvider - Factory to create a new editor-tab WebView provider + * @param outputChannel - Optional output channel for the showLogs command + */ export function registerNewCommands( context: vscode.ExtensionContext, log: Logger, diffManager: DiffManager, getWebViewProviders: () => WebViewProvider[], createWebViewProvider: () => WebViewProvider, - getChatHostLocation: () => ChatHostLocation, - focusChatView: () => Promise, + outputChannel?: vscode.OutputChannel, ): void { const disposables: vscode.Disposable[] = []; + // Open Chat: show the most recent editor tab or create a new one disposables.push( vscode.commands.registerCommand(openChatCommand, async () => { - const host = getChatHostLocation(); - if (host !== 'editor') { - await focusChatView(); - return; - } - const providers = getWebViewProviders(); if (providers.length > 0) { await providers[providers.length - 1].show(); @@ -75,19 +82,10 @@ export function registerNewCommands( ), ); + // Open New Chat Tab: always create a new editor tab disposables.push( vscode.commands.registerCommand(openNewChatTabCommand, async () => { - const host = getChatHostLocation(); - if (host !== 'editor') { - vscode.window.showInformationMessage( - '当前配置使用面板/Secondary Bar 承载聊天,暂不支持多标签。将为你聚焦现有聊天视图。', - ); - await focusChatView(); - return; - } - const provider = createWebViewProvider(); - // Session restoration is now disabled by default, so no need to suppress it await provider.show(); }), ); @@ -105,74 +103,33 @@ export function registerNewCommands( }), ); - const updateChatLocation = async (location: ChatHostLocation) => { - const current = getChatHostLocation(); - if (current === location) { - log(`[Command] Chat location unchanged (${location}), focusing view`); - await focusChatView(); - return; - } - - const target: vscode.ConfigurationTarget = - vscode.workspace.workspaceFolders?.length && vscode.workspace.name - ? vscode.ConfigurationTarget.Workspace - : vscode.ConfigurationTarget.Global; - - try { - await vscode.workspace - .getConfiguration('qwen-code') - .update('chat.location', location, target); - log( - `[Command] Chat location set to ${location} (target=${target}; prev=${current})`, - ); - // Update context immediately so view container visibility switches without reload - await vscode.commands.executeCommand( - 'setContext', - 'qwenCode.chatLocation', - location, - ); - - // Dispose existing editor-hosted chat panels when moving to panel/secondary to avoid confusion. - if (location !== 'editor') { - for (const provider of getWebViewProviders()) { - try { - provider.getPanel()?.dispose(); - } catch (error) { - log( - `[Command] Failed to dispose chat panel during relocation: ${error}`, - ); - } - } - } - - // Small delay to ensure context has updated before focusing - await new Promise((resolve) => setTimeout(resolve, 100)); - await focusChatView(); - void vscode.window.showInformationMessage( - `聊天位置已切换为 ${location === 'editor' ? '编辑器标签页' : location === 'panel' ? '底部面板' : 'Secondary Bar'},已尝试为你打开对应位置。如未生效,请重载窗口。`, - ); - } catch (error) { - const msg = error instanceof Error ? error.message : String(error); - log(`[Command] Failed to set chat location: ${msg}`); - void vscode.window.showErrorMessage( - `切换聊天位置失败:${msg}。可在 Settings 搜索 "Qwen Code Chat Location" 手动修改。`, - ); - } - }; - + // Focus Chat: bring the primary sidebar chat view to front disposables.push( - vscode.commands.registerCommand(setChatLocationEditorCommand, async () => { - await updateChatLocation('editor'); + vscode.commands.registerCommand(focusChatCommand, async () => { + await vscode.commands.executeCommand(`${CHAT_VIEW_ID_SIDEBAR}.focus`); }), - vscode.commands.registerCommand(setChatLocationPanelCommand, async () => { - await updateChatLocation('panel'); - }), - vscode.commands.registerCommand( - setChatLocationSecondaryCommand, - async () => { - await updateChatLocation('secondary'); - }, - ), ); + + // New Conversation: open a new editor tab for a fresh conversation + disposables.push( + vscode.commands.registerCommand(newConversationCommand, async () => { + const provider = createWebViewProvider(); + await provider.show(); + }), + ); + + // Show Logs: reveal the output channel + disposables.push( + vscode.commands.registerCommand(showLogsCommand, async () => { + if (outputChannel) { + outputChannel.show(true); + } else { + vscode.window.showWarningMessage( + 'Qwen Code Companion log channel is not available.', + ); + } + }), + ); + context.subscriptions.push(...disposables); } diff --git a/packages/vscode-ide-companion/src/constants/viewIds.ts b/packages/vscode-ide-companion/src/constants/viewIds.ts new file mode 100644 index 000000000..96862d058 --- /dev/null +++ b/packages/vscode-ide-companion/src/constants/viewIds.ts @@ -0,0 +1,13 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * WebviewView IDs for the three host positions where the chat UI can appear. + * These IDs must match the `views` contributions declared in package.json. + */ +export const CHAT_VIEW_ID_PANEL = 'qwenCode.chatView.panel'; +export const CHAT_VIEW_ID_SECONDARY = 'qwenCode.chatView.secondary'; +export const CHAT_VIEW_ID_SIDEBAR = 'qwenCode.chatView.sidebar'; diff --git a/packages/vscode-ide-companion/src/extension.test.ts b/packages/vscode-ide-companion/src/extension.test.ts index ef0d5ad46..d6128f91e 100644 --- a/packages/vscode-ide-companion/src/extension.test.ts +++ b/packages/vscode-ide-companion/src/extension.test.ts @@ -43,6 +43,9 @@ vi.mock('vscode', () => ({ registerWebviewPanelSerializer: vi.fn(() => ({ dispose: vi.fn(), })), + registerWebviewViewProvider: vi.fn(() => ({ + dispose: vi.fn(), + })), }, workspace: { workspaceFolders: [], diff --git a/packages/vscode-ide-companion/src/extension.ts b/packages/vscode-ide-companion/src/extension.ts index 0efe48c6b..55c3f5299 100644 --- a/packages/vscode-ide-companion/src/extension.ts +++ b/packages/vscode-ide-companion/src/extension.ts @@ -14,43 +14,20 @@ import { IDE_DEFINITIONS, type IdeInfo, } from '@qwen-code/qwen-code-core/src/ide/detect-ide.js'; -import { WebViewProvider } from './webview/WebViewProvider.js'; +import { WebViewProvider } from './webview/providers/WebViewProvider.js'; +import { ChatWebviewViewProvider } from './webview/providers/ChatWebviewViewProvider.js'; import { - ChatWebviewViewProvider, CHAT_VIEW_ID_PANEL, CHAT_VIEW_ID_SECONDARY, -} from './webview/ChatWebviewViewProvider.js'; -import { - registerNewCommands, - type ChatHostLocation, -} from './commands/index.js'; + CHAT_VIEW_ID_SIDEBAR, +} from './constants/viewIds.js'; +import { registerNewCommands } from './commands/index.js'; import { ReadonlyFileSystemProvider } from './services/readonlyFileSystemProvider.js'; import { isWindows } from './utils/platform.js'; const CLI_IDE_COMPANION_IDENTIFIER = 'qwenlm.qwen-code-vscode-ide-companion'; const INFO_MESSAGE_SHOWN_KEY = 'qwenCodeInfoMessageShown'; export const DIFF_SCHEME = 'qwen-diff'; -const CHAT_LOCATION_SETTING_KEY = 'chat.location'; - -function getChatHostLocation(): ChatHostLocation { - const location = vscode.workspace - .getConfiguration('qwen-code') - .get(CHAT_LOCATION_SETTING_KEY, 'editor'); - - if (location === 'panel' || location === 'secondary') { - return location; - } - return 'editor'; -} - -async function setChatLocationContext(location: ChatHostLocation) { - console.log('[Extension] setChatLocationContext ->', location); - await vscode.commands.executeCommand( - 'setContext', - 'qwenCode.chatLocation', - location, - ); -} /** * IDE environments where the installation greeting is hidden. In these @@ -172,9 +149,6 @@ export async function activate(context: vscode.ExtensionContext) { }, ); - const initialChatLocation = getChatHostLocation(); - await setChatLocationContext(initialChatLocation); - // Helper function to create a new WebView provider instance const createWebViewProvider = (): WebViewProvider => { const provider = new WebViewProvider(context, context.extensionUri); @@ -182,43 +156,24 @@ export async function activate(context: vscode.ExtensionContext) { return provider; }; - // Register WebviewView hosts (panel and secondary sidebar). They are shown based on config conditions. - const chatViewProviderPanel = new ChatWebviewViewProvider( - createWebViewProvider(), - context.extensionUri, - 'panel', - ); - const chatViewProviderSecondary = new ChatWebviewViewProvider( - createWebViewProvider(), - context.extensionUri, - 'secondary', - ); - console.log( - '[Extension] Registering WebviewView providers for panel and secondary', - ); - const panelProviderDisposable = vscode.window.registerWebviewViewProvider( + // Register WebviewView hosts for all positions (sidebar, panel, secondary). + // Providers are lazily instantiated — the factory is only called when VS Code + // actually opens the view, keeping startup lightweight. + const chatViewIds = [ + CHAT_VIEW_ID_SIDEBAR, CHAT_VIEW_ID_PANEL, - chatViewProviderPanel, - { - webviewOptions: { retainContextWhenHidden: true }, - }, - ); - const secondaryProviderDisposable = vscode.window.registerWebviewViewProvider( CHAT_VIEW_ID_SECONDARY, - chatViewProviderSecondary, - { - webviewOptions: { retainContextWhenHidden: true }, - }, - ); - console.log( - '[Extension] WebviewView providers registered:', - panelProviderDisposable ? 'panel' : 'panel (missing)', - secondaryProviderDisposable ? 'secondary' : 'secondary (missing)', - ); - context.subscriptions.push( - panelProviderDisposable, - secondaryProviderDisposable, - ); + ] as const; + + for (const viewId of chatViewIds) { + context.subscriptions.push( + vscode.window.registerWebviewViewProvider( + viewId, + new ChatWebviewViewProvider(createWebViewProvider), + { webviewOptions: { retainContextWhenHidden: true } }, + ), + ); + } // Register WebView panel serializer for persistence across reloads context.subscriptions.push( @@ -257,43 +212,6 @@ export async function activate(context: vscode.ExtensionContext) { }), ); - const focusChatView = async () => { - const host = getChatHostLocation(); - if (host === 'editor') { - const providers = webViewProviders; - if (providers.length > 0) { - await providers[providers.length - 1].show(); - } else { - const provider = createWebViewProvider(); - await provider.show(); - } - return; - } - - const viewId = - host === 'secondary' ? CHAT_VIEW_ID_SECONDARY : CHAT_VIEW_ID_PANEL; - try { - // Ensure the view provider is registered and ready before focusing - await new Promise((resolve) => setTimeout(resolve, 200)); - await vscode.commands.executeCommand(`${viewId}.focus`); - - // Wait a bit more to ensure the view has initialized - await new Promise((resolve) => setTimeout(resolve, 300)); - } catch (err) { - log(`Failed to focus chat view (${viewId}): ${err}`); - // Try to ensure the view provider exists by creating new ones if needed - try { - const currentProviders = webViewProviders; - if (currentProviders.length === 0) { - createWebViewProvider(); // Create a new provider if none exist - } - await vscode.commands.executeCommand(`${viewId}.focus`); - } catch (retryErr) { - log(`Retry failed to focus chat view (${viewId}): ${retryErr}`); - } - } - }; - // Register newly added commands via commands module registerNewCommands( context, @@ -301,8 +219,7 @@ export async function activate(context: vscode.ExtensionContext) { diffManager, () => webViewProviders, createWebViewProvider, - getChatHostLocation, - focusChatView, + logger, ); context.subscriptions.push( @@ -311,48 +228,6 @@ export async function activate(context: vscode.ExtensionContext) { diffManager.cancelDiff(doc.uri); } }), - vscode.workspace.onDidChangeConfiguration(async (event) => { - if (event.affectsConfiguration('qwen-code.chat.location')) { - const newLocation = getChatHostLocation(); - log( - `[Extension] Configuration changed: chat.location -> ${newLocation}`, - ); - await setChatLocationContext(newLocation); - if (newLocation !== 'editor') { - for (const provider of webViewProviders) { - try { - provider.getPanel()?.dispose(); - } catch (err) { - log( - `[Extension] Failed to dispose chat panel after location change: ${err}`, - ); - } - } - } - - // Focus the appropriate view after config change - if (newLocation === 'panel' || newLocation === 'secondary') { - const viewId = - newLocation === 'secondary' - ? CHAT_VIEW_ID_SECONDARY - : CHAT_VIEW_ID_PANEL; - try { - // Give VS Code a moment to update the context and UI - await new Promise((resolve) => setTimeout(resolve, 300)); - await vscode.commands.executeCommand(`${viewId}.focus`); - - // Refresh the view to ensure content appears - await new Promise((resolve) => setTimeout(resolve, 200)); - // Trigger a re-render by focusing again after a delay - await vscode.commands.executeCommand(`${viewId}.focus`); - } catch (err) { - log( - `[Extension] Failed to focus chat view after location change: ${err}`, - ); - } - } - } - }), vscode.workspace.registerTextDocumentContentProvider( DIFF_SCHEME, diffContentProvider, diff --git a/packages/vscode-ide-companion/src/utils/editorGroupUtils.ts b/packages/vscode-ide-companion/src/utils/editorGroupUtils.ts index 13897412c..e855b2dec 100644 --- a/packages/vscode-ide-companion/src/utils/editorGroupUtils.ts +++ b/packages/vscode-ide-companion/src/utils/editorGroupUtils.ts @@ -7,23 +7,13 @@ import * as vscode from 'vscode'; import { openChatCommand } from '../commands/index.js'; -function isEditorHostedChat(): boolean { - const location = vscode.workspace - .getConfiguration('qwen-code') - .get('chat.location', 'editor'); - return location === 'editor'; -} - /** * Find the editor group immediately to the left of the Qwen chat webview. * - If the chat webview group is the leftmost group, returns undefined. + * - If no chat webview is found in any editor group, returns undefined. * - Uses the webview tab viewType 'mainThreadWebview-qwenCode.chat'. */ export function findLeftGroupOfChatWebview(): vscode.ViewColumn | undefined { - if (!isEditorHostedChat()) { - return undefined; - } - try { const groups = vscode.window.tabGroups.all; @@ -103,10 +93,6 @@ function waitForTabGroupsCondition( export async function ensureLeftGroupOfChatWebview(): Promise< vscode.ViewColumn | undefined > { - if (!isEditorHostedChat()) { - return undefined; - } - // First try to find an existing left neighbor const existing = findLeftGroupOfChatWebview(); if (existing !== undefined) { diff --git a/packages/vscode-ide-companion/src/webview/ChatWebviewViewProvider.ts b/packages/vscode-ide-companion/src/webview/ChatWebviewViewProvider.ts deleted file mode 100644 index 5eb906ac5..000000000 --- a/packages/vscode-ide-companion/src/webview/ChatWebviewViewProvider.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode'; -import type { WebViewProvider } from './WebViewProvider.js'; - -export const CHAT_VIEW_ID_PANEL = 'qwenCode.chatView.panel'; -export const CHAT_VIEW_ID_SECONDARY = 'qwenCode.chatView.secondary'; - -/** - * WebviewView host for placing the chat UI in panel/secondary sidebar. - */ -export class ChatWebviewViewProvider implements vscode.WebviewViewProvider { - constructor( - private readonly webViewProvider: WebViewProvider, - private readonly extensionUri: vscode.Uri, - private readonly hostKind: 'panel' | 'secondary', - ) {} - - async resolveWebviewView(webviewView: vscode.WebviewView): Promise { - console.log( - `[ChatWebviewViewProvider] resolveWebviewView invoked for ${webviewView.viewType} (host=${this.hostKind})`, - ); - // Determine the view ID from the webviewView - const viewId = webviewView.viewType; // This will be either 'qwenCode.chatView.panel' or 'qwenCode.chatView.secondary' - - // Ensure scripts/resources are allowed - webviewView.webview.options = { - enableScripts: true, - localResourceRoots: [ - vscode.Uri.joinPath(this.extensionUri, 'dist'), - vscode.Uri.joinPath(this.extensionUri, 'assets'), - ], - }; - - await this.webViewProvider.attachToView(webviewView, viewId); - } -} diff --git a/packages/vscode-ide-companion/src/webview/providers/ChatWebviewViewProvider.ts b/packages/vscode-ide-companion/src/webview/providers/ChatWebviewViewProvider.ts new file mode 100644 index 000000000..ffce1152a --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/providers/ChatWebviewViewProvider.ts @@ -0,0 +1,47 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +import type * as vscode from 'vscode'; +import type { WebViewProvider } from './WebViewProvider.js'; + +/** + * Factory function type that lazily creates a WebViewProvider instance. + * The provider is only instantiated when VS Code actually opens the view. + */ +export type WebViewProviderFactory = () => WebViewProvider; + +/** + * WebviewView host for placing the chat UI in sidebar / panel / secondary sidebar. + * + * Accepts a factory function instead of a pre-built WebViewProvider so the + * heavyweight provider (QwenAgentManager, ConversationStore, etc.) is only + * created when VS Code actually opens the view, not at extension startup. + */ +export class ChatWebviewViewProvider implements vscode.WebviewViewProvider { + private webViewProvider: WebViewProvider | null = null; + + /** + * @param createWebViewProvider - Factory that creates a WebViewProvider on demand + */ + constructor(private readonly createWebViewProvider: WebViewProviderFactory) {} + + /** + * Called by VS Code when the webview view becomes visible for the first time. + * Creates the WebViewProvider lazily and attaches the webview. + * + * @param webviewView - The webview view created by VS Code + */ + async resolveWebviewView(webviewView: vscode.WebviewView): Promise { + // Lazily create the provider on first resolve + if (!this.webViewProvider) { + this.webViewProvider = this.createWebViewProvider(); + } + + // Webview options (enableScripts, localResourceRoots) are configured + // inside WebViewProvider.attachToView — no duplication needed here. + await this.webViewProvider.attachToView(webviewView, webviewView.viewType); + } +} diff --git a/packages/vscode-ide-companion/src/webview/MessageHandler.ts b/packages/vscode-ide-companion/src/webview/providers/MessageHandler.ts similarity index 84% rename from packages/vscode-ide-companion/src/webview/MessageHandler.ts rename to packages/vscode-ide-companion/src/webview/providers/MessageHandler.ts index 30b9abe56..7507ad4e1 100644 --- a/packages/vscode-ide-companion/src/webview/MessageHandler.ts +++ b/packages/vscode-ide-companion/src/webview/providers/MessageHandler.ts @@ -4,10 +4,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { QwenAgentManager } from '../services/qwenAgentManager.js'; -import type { ConversationStore } from '../services/conversationStore.js'; -import type { PermissionResponseMessage } from '../types/webviewMessageTypes.js'; -import { MessageRouter } from './handlers/MessageRouter.js'; +import type { QwenAgentManager } from '../../services/qwenAgentManager.js'; +import type { ConversationStore } from '../../services/conversationStore.js'; +import type { PermissionResponseMessage } from '../../types/webviewMessageTypes.js'; +import { MessageRouter } from '../handlers/MessageRouter.js'; /** * MessageHandler (Refactored Version) diff --git a/packages/vscode-ide-companion/src/webview/PanelManager.ts b/packages/vscode-ide-companion/src/webview/providers/PanelManager.ts similarity index 100% rename from packages/vscode-ide-companion/src/webview/PanelManager.ts rename to packages/vscode-ide-companion/src/webview/providers/PanelManager.ts diff --git a/packages/vscode-ide-companion/src/webview/WebViewContent.ts b/packages/vscode-ide-companion/src/webview/providers/WebViewContent.ts similarity index 96% rename from packages/vscode-ide-companion/src/webview/WebViewContent.ts rename to packages/vscode-ide-companion/src/webview/providers/WebViewContent.ts index f3f934890..bdc71e0ba 100644 --- a/packages/vscode-ide-companion/src/webview/WebViewContent.ts +++ b/packages/vscode-ide-companion/src/webview/providers/WebViewContent.ts @@ -5,7 +5,7 @@ */ import * as vscode from 'vscode'; -import { escapeHtml } from './utils/webviewUtils.js'; +import { escapeHtml } from '../utils/webviewUtils.js'; /** * WebView HTML Content Generator diff --git a/packages/vscode-ide-companion/src/webview/WebViewProvider.ts b/packages/vscode-ide-companion/src/webview/providers/WebViewProvider.ts similarity index 98% rename from packages/vscode-ide-companion/src/webview/WebViewProvider.ts rename to packages/vscode-ide-companion/src/webview/providers/WebViewProvider.ts index 5a9bd4346..d926b8315 100644 --- a/packages/vscode-ide-companion/src/webview/WebViewProvider.ts +++ b/packages/vscode-ide-companion/src/webview/providers/WebViewProvider.ts @@ -5,17 +5,17 @@ */ import * as vscode from 'vscode'; -import { QwenAgentManager } from '../services/qwenAgentManager.js'; -import { ConversationStore } from '../services/conversationStore.js'; -import type { AcpPermissionRequest } from '../types/acpTypes.js'; -import type { ModelInfo } from '../types/acpTypes.js'; -import type { PermissionResponseMessage } from '../types/webviewMessageTypes.js'; -import { PanelManager } from '../webview/PanelManager.js'; -import { MessageHandler } from '../webview/MessageHandler.js'; -import { WebViewContent } from '../webview/WebViewContent.js'; -import { getFileName } from './utils/webviewUtils.js'; -import { type ApprovalModeValue } from '../types/approvalModeValueTypes.js'; -import { isAuthenticationRequiredError } from '../utils/authErrors.js'; +import { QwenAgentManager } from '../../services/qwenAgentManager.js'; +import { ConversationStore } from '../../services/conversationStore.js'; +import type { AcpPermissionRequest } from '../../types/acpTypes.js'; +import type { ModelInfo } from '../../types/acpTypes.js'; +import type { PermissionResponseMessage } from '../../types/webviewMessageTypes.js'; +import { PanelManager } from './PanelManager.js'; +import { MessageHandler } from './MessageHandler.js'; +import { WebViewContent } from './WebViewContent.js'; +import { getFileName } from '../utils/webviewUtils.js'; +import { type ApprovalModeValue } from '../../types/approvalModeValueTypes.js'; +import { isAuthenticationRequiredError } from '../../utils/authErrors.js'; export class WebViewProvider { private panelManager: PanelManager;