Skip to content

Commit 35a7e43

Browse files
committed
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
1 parent bc5b00e commit 35a7e43

File tree

3 files changed

+178
-127
lines changed

3 files changed

+178
-127
lines changed

src/core/CodeActionProvider.ts

Lines changed: 5 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as vscode from "vscode"
2-
import * as path from "path"
32
import { ClineProvider } from "./webview/ClineProvider"
3+
import { EditorUtils } from "./EditorUtils"
44

55
export const ACTION_NAMES = {
66
EXPLAIN: "Roo Code: Explain Code",
@@ -14,100 +14,12 @@ const COMMAND_IDS = {
1414
IMPROVE: "roo-cline.improveCode",
1515
} as const
1616

17-
interface DiagnosticData {
18-
message: string
19-
severity: vscode.DiagnosticSeverity
20-
code?: string | number | { value: string | number; target: vscode.Uri }
21-
source?: string
22-
range: vscode.Range
23-
}
24-
25-
interface EffectiveRange {
26-
range: vscode.Range
27-
text: string
28-
}
29-
3017
export class CodeActionProvider implements vscode.CodeActionProvider {
3118
public static readonly providedCodeActionKinds = [
3219
vscode.CodeActionKind.QuickFix,
3320
vscode.CodeActionKind.RefactorRewrite,
3421
]
3522

36-
// Cache file paths for performance
37-
private readonly filePathCache = new WeakMap<vscode.TextDocument, string>()
38-
39-
private getEffectiveRange(
40-
document: vscode.TextDocument,
41-
range: vscode.Range | vscode.Selection,
42-
): EffectiveRange | null {
43-
try {
44-
const selectedText = document.getText(range)
45-
if (selectedText) {
46-
return { range, text: selectedText }
47-
}
48-
49-
const currentLine = document.lineAt(range.start.line)
50-
if (!currentLine.text.trim()) {
51-
return null
52-
}
53-
54-
// Optimize range creation by checking bounds first
55-
const startLine = Math.max(0, currentLine.lineNumber - 1)
56-
const endLine = Math.min(document.lineCount - 1, currentLine.lineNumber + 1)
57-
58-
// Only create new positions if needed
59-
const effectiveRange = new vscode.Range(
60-
startLine === currentLine.lineNumber ? range.start : new vscode.Position(startLine, 0),
61-
endLine === currentLine.lineNumber
62-
? range.end
63-
: new vscode.Position(endLine, document.lineAt(endLine).text.length),
64-
)
65-
66-
return {
67-
range: effectiveRange,
68-
text: document.getText(effectiveRange),
69-
}
70-
} catch (error) {
71-
console.error("Error getting effective range:", error)
72-
return null
73-
}
74-
}
75-
76-
private getFilePath(document: vscode.TextDocument): string {
77-
// Check cache first
78-
let filePath = this.filePathCache.get(document)
79-
if (filePath) {
80-
return filePath
81-
}
82-
83-
try {
84-
const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri)
85-
if (!workspaceFolder) {
86-
filePath = document.uri.fsPath
87-
} else {
88-
const relativePath = path.relative(workspaceFolder.uri.fsPath, document.uri.fsPath)
89-
filePath = !relativePath || relativePath.startsWith("..") ? document.uri.fsPath : relativePath
90-
}
91-
92-
// Cache the result
93-
this.filePathCache.set(document, filePath)
94-
return filePath
95-
} catch (error) {
96-
console.error("Error getting file path:", error)
97-
return document.uri.fsPath
98-
}
99-
}
100-
101-
private createDiagnosticData(diagnostic: vscode.Diagnostic): DiagnosticData {
102-
return {
103-
message: diagnostic.message,
104-
severity: diagnostic.severity,
105-
code: diagnostic.code,
106-
source: diagnostic.source,
107-
range: diagnostic.range, // Reuse the range object
108-
}
109-
}
110-
11123
private createAction(title: string, kind: vscode.CodeActionKind, command: string, args: any[]): vscode.CodeAction {
11224
const action = new vscode.CodeAction(title, kind)
11325
action.command = { command, title, arguments: args }
@@ -126,47 +38,34 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
12638
]
12739
}
12840

129-
private hasIntersectingRange(range1: vscode.Range, range2: vscode.Range): boolean {
130-
// Optimize range intersection check
131-
return !(
132-
range2.end.line < range1.start.line ||
133-
range2.start.line > range1.end.line ||
134-
(range2.end.line === range1.start.line && range2.end.character < range1.start.character) ||
135-
(range2.start.line === range1.end.line && range2.start.character > range1.end.character)
136-
)
137-
}
138-
13941
public provideCodeActions(
14042
document: vscode.TextDocument,
14143
range: vscode.Range | vscode.Selection,
14244
context: vscode.CodeActionContext,
14345
): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> {
14446
try {
145-
const effectiveRange = this.getEffectiveRange(document, range)
47+
const effectiveRange = EditorUtils.getEffectiveRange(document, range)
14648
if (!effectiveRange) {
14749
return []
14850
}
14951

150-
const filePath = this.getFilePath(document)
52+
const filePath = EditorUtils.getFilePath(document)
15153
const actions: vscode.CodeAction[] = []
15254

153-
// Create actions using helper method
154-
// Add explain actions
15555
actions.push(
15656
...this.createActionPair(ACTION_NAMES.EXPLAIN, vscode.CodeActionKind.QuickFix, COMMAND_IDS.EXPLAIN, [
15757
filePath,
15858
effectiveRange.text,
15959
]),
16060
)
16161

162-
// Only process diagnostics if they exist
16362
if (context.diagnostics.length > 0) {
16463
const relevantDiagnostics = context.diagnostics.filter((d) =>
165-
this.hasIntersectingRange(effectiveRange.range, d.range),
64+
EditorUtils.hasIntersectingRange(effectiveRange.range, d.range),
16665
)
16766

16867
if (relevantDiagnostics.length > 0) {
169-
const diagnosticMessages = relevantDiagnostics.map(this.createDiagnosticData)
68+
const diagnosticMessages = relevantDiagnostics.map(EditorUtils.createDiagnosticData)
17069
actions.push(
17170
...this.createActionPair(ACTION_NAMES.FIX, vscode.CodeActionKind.QuickFix, COMMAND_IDS.FIX, [
17271
filePath,
@@ -177,7 +76,6 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
17776
}
17877
}
17978

180-
// Add improve actions
18179
actions.push(
18280
...this.createActionPair(
18381
ACTION_NAMES.IMPROVE,

src/core/EditorUtils.ts

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import * as vscode from "vscode"
2+
import * as path from "path"
3+
4+
export interface EffectiveRange {
5+
range: vscode.Range
6+
text: string
7+
}
8+
9+
export interface DiagnosticData {
10+
message: string
11+
severity: vscode.DiagnosticSeverity
12+
code?: string | number | { value: string | number; target: vscode.Uri }
13+
source?: string
14+
range: vscode.Range
15+
}
16+
17+
export interface EditorContext {
18+
filePath: string
19+
selectedText: string
20+
diagnostics?: DiagnosticData[]
21+
}
22+
23+
export class EditorUtils {
24+
// Cache file paths for performance
25+
private static readonly filePathCache = new WeakMap<vscode.TextDocument, string>()
26+
27+
static getEffectiveRange(
28+
document: vscode.TextDocument,
29+
range: vscode.Range | vscode.Selection,
30+
): EffectiveRange | null {
31+
try {
32+
const selectedText = document.getText(range)
33+
if (selectedText) {
34+
return { range, text: selectedText }
35+
}
36+
37+
const currentLine = document.lineAt(range.start.line)
38+
if (!currentLine.text.trim()) {
39+
return null
40+
}
41+
42+
// Optimize range creation by checking bounds first
43+
const startLine = Math.max(0, currentLine.lineNumber - 1)
44+
const endLine = Math.min(document.lineCount - 1, currentLine.lineNumber + 1)
45+
46+
// Only create new positions if needed
47+
const effectiveRange = new vscode.Range(
48+
startLine === currentLine.lineNumber ? range.start : new vscode.Position(startLine, 0),
49+
endLine === currentLine.lineNumber
50+
? range.end
51+
: new vscode.Position(endLine, document.lineAt(endLine).text.length),
52+
)
53+
54+
return {
55+
range: effectiveRange,
56+
text: document.getText(effectiveRange),
57+
}
58+
} catch (error) {
59+
console.error("Error getting effective range:", error)
60+
return null
61+
}
62+
}
63+
64+
static getFilePath(document: vscode.TextDocument): string {
65+
// Check cache first
66+
let filePath = this.filePathCache.get(document)
67+
if (filePath) {
68+
return filePath
69+
}
70+
71+
try {
72+
const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri)
73+
if (!workspaceFolder) {
74+
filePath = document.uri.fsPath
75+
} else {
76+
const relativePath = path.relative(workspaceFolder.uri.fsPath, document.uri.fsPath)
77+
filePath = !relativePath || relativePath.startsWith("..") ? document.uri.fsPath : relativePath
78+
}
79+
80+
// Cache the result
81+
this.filePathCache.set(document, filePath)
82+
return filePath
83+
} catch (error) {
84+
console.error("Error getting file path:", error)
85+
return document.uri.fsPath
86+
}
87+
}
88+
89+
static createDiagnosticData(diagnostic: vscode.Diagnostic): DiagnosticData {
90+
return {
91+
message: diagnostic.message,
92+
severity: diagnostic.severity,
93+
code: diagnostic.code,
94+
source: diagnostic.source,
95+
range: diagnostic.range,
96+
}
97+
}
98+
99+
static hasIntersectingRange(range1: vscode.Range, range2: vscode.Range): boolean {
100+
return !(
101+
range2.end.line < range1.start.line ||
102+
range2.start.line > range1.end.line ||
103+
(range2.end.line === range1.start.line && range2.end.character < range1.start.character) ||
104+
(range2.start.line === range1.end.line && range2.start.character > range1.end.character)
105+
)
106+
}
107+
108+
static getEditorContext(editor?: vscode.TextEditor): EditorContext | null {
109+
try {
110+
if (!editor) {
111+
editor = vscode.window.activeTextEditor
112+
}
113+
if (!editor) {
114+
return null
115+
}
116+
117+
const document = editor.document
118+
const selection = editor.selection
119+
const effectiveRange = this.getEffectiveRange(document, selection)
120+
121+
if (!effectiveRange) {
122+
return null
123+
}
124+
125+
const filePath = this.getFilePath(document)
126+
const diagnostics = vscode.languages
127+
.getDiagnostics(document.uri)
128+
.filter((d) => this.hasIntersectingRange(effectiveRange.range, d.range))
129+
.map(this.createDiagnosticData)
130+
131+
return {
132+
filePath,
133+
selectedText: effectiveRange.text,
134+
...(diagnostics.length > 0 ? { diagnostics } : {}),
135+
}
136+
} catch (error) {
137+
console.error("Error getting editor context:", error)
138+
return null
139+
}
140+
}
141+
}

src/extension.ts

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ClineProvider } from "./core/webview/ClineProvider"
66
import { createClineAPI } from "./exports"
77
import "./utils/path" // necessary to have access to String.prototype.toPosix
88
import { ACTION_NAMES, CodeActionProvider } from "./core/CodeActionProvider"
9+
import { EditorUtils } from "./core/EditorUtils"
910
import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
1011

1112
/*
@@ -178,26 +179,37 @@ export function activate(context: vscode.ExtensionContext) {
178179
let userInput: string | undefined
179180

180181
context.subscriptions.push(
181-
vscode.commands.registerCommand(
182-
command,
183-
async (filePath: string, selectedText: string, diagnostics?: any[]) => {
184-
if (inputPrompt) {
185-
userInput = await vscode.window.showInputBox({
186-
prompt: inputPrompt,
187-
placeHolder: inputPlaceholder,
188-
})
189-
}
190-
191-
const params = {
192-
filePath,
193-
selectedText,
194-
...(diagnostics ? { diagnostics } : {}),
195-
...(userInput ? { userInput } : {}),
196-
}
197-
198-
await ClineProvider.handleCodeAction(command, promptType, params)
199-
},
200-
),
182+
vscode.commands.registerCommand(command, async (...args: any[]) => {
183+
if (inputPrompt) {
184+
userInput = await vscode.window.showInputBox({
185+
prompt: inputPrompt,
186+
placeHolder: inputPlaceholder,
187+
})
188+
}
189+
190+
// Handle both code action and direct command cases
191+
let filePath: string
192+
let selectedText: string
193+
let diagnostics: any[] | undefined
194+
195+
if (args.length > 1) {
196+
// Called from code action
197+
;[filePath, selectedText, diagnostics] = args
198+
} else {
199+
// Called directly from command palette
200+
const context = EditorUtils.getEditorContext()
201+
if (!context) return
202+
;({ filePath, selectedText, diagnostics } = context)
203+
}
204+
205+
const params = {
206+
...{ filePath, selectedText },
207+
...(diagnostics ? { diagnostics } : {}),
208+
...(userInput ? { userInput } : {}),
209+
}
210+
211+
await ClineProvider.handleCodeAction(command, promptType, params)
212+
}),
201213
)
202214
}
203215

0 commit comments

Comments
 (0)