From f2a74c74b6eac2190919b7b3fcdb9ec1d4f0d256 Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Tue, 9 Dec 2025 15:53:19 +0800 Subject: [PATCH] feat(vscode-ide-companion): enhance panel manager group tracking and cleanup - Add panelGroupViewColumn tracking for precise group identification - Implement improved tab capture mechanism with delayed execution - Add robust group cleanup logic to close empty locked columns - Implement focusGroupByColumn helper for reliable group focusing - Add proper error handling and fallback mechanisms for group operations --- .../src/webview/PanelManager.ts | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/packages/vscode-ide-companion/src/webview/PanelManager.ts b/packages/vscode-ide-companion/src/webview/PanelManager.ts index 283276a8..72c7fa7b 100644 --- a/packages/vscode-ide-companion/src/webview/PanelManager.ts +++ b/packages/vscode-ide-companion/src/webview/PanelManager.ts @@ -13,6 +13,10 @@ import * as vscode from 'vscode'; export class PanelManager { private panel: vscode.WebviewPanel | null = null; private panelTab: vscode.Tab | null = null; + // Best-effort tracking of the group (by view column) that currently hosts + // the Qwen webview. We update this when creating/revealing the panel and + // whenever we can capture the Tab from the tab model. + private panelGroupViewColumn: vscode.ViewColumn | null = null; constructor( private extensionUri: vscode.Uri, @@ -64,6 +68,8 @@ export class PanelManager { ], }, ); + // Track the group column hosting this panel + this.panelGroupViewColumn = existingGroup.viewColumn; } else { // If no existing Qwen Code group, create a new group to the right of the active editor group try { @@ -114,6 +120,9 @@ export class PanelManager { // Lock the group after creation await this.autoLockEditorGroup(); + + // Track the newly created group's column + this.panelGroupViewColumn = newGroupColumn; } // Set panel icon to Qwen logo @@ -123,6 +132,10 @@ export class PanelManager { 'icon.png', ); + // Try to capture Tab info shortly after creation so we can track the + // precise group even if the user later drags the tab between groups. + this.captureTab(); + return true; // New panel created } @@ -216,6 +229,19 @@ export class PanelManager { return !!(sameViewType || sameLabel); }); this.panelTab = match ?? null; + // Update last-known group column if we can read it from the captured tab + try { + const groupViewColumn = ( + this.panelTab as unknown as { + group?: { viewColumn?: vscode.ViewColumn }; + } + )?.group?.viewColumn; + if (groupViewColumn != null) { + this.panelGroupViewColumn = groupViewColumn as vscode.ViewColumn; + } + } catch { + // Best effort only; ignore if the API shape differs + } }, 50); } @@ -230,15 +256,94 @@ export class PanelManager { this.panel.onDidDispose( () => { + // Capture the group we intend to clean up before we clear fields + const targetColumn = + // Prefer the group from the captured tab if available + (( + this.panelTab as unknown as { + group?: { viewColumn?: vscode.ViewColumn }; + } + )?.group?.viewColumn as vscode.ViewColumn | undefined) ?? + // Fall back to our last-known group column + this.panelGroupViewColumn ?? + undefined; + this.panel = null; this.panelTab = null; this.onPanelDispose(); + + // After VS Code updates its tab model, check if that group is now + // empty (and typically locked for Qwen). If so, close the group to + // avoid leaving an empty locked column when the user closes Qwen. + if (targetColumn != null) { + setTimeout(async () => { + try { + const groups = vscode.window.tabGroups.all; + const group = groups.find((g) => g.viewColumn === targetColumn); + // If the group that hosted Qwen is now empty, close it to avoid + // leaving an empty locked column around. VS Code's stable API + // does not expose the lock state on TabGroup, so we only check + // for emptiness here. + if (group && group.tabs.length === 0) { + // Focus the group we want to close + await this.focusGroupByColumn(targetColumn); + // Try closeGroup first; fall back to removeActiveEditorGroup + try { + await vscode.commands.executeCommand( + 'workbench.action.closeGroup', + ); + } catch { + try { + await vscode.commands.executeCommand( + 'workbench.action.removeActiveEditorGroup', + ); + } catch (err) { + console.warn( + '[PanelManager] Failed to close empty group after Qwen panel disposed:', + err, + ); + } + } + } + } catch (err) { + console.warn( + '[PanelManager] Error while trying to close empty Qwen group:', + err, + ); + } + }, 50); + } }, null, disposables, ); } + /** + * Focus the editor group at the given view column by stepping left/right. + * This avoids depending on Nth-group focus commands that may not exist. + */ + private async focusGroupByColumn(target: vscode.ViewColumn): Promise { + const maxHops = 20; // safety guard for unusual layouts + let hops = 0; + while ( + vscode.window.tabGroups.activeTabGroup.viewColumn !== target && + hops < maxHops + ) { + const current = vscode.window.tabGroups.activeTabGroup.viewColumn; + if (current < target) { + await vscode.commands.executeCommand( + 'workbench.action.focusRightGroup', + ); + } else if (current > target) { + await vscode.commands.executeCommand('workbench.action.focusLeftGroup'); + } else { + break; + } + hops++; + } + } + /** * Register the view state change event handler * @param disposables Array used to store Disposable objects