1+ import * as vscode from 'vscode' ;
2+ import * as path from 'path' ;
3+
4+ const ACTION_NAMES = {
5+ EXPLAIN : 'Roo Cline: Explain Code' ,
6+ FIX : 'Roo Cline: Fix Code' ,
7+ IMPROVE : 'Roo Cline: Improve Code'
8+ } as const ;
9+
10+ const COMMAND_IDS = {
11+ EXPLAIN : 'roo-cline.explainCode' ,
12+ FIX : 'roo-cline.fixCode' ,
13+ IMPROVE : 'roo-cline.improveCode'
14+ } as const ;
15+
16+ interface DiagnosticData {
17+ message : string ;
18+ severity : vscode . DiagnosticSeverity ;
19+ code ?: string | number | { value : string | number ; target : vscode . Uri } ;
20+ source ?: string ;
21+ range : vscode . Range ;
22+ }
23+
24+ interface EffectiveRange {
25+ range : vscode . Range ;
26+ text : string ;
27+ }
28+
29+ export class CodeActionProvider implements vscode . CodeActionProvider {
30+ public static readonly providedCodeActionKinds = [
31+ vscode . CodeActionKind . QuickFix ,
32+ vscode . CodeActionKind . RefactorRewrite ,
33+ ] ;
34+
35+ // Cache file paths for performance
36+ private readonly filePathCache = new WeakMap < vscode . TextDocument , string > ( ) ;
37+
38+ private getEffectiveRange (
39+ document : vscode . TextDocument ,
40+ range : vscode . Range | vscode . Selection
41+ ) : EffectiveRange | null {
42+ try {
43+ const selectedText = document . getText ( range ) ;
44+ if ( selectedText ) {
45+ return { range, text : selectedText } ;
46+ }
47+
48+ const currentLine = document . lineAt ( range . start . line ) ;
49+ if ( ! currentLine . text . trim ( ) ) {
50+ return null ;
51+ }
52+
53+ // Optimize range creation by checking bounds first
54+ const startLine = Math . max ( 0 , currentLine . lineNumber - 1 ) ;
55+ const endLine = Math . min ( document . lineCount - 1 , currentLine . lineNumber + 1 ) ;
56+
57+ // Only create new positions if needed
58+ const effectiveRange = new vscode . Range (
59+ startLine === currentLine . lineNumber ? range . start : new vscode . Position ( startLine , 0 ) ,
60+ endLine === currentLine . lineNumber ? range . end : new vscode . Position ( endLine , document . lineAt ( endLine ) . text . length )
61+ ) ;
62+
63+ return {
64+ range : effectiveRange ,
65+ text : document . getText ( effectiveRange )
66+ } ;
67+ } catch ( error ) {
68+ console . error ( 'Error getting effective range:' , error ) ;
69+ return null ;
70+ }
71+ }
72+
73+ private getFilePath ( document : vscode . TextDocument ) : string {
74+ // Check cache first
75+ let filePath = this . filePathCache . get ( document ) ;
76+ if ( filePath ) {
77+ return filePath ;
78+ }
79+
80+ try {
81+ const workspaceFolder = vscode . workspace . getWorkspaceFolder ( document . uri ) ;
82+ if ( ! workspaceFolder ) {
83+ filePath = document . uri . fsPath ;
84+ } else {
85+ const relativePath = path . relative ( workspaceFolder . uri . fsPath , document . uri . fsPath ) ;
86+ filePath = ( ! relativePath || relativePath . startsWith ( '..' ) ) ? document . uri . fsPath : relativePath ;
87+ }
88+
89+ // Cache the result
90+ this . filePathCache . set ( document , filePath ) ;
91+ return filePath ;
92+ } catch ( error ) {
93+ console . error ( 'Error getting file path:' , error ) ;
94+ return document . uri . fsPath ;
95+ }
96+ }
97+
98+ private createDiagnosticData ( diagnostic : vscode . Diagnostic ) : DiagnosticData {
99+ return {
100+ message : diagnostic . message ,
101+ severity : diagnostic . severity ,
102+ code : diagnostic . code ,
103+ source : diagnostic . source ,
104+ range : diagnostic . range // Reuse the range object
105+ } ;
106+ }
107+
108+ private createAction (
109+ title : string ,
110+ kind : vscode . CodeActionKind ,
111+ command : string ,
112+ args : any [ ]
113+ ) : vscode . CodeAction {
114+ const action = new vscode . CodeAction ( title , kind ) ;
115+ action . command = { command, title, arguments : args } ;
116+ return action ;
117+ }
118+
119+ private hasIntersectingRange ( range1 : vscode . Range , range2 : vscode . Range ) : boolean {
120+ // Optimize range intersection check
121+ return ! (
122+ range2 . end . line < range1 . start . line ||
123+ range2 . start . line > range1 . end . line ||
124+ ( range2 . end . line === range1 . start . line && range2 . end . character < range1 . start . character ) ||
125+ ( range2 . start . line === range1 . end . line && range2 . start . character > range1 . end . character )
126+ ) ;
127+ }
128+
129+ public provideCodeActions (
130+ document : vscode . TextDocument ,
131+ range : vscode . Range | vscode . Selection ,
132+ context : vscode . CodeActionContext
133+ ) : vscode . ProviderResult < ( vscode . CodeAction | vscode . Command ) [ ] > {
134+ try {
135+ const effectiveRange = this . getEffectiveRange ( document , range ) ;
136+ if ( ! effectiveRange ) {
137+ return [ ] ;
138+ }
139+
140+ const filePath = this . getFilePath ( document ) ;
141+ const actions : vscode . CodeAction [ ] = [ ] ;
142+
143+ // Create actions using helper method
144+ actions . push ( this . createAction (
145+ ACTION_NAMES . EXPLAIN ,
146+ vscode . CodeActionKind . QuickFix ,
147+ COMMAND_IDS . EXPLAIN ,
148+ [ filePath , effectiveRange . text ]
149+ ) ) ;
150+
151+ // Only process diagnostics if they exist
152+ if ( context . diagnostics . length > 0 ) {
153+ const relevantDiagnostics = context . diagnostics . filter ( d =>
154+ this . hasIntersectingRange ( effectiveRange . range , d . range )
155+ ) ;
156+
157+ if ( relevantDiagnostics . length > 0 ) {
158+ const diagnosticMessages = relevantDiagnostics . map ( this . createDiagnosticData ) ;
159+ actions . push ( this . createAction (
160+ ACTION_NAMES . FIX ,
161+ vscode . CodeActionKind . QuickFix ,
162+ COMMAND_IDS . FIX ,
163+ [ filePath , effectiveRange . text , diagnosticMessages ]
164+ ) ) ;
165+ }
166+ }
167+
168+ actions . push ( this . createAction (
169+ ACTION_NAMES . IMPROVE ,
170+ vscode . CodeActionKind . RefactorRewrite ,
171+ COMMAND_IDS . IMPROVE ,
172+ [ filePath , effectiveRange . text ]
173+ ) ) ;
174+
175+ return actions ;
176+ } catch ( error ) {
177+ console . error ( 'Error providing code actions:' , error ) ;
178+ return [ ] ;
179+ }
180+ }
181+ }
0 commit comments