diff --git a/src/core/CodeActionProvider.ts b/src/core/CodeActionProvider.ts index 1f977a4..24dc72c 100644 --- a/src/core/CodeActionProvider.ts +++ b/src/core/CodeActionProvider.ts @@ -1,6 +1,6 @@ import * as vscode from "vscode" -import * as path from "path" import { ClineProvider } from "./webview/ClineProvider" +import { EditorUtils } from "./EditorUtils" export const ACTION_NAMES = { EXPLAIN: "Roo Code: Explain Code", @@ -14,100 +14,12 @@ const COMMAND_IDS = { IMPROVE: "roo-cline.improveCode", } as const -interface DiagnosticData { - message: string - severity: vscode.DiagnosticSeverity - code?: string | number | { value: string | number; target: vscode.Uri } - source?: string - range: vscode.Range -} - -interface EffectiveRange { - range: vscode.Range - text: string -} - export class CodeActionProvider implements vscode.CodeActionProvider { public static readonly providedCodeActionKinds = [ vscode.CodeActionKind.QuickFix, vscode.CodeActionKind.RefactorRewrite, ] - // Cache file paths for performance - private readonly filePathCache = new WeakMap() - - private getEffectiveRange( - document: vscode.TextDocument, - range: vscode.Range | vscode.Selection, - ): EffectiveRange | null { - try { - const selectedText = document.getText(range) - if (selectedText) { - return { range, text: selectedText } - } - - const currentLine = document.lineAt(range.start.line) - if (!currentLine.text.trim()) { - return null - } - - // Optimize range creation by checking bounds first - const startLine = Math.max(0, currentLine.lineNumber - 1) - const endLine = Math.min(document.lineCount - 1, currentLine.lineNumber + 1) - - // Only create new positions if needed - const effectiveRange = new vscode.Range( - startLine === currentLine.lineNumber ? range.start : new vscode.Position(startLine, 0), - endLine === currentLine.lineNumber - ? range.end - : new vscode.Position(endLine, document.lineAt(endLine).text.length), - ) - - return { - range: effectiveRange, - text: document.getText(effectiveRange), - } - } catch (error) { - console.error("Error getting effective range:", error) - return null - } - } - - private getFilePath(document: vscode.TextDocument): string { - // Check cache first - let filePath = this.filePathCache.get(document) - if (filePath) { - return filePath - } - - try { - const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri) - if (!workspaceFolder) { - filePath = document.uri.fsPath - } else { - const relativePath = path.relative(workspaceFolder.uri.fsPath, document.uri.fsPath) - filePath = !relativePath || relativePath.startsWith("..") ? document.uri.fsPath : relativePath - } - - // Cache the result - this.filePathCache.set(document, filePath) - return filePath - } catch (error) { - console.error("Error getting file path:", error) - return document.uri.fsPath - } - } - - private createDiagnosticData(diagnostic: vscode.Diagnostic): DiagnosticData { - return { - message: diagnostic.message, - severity: diagnostic.severity, - code: diagnostic.code, - source: diagnostic.source, - range: diagnostic.range, // Reuse the range object - } - } - private createAction(title: string, kind: vscode.CodeActionKind, command: string, args: any[]): vscode.CodeAction { const action = new vscode.CodeAction(title, kind) action.command = { command, title, arguments: args } @@ -126,32 +38,20 @@ export class CodeActionProvider implements vscode.CodeActionProvider { ] } - private hasIntersectingRange(range1: vscode.Range, range2: vscode.Range): boolean { - // Optimize range intersection check - return !( - range2.end.line < range1.start.line || - range2.start.line > range1.end.line || - (range2.end.line === range1.start.line && range2.end.character < range1.start.character) || - (range2.start.line === range1.end.line && range2.start.character > range1.end.character) - ) - } - public provideCodeActions( document: vscode.TextDocument, range: vscode.Range | vscode.Selection, context: vscode.CodeActionContext, ): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> { try { - const effectiveRange = this.getEffectiveRange(document, range) + const effectiveRange = EditorUtils.getEffectiveRange(document, range) if (!effectiveRange) { return [] } - const filePath = this.getFilePath(document) + const filePath = EditorUtils.getFilePath(document) const actions: vscode.CodeAction[] = [] - // Create actions using helper method - // Add explain actions actions.push( ...this.createActionPair(ACTION_NAMES.EXPLAIN, vscode.CodeActionKind.QuickFix, COMMAND_IDS.EXPLAIN, [ filePath, @@ -159,14 +59,13 @@ export class CodeActionProvider implements vscode.CodeActionProvider { ]), ) - // Only process diagnostics if they exist if (context.diagnostics.length > 0) { const relevantDiagnostics = context.diagnostics.filter((d) => - this.hasIntersectingRange(effectiveRange.range, d.range), + EditorUtils.hasIntersectingRange(effectiveRange.range, d.range), ) if (relevantDiagnostics.length > 0) { - const diagnosticMessages = relevantDiagnostics.map(this.createDiagnosticData) + const diagnosticMessages = relevantDiagnostics.map(EditorUtils.createDiagnosticData) actions.push( ...this.createActionPair(ACTION_NAMES.FIX, vscode.CodeActionKind.QuickFix, COMMAND_IDS.FIX, [ filePath, @@ -177,7 +76,6 @@ export class CodeActionProvider implements vscode.CodeActionProvider { } } - // Add improve actions actions.push( ...this.createActionPair( ACTION_NAMES.IMPROVE, diff --git a/src/core/EditorUtils.ts b/src/core/EditorUtils.ts new file mode 100644 index 0000000..e58a82f --- /dev/null +++ b/src/core/EditorUtils.ts @@ -0,0 +1,141 @@ +import * as vscode from "vscode" +import * as path from "path" + +export interface EffectiveRange { + range: vscode.Range + text: string +} + +export interface DiagnosticData { + message: string + severity: vscode.DiagnosticSeverity + code?: string | number | { value: string | number; target: vscode.Uri } + source?: string + range: vscode.Range +} + +export interface EditorContext { + filePath: string + selectedText: string + diagnostics?: DiagnosticData[] +} + +export class EditorUtils { + // Cache file paths for performance + private static readonly filePathCache = new WeakMap() + + static getEffectiveRange( + document: vscode.TextDocument, + range: vscode.Range | vscode.Selection, + ): EffectiveRange | null { + try { + const selectedText = document.getText(range) + if (selectedText) { + return { range, text: selectedText } + } + + const currentLine = document.lineAt(range.start.line) + if (!currentLine.text.trim()) { + return null + } + + // Optimize range creation by checking bounds first + const startLine = Math.max(0, currentLine.lineNumber - 1) + const endLine = Math.min(document.lineCount - 1, currentLine.lineNumber + 1) + + // Only create new positions if needed + const effectiveRange = new vscode.Range( + startLine === currentLine.lineNumber ? range.start : new vscode.Position(startLine, 0), + endLine === currentLine.lineNumber + ? range.end + : new vscode.Position(endLine, document.lineAt(endLine).text.length), + ) + + return { + range: effectiveRange, + text: document.getText(effectiveRange), + } + } catch (error) { + console.error("Error getting effective range:", error) + return null + } + } + + static getFilePath(document: vscode.TextDocument): string { + // Check cache first + let filePath = this.filePathCache.get(document) + if (filePath) { + return filePath + } + + try { + const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri) + if (!workspaceFolder) { + filePath = document.uri.fsPath + } else { + const relativePath = path.relative(workspaceFolder.uri.fsPath, document.uri.fsPath) + filePath = !relativePath || relativePath.startsWith("..") ? document.uri.fsPath : relativePath + } + + // Cache the result + this.filePathCache.set(document, filePath) + return filePath + } catch (error) { + console.error("Error getting file path:", error) + return document.uri.fsPath + } + } + + static createDiagnosticData(diagnostic: vscode.Diagnostic): DiagnosticData { + return { + message: diagnostic.message, + severity: diagnostic.severity, + code: diagnostic.code, + source: diagnostic.source, + range: diagnostic.range, + } + } + + static hasIntersectingRange(range1: vscode.Range, range2: vscode.Range): boolean { + return !( + range2.end.line < range1.start.line || + range2.start.line > range1.end.line || + (range2.end.line === range1.start.line && range2.end.character < range1.start.character) || + (range2.start.line === range1.end.line && range2.start.character > range1.end.character) + ) + } + + static getEditorContext(editor?: vscode.TextEditor): EditorContext | null { + try { + if (!editor) { + editor = vscode.window.activeTextEditor + } + if (!editor) { + return null + } + + const document = editor.document + const selection = editor.selection + const effectiveRange = this.getEffectiveRange(document, selection) + + if (!effectiveRange) { + return null + } + + const filePath = this.getFilePath(document) + const diagnostics = vscode.languages + .getDiagnostics(document.uri) + .filter((d) => this.hasIntersectingRange(effectiveRange.range, d.range)) + .map(this.createDiagnosticData) + + return { + filePath, + selectedText: effectiveRange.text, + ...(diagnostics.length > 0 ? { diagnostics } : {}), + } + } catch (error) { + console.error("Error getting editor context:", error) + return null + } + } +} diff --git a/src/extension.ts b/src/extension.ts index 332759a..37cc8c0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -6,6 +6,7 @@ import { ClineProvider } from "./core/webview/ClineProvider" import { createClineAPI } from "./exports" import "./utils/path" // necessary to have access to String.prototype.toPosix import { ACTION_NAMES, CodeActionProvider } from "./core/CodeActionProvider" +import { EditorUtils } from "./core/EditorUtils" import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider" /* @@ -178,26 +179,37 @@ export function activate(context: vscode.ExtensionContext) { let userInput: string | undefined context.subscriptions.push( - vscode.commands.registerCommand( - command, - async (filePath: string, selectedText: string, diagnostics?: any[]) => { - if (inputPrompt) { - userInput = await vscode.window.showInputBox({ - prompt: inputPrompt, - placeHolder: inputPlaceholder, - }) - } + vscode.commands.registerCommand(command, async (...args: any[]) => { + if (inputPrompt) { + userInput = await vscode.window.showInputBox({ + prompt: inputPrompt, + placeHolder: inputPlaceholder, + }) + } - const params = { - filePath, - selectedText, - ...(diagnostics ? { diagnostics } : {}), - ...(userInput ? { userInput } : {}), - } + // Handle both code action and direct command cases + let filePath: string + let selectedText: string + let diagnostics: any[] | undefined - await ClineProvider.handleCodeAction(command, promptType, params) - }, - ), + if (args.length > 1) { + // Called from code action + ;[filePath, selectedText, diagnostics] = args + } else { + // Called directly from command palette + const context = EditorUtils.getEditorContext() + if (!context) return + ;({ filePath, selectedText, diagnostics } = context) + } + + const params = { + ...{ filePath, selectedText }, + ...(diagnostics ? { diagnostics } : {}), + ...(userInput ? { userInput } : {}), + } + + await ClineProvider.handleCodeAction(command, promptType, params) + }), ) }