2
0
Эх сурвалжийг харах

refactor: centralize editor utilities and unify command handling

- Create EditorUtils class to centralize shared editor functionality
- Remove duplicated code between CodeActionProvider and command handlers
- Improve command handling to work consistently for both code actions and direct commands
- Add better type safety and error handling for editor operations
sam hoang 11 сар өмнө
parent
commit
35a7e433f2

+ 5 - 107
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<vscode.TextDocument, string>()
-
-	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,

+ 141 - 0
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<vscode.TextDocument, string>()
+
+	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
+		}
+	}
+}

+ 32 - 20
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,
-						})
-					}
-
-					const params = {
-						filePath,
-						selectedText,
-						...(diagnostics ? { diagnostics } : {}),
-						...(userInput ? { userInput } : {}),
-					}
-
-					await ClineProvider.handleCodeAction(command, promptType, params)
-				},
-			),
+			vscode.commands.registerCommand(command, async (...args: any[]) => {
+				if (inputPrompt) {
+					userInput = await vscode.window.showInputBox({
+						prompt: inputPrompt,
+						placeHolder: inputPlaceholder,
+					})
+				}
+
+				// Handle both code action and direct command cases
+				let filePath: string
+				let selectedText: string
+				let diagnostics: any[] | undefined
+
+				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)
+			}),
 		)
 	}