depends on https://github.com/codeflash-ai/codeflash/pull/688, https://github.com/codeflash-ai/codeflash-internal/pull/1767 --------- Signed-off-by: ali <mohammed18200118@gmail.com> Co-authored-by: Sarthak Agarwal <sarthak.saga@gmail.com>
21 KiB
Complete VSCode Extension Development Guide: Codeflash
Table of Contents
- Extension Architecture Overview
- Project Structure & Files
- Core Concepts & Technologies
- Detailed Component Analysis
- VSCode Extension APIs Used
- Decision Making Process
- Build System & Tooling
- Extension Lifecycle
Extension Architecture Overview
High-Level Architecture
┌─────────────────────────────────────────────────────────────┐
│ VSCode Extension Host │
├─────────────────────────────────────────────────────────────┤
│ Extension.ts (Main Entry Point) │
│ ├── Registers Providers & Commands │
│ ├── Manages Service Lifecycle │
│ └── Handles Extension Activation/Deactivation │
├─────────────────────────────────────────────────────────────┤
│ Providers (UI Components) │
│ ├── SidebarProvider (Webview) │
│ └── CodeLensProvider (Inline Buttons) │
├─────────────────────────────────────────────────────────────┤
│ Services (Business Logic) │
│ ├── LSP Service (Language Server Communication) │
│ ├── Analysis Service (Code Analysis) │
│ ├── Optimization Service (Code Optimization) │
│ ├── Diff Service (Diff View Management) │
│ └── Python Service (Environment Detection) │
├─────────────────────────────────────────────────────────────┤
│ Utils & Constants │
│ ├── Logger (Centralized Logging) │
│ ├── Error Handling │
│ └── Path Utilities │
└─────────────────────────────────────────────────────────────┘
Why This Architecture?
- Separation of Concerns: UI (Providers) separated from business logic (Services)
- Testability: Each service can be tested independently
- Maintainability: Clear boundaries between components
- Scalability: Easy to add new providers or services
Project Structure & Files
Core Files Analysis
package.json - Extension Manifest
{
"name": "codeflash",
"displayName": "Codeflash",
"description": "Optimize Your Code - Automatically",
"main": "./dist/extension.js",
"engines": { "vscode": "^1.98.0" }
}
Key Decisions:
main: Points to compiled JavaScript, not TypeScript sourceengines.vscode: Minimum VSCode version requiredactivationEvents: []: Empty array means activate on any event (modern approach)
contributes Section - VSCode Integration Points
"contributes": {
"viewsContainers": { /* Custom sidebar container */ },
"views": { /* Sidebar webview */ },
"commands": { /* Extension commands */ },
"menus": { /* Command palette & context menus */ },
"keybindings": { /* Keyboard shortcuts */ }
}
Why Each Contribution:
- viewsContainers: Creates our own sidebar section (not mixed with Explorer/Git)
- commands: Defines callable actions from command palette
- menus: Controls where commands appear (command palette, context menus)
- keybindings: Provides keyboard shortcuts for power users
TypeScript Configuration
tsconfig.json - Compilation Settings
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"strict": true
}
}
Decisions:
- ES2020: Modern JavaScript features while maintaining compatibility
- commonjs: Required for Node.js/VSCode extension environment
- strict: true: Enables all TypeScript strict type checking
Core Concepts & Technologies
1. Language Server Protocol (LSP)
What is LSP? Language Server Protocol is a standardized way for editors to communicate with language analysis tools.
Implementation in Codeflash:
// src/services/lspService.ts
export class LspService {
async startClient(pythonPath: string): Promise<LanguageClient> {
const client = new LanguageClient(
LSP_CLIENT_ID,
LSP_CLIENT_NAME,
serverOptions,
clientOptions,
);
await client.start();
return client;
}
}
Why LSP?
- Standardized: Works with any LSP-compliant language server
- Performant: Separate process handles heavy analysis
- Extensible: Can add new LSP commands easily
2. Webview API
What are Webviews? Webviews let you display custom HTML/CSS/JavaScript UI within VSCode.
Implementation:
// src/providers/SidebarProvider.ts
export class SidebarProvider implements vscode.WebviewViewProvider {
resolveWebviewView(webviewView: vscode.WebviewView) {
webviewView.webview.html = generateWebviewHtml(/* ... */);
webviewView.webview.onDidReceiveMessage(this.handleWebviewMessage);
}
}
Security Considerations:
- Content Security Policy: Prevents XSS attacks
- localResourceRoots: Limits file access
- enableScripts: Explicitly enables JavaScript
3. CodeLens API
What is CodeLens? CodeLens provides inline actionable information in the editor.
Implementation:
// src/providers/CodeLensProvider.ts
export class CodeflashCodeLensProvider implements vscode.CodeLensProvider {
async provideCodeLenses(
document: vscode.TextDocument,
): Promise<vscode.CodeLens[]> {
// Analyze document and return CodeLens items
return codeLenses;
}
}
Why CodeLens?
- Contextual: Appears exactly where it's relevant
- Non-intrusive: Doesn't modify the actual code
- Discoverable: Users see optimization opportunities immediately
Detailed Component Analysis
Extension Entry Point (src/extension.ts)
Activation Function
export async function activate(
context: vscode.ExtensionContext,
): Promise<void> {
// 1. Initialize services
const pythonService = new PythonService();
const lspService = new LspService();
// 2. Start language client
const client = await lspService.startClient(pythonPath);
// 3. Register providers
const sidebarProvider = new SidebarProvider(context.extensionUri, client);
const codeLensProvider = new CodeflashCodeLensProvider(
client,
analysisService,
);
// 4. Register with VSCode
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(SIDEBAR_VIEW_ID, sidebarProvider),
vscode.languages.registerCodeLensProvider(
{ language: "python" },
codeLensProvider,
),
);
}
Key Concepts:
- ExtensionContext: Provides extension lifetime management
- subscriptions: Automatic cleanup when extension deactivates
- Dependency Injection: Services receive their dependencies
Command Registration
const optimizeFunctionCommand = vscode.commands.registerCommand(
"codeflash.optimizeFunction",
async (uri: vscode.Uri, functionName: string) => {
await handleOptimizeFunction(uri, functionName);
},
);
Command Design Patterns:
- Async handlers: All operations are asynchronous
- Typed parameters: Commands receive strongly-typed parameters
- Error boundaries: Each command has error handling
Sidebar Provider (src/providers/SidebarProvider.ts)
Webview Communication Pattern
// Extension → Webview
this.sendMessage({
type: "updateState",
payload: { status, message, functionData },
});
// Webview → Extension
webviewView.webview.onDidReceiveMessage(async (data) => {
switch (data.type) {
case "requestAnalysis":
await this.handleRequestAnalysis();
break;
}
});
Message Types:
- updateState: Updates UI state (loading, success, error)
- optimizationStarted: Shows progress overlay
- optimizationComplete: Shows results
State Management:
- Centralized: All state changes go through
updateWebviewState - Reactive: UI updates automatically when state changes
- Persistent: State survives webview reloads
CodeLens Provider (src/providers/CodeLensProvider.ts)
Function Detection Algorithm
private findFunctionPosition(lines: string[], functionName: string): Position | null {
const isMethod = functionName.includes('.');
if (isMethod) {
const [className, methodName] = functionName.split('.');
// 1. Find class definition
// 2. Find method within class
} else {
// Find standalone function
}
}
Why This Approach?
- Handles both functions and methods: Single algorithm for all cases
- Line-by-line parsing: Simple and reliable
- Position accuracy: Returns exact character position for button placement
CodeLens Refresh Strategy
// Refresh triggers
this._lspClient.onDidChangeState(() => this._onDidChangeCodeLenses.fire());
vscode.workspace.onDidSaveTextDocument(() =>
this._onDidChangeCodeLenses.fire(),
);
Refresh Strategy:
- LSP state changes: When language server starts/stops
- File saves: When user saves Python files
- Manual refresh: Via command palette
Services Architecture
Service Communication Pattern
// Service dependencies are injected
export class AnalysisService {
constructor(private client: LanguageClient) {}
async getOptimizableFunctions(uri: vscode.Uri) {
return await this.client.sendRequest(
LSP_COMMANDS.GET_OPTIMIZABLE_FUNCTIONS,
{
textDocument: { uri: uri.toString() },
},
);
}
}
Design Principles:
- Single Responsibility: Each service has one clear purpose
- Dependency Injection: Services receive dependencies in constructor
- Interface Segregation: Services expose minimal public APIs
Error Handling Strategy
try {
const result = await this.optimizationService.optimizeFunction(
functionName,
uri,
);
// Handle success
} catch (error) {
this.logger.error(`Error optimizing function ${functionName}`, error);
vscode.window.showErrorMessage(`Failed to optimize ${functionName}`);
}
Error Handling Layers:
- Service Level: Catch and log errors
- UI Level: Show user-friendly messages
- Global Level: Extension-wide error boundaries
VSCode Extension APIs Used
Core APIs
1. Commands API
// Register command
vscode.commands.registerCommand("codeflash.optimizeFunction", handler);
// Execute command
vscode.commands.executeCommand("vscode.diff", originalUri, optimizedUri, title);
Use Cases:
- User-triggered actions: Optimize function, refresh analysis
- Internal operations: Open diff view, navigate to function
2. Language Features API
// CodeLens provider
vscode.languages.registerCodeLensProvider(selector, provider);
// Document selectors
{ language: 'python', scheme: 'file' }
Selector Strategy:
- language: Only Python files
- scheme: Only file system files (not git:// or other schemes)
3. Workspace API
// File operations
vscode.workspace.openTextDocument(uri);
vscode.workspace.fs.writeFile(uri, content);
// Event listening
vscode.workspace.onDidSaveTextDocument(handler);
4. Window API
// User interaction
vscode.window.showInformationMessage(message, ...actions);
vscode.window.withProgress(options, handler);
// Editor operations
vscode.window.showTextDocument(document, options);
Advanced APIs
1. Webview API
// Security configuration
webview.options = {
enableScripts: true,
localResourceRoots: [extensionUri, nodeModulesUri]
};
// Content Security Policy
<meta http-equiv="Content-Security-Policy" content="
default-src 'none';
script-src ${webview.cspSource} 'nonce-${nonce}';
style-src ${webview.cspSource} 'unsafe-inline';
">
2. Language Client API (from vscode-languageclient)
const client = new LanguageClient(id, name, serverOptions, clientOptions);
await client.start();
const result = await client.sendRequest(method, params);
Decision Making Process
Technology Choices
1. TypeScript vs JavaScript
Chosen: TypeScript
Reasons:
- Type Safety: Catches errors at compile time
- IDE Support: Better IntelliSense and refactoring
- Maintainability: Self-documenting code with types
- VSCode Standard: Most VSCode extensions use TypeScript
2. ESBuild vs Webpack
Chosen: ESBuild
Reasons:
- Speed: 10-100x faster than Webpack
- Simplicity: Minimal configuration required
- Size: Smaller bundle sizes
- Modern: Built for modern JavaScript/TypeScript
3. Webview vs TreeView for Sidebar
Chosen: Webview
Reasons:
- Flexibility: Complete control over UI
- Rich Interactions: Complex UI elements (progress bars, buttons)
- Styling: Custom CSS for branding
- Future-proof: Can add any web technology
4. CodeLens vs Decorations for Inline UI
Chosen: CodeLens
Reasons:
- Clickable: CodeLens supports commands, decorations don't
- Standard: Users familiar with CodeLens from other extensions
- Semantic: CodeLens is meant for actionable metadata
- Positioning: Automatically positioned above code
Architectural Decisions
1. Service-Based Architecture
Why:
- Testability: Services can be unit tested independently
- Reusability: Services can be used by multiple providers
- Separation: UI logic separated from business logic
2. LSP Communication
Why:
- Performance: Heavy analysis runs in separate process
- Language Agnostic: Same extension could support multiple languages
- Standardized: Uses industry-standard protocol
3. Message-Based Webview Communication
Why:
- Security: No direct object sharing between contexts
- Reliability: Structured communication reduces bugs
- Debuggability: All messages can be logged
Build System & Tooling
ESBuild Configuration (esbuild.js)
const esbuild = require("esbuild");
const production = process.argv.includes("--production");
const watch = process.argv.includes("--watch");
const ctx = await esbuild.context({
entryPoints: ["src/extension.ts"],
bundle: true,
format: "cjs",
minify: production,
sourcemap: !production,
platform: "node",
outdir: "dist",
external: ["vscode"],
});
Configuration Explained:
- bundle: true: Combines all files into single output
- format: 'cjs': CommonJS format for Node.js
- external: ['vscode']: VSCode API provided by host
- platform: 'node': Target Node.js environment
ESLint Configuration (eslint.config.mjs)
export default [
{
files: ["src/**/*.ts"],
languageOptions: {
parser: tsParser,
parserOptions: { project: "./tsconfig.json" },
},
rules: {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-explicit-any": "warn",
},
},
];
Development Scripts
{
"dev": "npm run compile && npm run watch",
"build": "npm run check-types && npm run lint && node esbuild.js",
"package": "npm run build --production"
}
Script Strategy:
- dev: Development with watch mode
- build: Full build with type checking and linting
- package: Production build with minification
Extension Lifecycle
1. Activation Sequence
1. VSCode loads extension.js
2. activate() function called
3. Services initialized
4. LSP client started
5. Providers registered
6. Commands registered
7. Extension ready
2. Runtime Operations
User opens Python file
├── CodeLens provider activated
├── Analysis service queries LSP
├── CodeLens buttons appear
│
User clicks "Optimize with CF"
├── Command handler invoked
├── Optimization service called
├── Progress indicator shown
├── Diff service opens results
└── User accepts/rejects changes
3. Deactivation Sequence
1. deactivate() function called
2. LSP client stopped
3. Services disposed
4. Event listeners removed
5. Resources cleaned up
Advanced Concepts
Memory Management
export async function deactivate(): Promise<void> {
// Dispose services in reverse order
codeLensProvider?.dispose();
diffService?.dispose();
await lspService?.stopClient();
// Clear references to prevent memory leaks
codeLensProvider = undefined;
diffService = undefined;
}
Error Boundaries
// Global error handling
process.on("uncaughtException", (error) => {
logger?.error("Uncaught exception", error);
});
// Service-level error handling
async function handleOptimizeFunction(uri: vscode.Uri, functionName: string) {
try {
// Operation
} catch (error) {
logger?.error(`Error optimizing function ${functionName}`, error);
vscode.window.showErrorMessage(`Failed to optimize ${functionName}`);
}
}
Performance Optimization
// Debounced CodeLens refresh
private refreshDebounced = debounce(() => {
this._onDidChangeCodeLenses.fire();
}, 500);
// Lazy loading
async provideCodeLenses(document: vscode.TextDocument) {
if (this.client.state !== LanguageClientState.Running) {
return []; // Early return if not ready
}
// Expensive operations only when needed
}
Testing Strategy
Unit Testing Example
describe("AnalysisService", () => {
let service: AnalysisService;
let mockClient: jest.Mocked<LanguageClient>;
beforeEach(() => {
mockClient = createMockLanguageClient();
service = new AnalysisService(mockClient);
});
it("should return optimizable functions", async () => {
mockClient.sendRequest.mockResolvedValue({
"file:///test.py": ["function1", "Class.method1"],
});
const result = await service.getOptimizableFunctions(testUri);
expect(result.status).toBe("success");
expect(result.functions).toHaveLength(2);
});
});
Integration Testing
describe("Extension Integration", () => {
it("should activate successfully", async () => {
const context = createMockExtensionContext();
await activate(context);
expect(context.subscriptions).toHaveLength(5);
expect(vscode.commands.registerCommand).toHaveBeenCalled();
});
});
Best Practices Applied
1. Security
- CSP: Content Security Policy for webviews
- Input validation: All user inputs validated
- Resource isolation: Webview resources restricted
2. Performance
- Lazy loading: Components loaded only when needed
- Debouncing: Frequent operations debounced
- Caching: Results cached when appropriate
3. User Experience
- Progressive disclosure: Simple UI with advanced features available
- Feedback: Loading states and progress indicators
- Accessibility: Proper ARIA labels and keyboard navigation
4. Maintainability
- Type safety: Comprehensive TypeScript types
- Documentation: Code comments and this guide
- Testing: Unit and integration tests
Common Patterns in VSCode Extensions
1. Provider Pattern
interface MyProvider {
provideX(document: TextDocument): X[];
}
vscode.languages.registerXProvider(selector, provider);
2. Command Pattern
const command = vscode.commands.registerCommand("extension.action", (args) => {
// Handle command
});
3. Event-Driven Architecture
vscode.workspace.onDidChangeTextDocument((event) => {
// React to changes
});
4. Disposable Pattern
class MyService implements vscode.Disposable {
private disposables: vscode.Disposable[] = [];
dispose() {
vscode.Disposable.from(...this.disposables).dispose();
}
}
This guide covers the fundamental concepts and decisions behind building a professional VSCode extension. Each component serves a specific purpose and follows established patterns for maintainability and user experience.