Skip to content

Commit

Permalink
Bug Fix: unexpected window behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
Talamantez committed Nov 22, 2024
1 parent 37b58ff commit f2eb4e7
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 56 deletions.
5 changes: 2 additions & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
},
"ghcr.io/devcontainers/features/git:1": {}
},
"postCreateCommand": "npm install",
"remoteUser": "node",
"postStartCommand": "npm install -g @vscode/vsce"
"postCreateCommand": "sudo apt-get update && sudo apt-get install -y libnss3 libatk1.0-0 libatk-bridge2.0-0 libdrm2 libgtk-3-0 libasound2 libgbm1 xvfb && npm install -g @vscode/vsce && npm install",
"remoteUser": "node"
}
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.1.12] - 2024-11-22
### Fixed
- Bug: Panel was closing, brought extension under docker vm, updated tests

## [1.1.11] - 2024-11-22
### Fixed
### ~Fixed~
- Bug: Panel was closing, changed preview mode to false

## [1.1.10] - 2024-11-21
Expand Down
10 changes: 9 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:18

# Install additional OS tools
# Install additional OS tools and VS Code test dependencies
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends \
git \
openssh-client \
libnss3 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libdrm2 \
libgtk-3-0 \
libasound2 \
libgbm1 \
xvfb \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "claude-vscode-assistant",
"displayName": "Claude AI Assistant",
"version": "1.1.11",
"version": "1.1.12",
"description": "Claude AI assistant for Visual Studio Code",
"publisher": "conscious-robot",
"repository": {
Expand Down Expand Up @@ -99,7 +99,7 @@
"publish": "pnpm run verify && vsce publish",
"verify": "pnpm run build && pnpm run test",
"pretest": "pnpm run build && pnpm run compile-tests",
"test": "node --force-node-api-uncaught-exceptions-policy=true ./out/test/runTest.js",
"test": "xvfb-run --auto-servernum --server-args='-screen 0 1024x768x24' node --force-node-api-uncaught-exceptions-policy=true ./out/test/runTest.js",
"compile-tests": "tsc -p ./tsconfig.test.json",
"watch-tests": "tsc -p ./tsconfig.test.json --watch"
},
Expand All @@ -121,4 +121,4 @@
"ts-node": "^10.9.2",
"typescript": "^5.6.3"
}
}
}
71 changes: 71 additions & 0 deletions src/editor-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// src/editor-utils.ts
import * as vscode from 'vscode';

export interface EditorRetryOptions {
maxAttempts?: number;
delayMs?: number;
timeout?: number;
}

const DEFAULT_OPTIONS: EditorRetryOptions = {
maxAttempts: 3,
delayMs: 100,
timeout: 5000
};

export class EditorTimeoutError extends Error {
constructor(message: string) {
super(message);
this.name = 'EditorTimeoutError';
}
}

/**
* Waits for the active editor with retries
*/
export async function waitForActiveEditor(options: EditorRetryOptions = {}): Promise<vscode.TextEditor> {
const opts = { ...DEFAULT_OPTIONS, ...options };
const startTime = Date.now();

for (let attempt = 1; attempt <= opts.maxAttempts!; attempt++) {
const editor = vscode.window.activeTextEditor;
if (editor) {
return editor;
}

if (Date.now() - startTime > opts.timeout!) {
throw new EditorTimeoutError('Timed out waiting for active editor');
}

// Log retry attempts in debug mode
console.log(`Waiting for active editor... Attempt ${attempt}/${opts.maxAttempts}`);

await new Promise(resolve => setTimeout(resolve, opts.delayMs));
}

throw new Error('No active editor found after retries');
}

/**
* Ensures response panel remains visible and active
*/
export async function ensureResponsePanelActive(panel: vscode.TextEditor, options: EditorRetryOptions = {}): Promise<void> {
const opts = { ...DEFAULT_OPTIONS, ...options };
const startTime = Date.now();

while (Date.now() - startTime < opts.timeout!) {
if (!panel.document.isClosed && vscode.window.visibleTextEditors.includes(panel)) {
return;
}

try {
await vscode.window.showTextDocument(panel.document, panel.viewColumn);
return;
} catch (error) {
console.log('Retrying to ensure response panel visibility...', error);
await new Promise(resolve => setTimeout(resolve, opts.delayMs));
}
}

throw new EditorTimeoutError('Failed to keep response panel active');
}
65 changes: 37 additions & 28 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as vscode from 'vscode';
import { ClaudeApiService, DefaultClaudeApiService } from './services/claude-api';
import { ClaudeResponse } from './api';
import { Timeouts } from './config';
import { waitForExtensionReady, ensureAllEditorsClosed, unregisterCommands } from './utils';
import { waitForActiveEditor, ensureResponsePanelActive, EditorTimeoutError } from './editor-utils';

// Global state management
let registeredCommands: vscode.Disposable[] = [];
Expand Down Expand Up @@ -71,23 +71,6 @@ export async function createResponsePanel(content: string): Promise<vscode.TextE
* Handles Claude API requests
*/
async function handleClaudeRequest(mode: 'general' | 'document') {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No active editor!');
return;
}

// Create new CancellationTokenSource
const tokenSource = new vscode.CancellationTokenSource();

const selection = editor.selection;
const text = editor.document.getText(selection);
if (!text) {
vscode.window.showInformationMessage('Please select some text first');
tokenSource.dispose();
return;
}

const statusBarItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right,
Timeouts.STATUS_BAR_PRIORITY
Expand All @@ -96,36 +79,62 @@ async function handleClaudeRequest(mode: 'general' | 'document') {
statusBarItem.show();

try {
const prompt = mode === 'document'
? `Please document this code:\n\n${text}`
: text;
// Wait for active editor with retries
const editor = await waitForActiveEditor({
maxAttempts: 5,
delayMs: 200
});

const selection = editor.selection;
const text = editor.document.getText(selection);
if (!text) {
vscode.window.showInformationMessage('Please select some text first');
return;
}

const tokenSource = new vscode.CancellationTokenSource();

const response = await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: mode === 'document' ? 'Generating Documentation...' : 'Asking Claude...',
cancellable: true
}, async (progress, progressToken) => {
// Link the progress cancellation to our token source
progressToken.onCancellationRequested(() => {
tokenSource.cancel();
});


const prompt = mode === 'document'
? `Please document this code:\n\n${text}`
: text;

return await apiService.askClaude(prompt, tokenSource.token);
});

const formattedResponse = formatResponse(text, response, mode);
await createResponsePanel(formattedResponse);
const responseEditor = await createResponsePanel(formattedResponse);

if (responseEditor) {
// Ensure the response panel stays active
await ensureResponsePanelActive(responseEditor, {
maxAttempts: 3,
delayMs: 100
});
}

} catch (error) {
if (error instanceof vscode.CancellationError) {
vscode.window.showInformationMessage('Request cancelled');
return;
}
if (error instanceof EditorTimeoutError) {
vscode.window.showErrorMessage('Editor window management issue. Please try again.');
return;
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
vscode.window.showErrorMessage(`Error: ${errorMessage}`);
console.error('Error handling Claude request:', error);
} finally {
statusBarItem.dispose();
tokenSource.dispose();
}
}

Expand Down Expand Up @@ -168,7 +177,7 @@ export async function activate(context: vscode.ExtensionContext, service?: Claud
// Ensure previous commands are disposed and add safety timeout
await deactivate();
await new Promise(resolve => setTimeout(resolve, Timeouts.ACTIVATION)); // Wait for cleanup

// Initialize API service
apiService = service || new DefaultClaudeApiService();

Expand All @@ -180,11 +189,11 @@ export async function activate(context: vscode.ExtensionContext, service?: Claud
}),

// Main commands
vscode.commands.registerCommand('claude-vscode.askClaude', () =>
vscode.commands.registerCommand('claude-vscode.askClaude', () =>
handleClaudeRequest('general')
),

vscode.commands.registerCommand('claude-vscode.documentCode', () =>
vscode.commands.registerCommand('claude-vscode.documentCode', () =>
handleClaudeRequest('document')
)
];
Expand Down
66 changes: 66 additions & 0 deletions src/test-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// src/test-utils.ts
import * as vscode from 'vscode';
import { waitForExtensionReady } from './utils';

export interface CleanupOptions {
timeout?: number;
retryDelay?: number;
maxRetries?: number;
}

const DEFAULT_CLEANUP_OPTIONS: CleanupOptions = {
timeout: 1000,
retryDelay: 100,
maxRetries: 3
};

/**
* Thorough cleanup of VS Code resources
*/
export async function thoroughCleanup(options: CleanupOptions = {}): Promise<void> {
const opts = { ...DEFAULT_CLEANUP_OPTIONS, ...options };
const startTime = Date.now();

// First attempt normal cleanup
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
await waitForExtensionReady(opts.retryDelay);

for (let attempt = 0; attempt < opts.maxRetries!; attempt++) {
if (Date.now() - startTime > opts.timeout!) {
console.warn('Cleanup timeout reached');
break;
}

// Force close any remaining editors
if (vscode.window.visibleTextEditors.length > 0) {
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
await waitForExtensionReady(opts.retryDelay);
}

// Explicit disposal of tab resources
vscode.window.tabGroups.all.forEach(group => {
group.tabs.forEach(tab => {
try {
if (tab.input && typeof tab.input === 'object' && 'dispose' in tab.input) {
(tab.input as { dispose: () => void }).dispose();
}
} catch (error) {
console.warn('Tab disposal error:', error);
}
});
});

// Force garbage collection if available
if (global.gc) {
global.gc();
}

// Break if everything is cleaned up
if (vscode.window.visibleTextEditors.length === 0 &&
vscode.window.tabGroups.all.every(group => group.tabs.length === 0)) {
break;
}

await waitForExtensionReady(opts.retryDelay);
}
}
Loading

0 comments on commit f2eb4e7

Please sign in to comment.