Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@
"command": "roo-cline.improveCode",
"title": "Roo Code: Improve Code",
"category": "Roo Code"
},
{
"command": "roo-cline.addToContext",
"title": "Roo Code: Add To Context",
"category": "Roo Code"
}
],
"menus": {
Expand All @@ -136,6 +141,11 @@
"command": "roo-cline.improveCode",
"when": "editorHasSelection",
"group": "Roo Code@3"
},
{
"command": "roo-cline.addToContext",
"when": "editorHasSelection",
"group": "Roo Code@4"
}
],
"view/title": [
Expand Down
132 changes: 24 additions & 108 deletions src/core/CodeActionProvider.ts
Original file line number Diff line number Diff line change
@@ -1,113 +1,27 @@
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",
FIX: "Roo Code: Fix Code",
FIX_LOGIC: "Roo Code: Fix Logic",
IMPROVE: "Roo Code: Improve Code",
ADD_TO_CONTEXT: "Roo Code: Add to Context",
} as const

const COMMAND_IDS = {
EXPLAIN: "roo-cline.explainCode",
FIX: "roo-cline.fixCode",
IMPROVE: "roo-cline.improveCode",
ADD_TO_CONTEXT: "roo-cline.addToContext",
} 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 }
Expand All @@ -126,47 +40,34 @@ 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,
effectiveRange.text,
]),
)

// 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,
Expand All @@ -175,9 +76,15 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
]),
)
}
} else {
actions.push(
...this.createActionPair(ACTION_NAMES.FIX_LOGIC, vscode.CodeActionKind.QuickFix, COMMAND_IDS.FIX, [
filePath,
effectiveRange.text,
]),
)
}

// Add improve actions
actions.push(
...this.createActionPair(
ACTION_NAMES.IMPROVE,
Expand All @@ -187,6 +94,15 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
),
)

actions.push(
this.createAction(
ACTION_NAMES.ADD_TO_CONTEXT,
vscode.CodeActionKind.QuickFix,
COMMAND_IDS.ADD_TO_CONTEXT,
[filePath, effectiveRange.text],
),
)

return actions
} catch (error) {
console.error("Error providing code actions:", error)
Expand Down
141 changes: 141 additions & 0 deletions src/core/EditorUtils.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Loading