Merge pull request #656 from samhvw8/feat/add-to-context-code-action

Feat add to context code action & Fix some code action error
This commit is contained in:
Matt Rubens
2025-01-31 00:24:38 -05:00
committed by GitHub
10 changed files with 372 additions and 201 deletions

View File

@@ -118,6 +118,11 @@
"command": "roo-cline.improveCode", "command": "roo-cline.improveCode",
"title": "Roo Code: Improve Code", "title": "Roo Code: Improve Code",
"category": "Roo Code" "category": "Roo Code"
},
{
"command": "roo-cline.addToContext",
"title": "Roo Code: Add To Context",
"category": "Roo Code"
} }
], ],
"menus": { "menus": {
@@ -136,6 +141,11 @@
"command": "roo-cline.improveCode", "command": "roo-cline.improveCode",
"when": "editorHasSelection", "when": "editorHasSelection",
"group": "Roo Code@3" "group": "Roo Code@3"
},
{
"command": "roo-cline.addToContext",
"when": "editorHasSelection",
"group": "Roo Code@4"
} }
], ],
"view/title": [ "view/title": [

View File

@@ -1,113 +1,27 @@
import * as vscode from "vscode" import * as vscode from "vscode"
import * as path from "path" import { EditorUtils } from "./EditorUtils"
import { ClineProvider } from "./webview/ClineProvider"
export const ACTION_NAMES = { export const ACTION_NAMES = {
EXPLAIN: "Roo Code: Explain Code", EXPLAIN: "Roo Code: Explain Code",
FIX: "Roo Code: Fix Code", FIX: "Roo Code: Fix Code",
FIX_LOGIC: "Roo Code: Fix Logic",
IMPROVE: "Roo Code: Improve Code", IMPROVE: "Roo Code: Improve Code",
ADD_TO_CONTEXT: "Roo Code: Add to Context",
} as const } as const
const COMMAND_IDS = { const COMMAND_IDS = {
EXPLAIN: "roo-cline.explainCode", EXPLAIN: "roo-cline.explainCode",
FIX: "roo-cline.fixCode", FIX: "roo-cline.fixCode",
IMPROVE: "roo-cline.improveCode", IMPROVE: "roo-cline.improveCode",
ADD_TO_CONTEXT: "roo-cline.addToContext",
} as const } 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 { export class CodeActionProvider implements vscode.CodeActionProvider {
public static readonly providedCodeActionKinds = [ public static readonly providedCodeActionKinds = [
vscode.CodeActionKind.QuickFix, vscode.CodeActionKind.QuickFix,
vscode.CodeActionKind.RefactorRewrite, 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 { private createAction(title: string, kind: vscode.CodeActionKind, command: string, args: any[]): vscode.CodeAction {
const action = new vscode.CodeAction(title, kind) const action = new vscode.CodeAction(title, kind)
action.command = { command, title, arguments: args } action.command = { command, title, arguments: args }
@@ -126,32 +40,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( public provideCodeActions(
document: vscode.TextDocument, document: vscode.TextDocument,
range: vscode.Range | vscode.Selection, range: vscode.Range | vscode.Selection,
context: vscode.CodeActionContext, context: vscode.CodeActionContext,
): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> { ): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> {
try { try {
const effectiveRange = this.getEffectiveRange(document, range) const effectiveRange = EditorUtils.getEffectiveRange(document, range)
if (!effectiveRange) { if (!effectiveRange) {
return [] return []
} }
const filePath = this.getFilePath(document) const filePath = EditorUtils.getFilePath(document)
const actions: vscode.CodeAction[] = [] const actions: vscode.CodeAction[] = []
// Create actions using helper method
// Add explain actions
actions.push( actions.push(
...this.createActionPair(ACTION_NAMES.EXPLAIN, vscode.CodeActionKind.QuickFix, COMMAND_IDS.EXPLAIN, [ ...this.createActionPair(ACTION_NAMES.EXPLAIN, vscode.CodeActionKind.QuickFix, COMMAND_IDS.EXPLAIN, [
filePath, filePath,
@@ -159,14 +61,13 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
]), ]),
) )
// Only process diagnostics if they exist
if (context.diagnostics.length > 0) { if (context.diagnostics.length > 0) {
const relevantDiagnostics = context.diagnostics.filter((d) => const relevantDiagnostics = context.diagnostics.filter((d) =>
this.hasIntersectingRange(effectiveRange.range, d.range), EditorUtils.hasIntersectingRange(effectiveRange.range, d.range),
) )
if (relevantDiagnostics.length > 0) { if (relevantDiagnostics.length > 0) {
const diagnosticMessages = relevantDiagnostics.map(this.createDiagnosticData) const diagnosticMessages = relevantDiagnostics.map(EditorUtils.createDiagnosticData)
actions.push( actions.push(
...this.createActionPair(ACTION_NAMES.FIX, vscode.CodeActionKind.QuickFix, COMMAND_IDS.FIX, [ ...this.createActionPair(ACTION_NAMES.FIX, vscode.CodeActionKind.QuickFix, COMMAND_IDS.FIX, [
filePath, filePath,
@@ -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( actions.push(
...this.createActionPair( ...this.createActionPair(
ACTION_NAMES.IMPROVE, ACTION_NAMES.IMPROVE,
@@ -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 return actions
} catch (error) { } catch (error) {
console.error("Error providing code actions:", error) console.error("Error providing code actions:", error)

141
src/core/EditorUtils.ts Normal file
View File

@@ -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
}
}
}

View File

@@ -1,5 +1,6 @@
import * as vscode from "vscode" import * as vscode from "vscode"
import { CodeActionProvider, ACTION_NAMES } from "../CodeActionProvider" import { CodeActionProvider, ACTION_NAMES } from "../CodeActionProvider"
import { EditorUtils } from "../EditorUtils"
// Mock VSCode API // Mock VSCode API
jest.mock("vscode", () => ({ jest.mock("vscode", () => ({
@@ -16,13 +17,6 @@ jest.mock("vscode", () => ({
start: { line: startLine, character: startChar }, start: { line: startLine, character: startChar },
end: { line: endLine, character: endChar }, end: { line: endLine, character: endChar },
})), })),
Position: jest.fn().mockImplementation((line, character) => ({
line,
character,
})),
workspace: {
getWorkspaceFolder: jest.fn(),
},
DiagnosticSeverity: { DiagnosticSeverity: {
Error: 0, Error: 0,
Warning: 1, Warning: 1,
@@ -31,6 +25,16 @@ jest.mock("vscode", () => ({
}, },
})) }))
// Mock EditorUtils
jest.mock("../EditorUtils", () => ({
EditorUtils: {
getEffectiveRange: jest.fn(),
getFilePath: jest.fn(),
hasIntersectingRange: jest.fn(),
createDiagnosticData: jest.fn(),
},
}))
describe("CodeActionProvider", () => { describe("CodeActionProvider", () => {
let provider: CodeActionProvider let provider: CodeActionProvider
let mockDocument: any let mockDocument: any
@@ -55,68 +59,32 @@ describe("CodeActionProvider", () => {
mockContext = { mockContext = {
diagnostics: [], diagnostics: [],
} }
})
describe("getEffectiveRange", () => { // Setup default EditorUtils mocks
it("should return selected text when available", () => { ;(EditorUtils.getEffectiveRange as jest.Mock).mockReturnValue({
mockDocument.getText.mockReturnValue("selected text")
const result = (provider as any).getEffectiveRange(mockDocument, mockRange)
expect(result).toEqual({
range: mockRange, range: mockRange,
text: "selected text", text: "test code",
})
})
it("should return null for empty line", () => {
mockDocument.getText.mockReturnValue("")
mockDocument.lineAt.mockReturnValue({ text: "", lineNumber: 0 })
const result = (provider as any).getEffectiveRange(mockDocument, mockRange)
expect(result).toBeNull()
})
})
describe("getFilePath", () => {
it("should return relative path when in workspace", () => {
const mockWorkspaceFolder = {
uri: { fsPath: "/test" },
}
;(vscode.workspace.getWorkspaceFolder as jest.Mock).mockReturnValue(mockWorkspaceFolder)
const result = (provider as any).getFilePath(mockDocument)
expect(result).toBe("file.ts")
})
it("should return absolute path when not in workspace", () => {
;(vscode.workspace.getWorkspaceFolder as jest.Mock).mockReturnValue(null)
const result = (provider as any).getFilePath(mockDocument)
expect(result).toBe("/test/file.ts")
}) })
;(EditorUtils.getFilePath as jest.Mock).mockReturnValue("/test/file.ts")
;(EditorUtils.hasIntersectingRange as jest.Mock).mockReturnValue(true)
;(EditorUtils.createDiagnosticData as jest.Mock).mockImplementation((d) => d)
}) })
describe("provideCodeActions", () => { describe("provideCodeActions", () => {
beforeEach(() => { it("should provide explain, improve, fix logic, and add to context actions by default", () => {
mockDocument.getText.mockReturnValue("test code")
mockDocument.lineAt.mockReturnValue({ text: "test code", lineNumber: 0 })
})
it("should provide explain and improve actions by default", () => {
const actions = provider.provideCodeActions(mockDocument, mockRange, mockContext) const actions = provider.provideCodeActions(mockDocument, mockRange, mockContext)
expect(actions).toHaveLength(4) expect(actions).toHaveLength(7) // 2 explain + 2 fix logic + 2 improve + 1 add to context
expect((actions as any)[0].title).toBe(`${ACTION_NAMES.EXPLAIN} in New Task`) expect((actions as any)[0].title).toBe(`${ACTION_NAMES.EXPLAIN} in New Task`)
expect((actions as any)[1].title).toBe(`${ACTION_NAMES.EXPLAIN} in Current Task`) expect((actions as any)[1].title).toBe(`${ACTION_NAMES.EXPLAIN} in Current Task`)
expect((actions as any)[2].title).toBe(`${ACTION_NAMES.IMPROVE} in New Task`) expect((actions as any)[2].title).toBe(`${ACTION_NAMES.FIX_LOGIC} in New Task`)
expect((actions as any)[3].title).toBe(`${ACTION_NAMES.IMPROVE} in Current Task`) expect((actions as any)[3].title).toBe(`${ACTION_NAMES.FIX_LOGIC} in Current Task`)
expect((actions as any)[4].title).toBe(`${ACTION_NAMES.IMPROVE} in New Task`)
expect((actions as any)[5].title).toBe(`${ACTION_NAMES.IMPROVE} in Current Task`)
expect((actions as any)[6].title).toBe(ACTION_NAMES.ADD_TO_CONTEXT)
}) })
it("should provide fix action when diagnostics exist", () => { it("should provide fix action instead of fix logic when diagnostics exist", () => {
mockContext.diagnostics = [ mockContext.diagnostics = [
{ {
message: "test error", message: "test error",
@@ -127,22 +95,33 @@ describe("CodeActionProvider", () => {
const actions = provider.provideCodeActions(mockDocument, mockRange, mockContext) const actions = provider.provideCodeActions(mockDocument, mockRange, mockContext)
expect(actions).toHaveLength(6) expect(actions).toHaveLength(7) // 2 explain + 2 fix + 2 improve + 1 add to context
expect((actions as any).some((a: any) => a.title === `${ACTION_NAMES.FIX} in New Task`)).toBe(true) expect((actions as any).some((a: any) => a.title === `${ACTION_NAMES.FIX} in New Task`)).toBe(true)
expect((actions as any).some((a: any) => a.title === `${ACTION_NAMES.FIX} in Current Task`)).toBe(true) expect((actions as any).some((a: any) => a.title === `${ACTION_NAMES.FIX} in Current Task`)).toBe(true)
expect((actions as any).some((a: any) => a.title === `${ACTION_NAMES.FIX_LOGIC} in New Task`)).toBe(false)
expect((actions as any).some((a: any) => a.title === `${ACTION_NAMES.FIX_LOGIC} in Current Task`)).toBe(
false,
)
}) })
it("should handle errors gracefully", () => { it("should return empty array when no effective range", () => {
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {}) ;(EditorUtils.getEffectiveRange as jest.Mock).mockReturnValue(null)
mockDocument.getText.mockImplementation(() => {
throw new Error("Test error")
})
mockDocument.lineAt.mockReturnValue({ text: "test", lineNumber: 0 })
const actions = provider.provideCodeActions(mockDocument, mockRange, mockContext) const actions = provider.provideCodeActions(mockDocument, mockRange, mockContext)
expect(actions).toEqual([]) expect(actions).toEqual([])
expect(consoleErrorSpy).toHaveBeenCalledWith("Error getting effective range:", expect.any(Error)) })
it("should handle errors gracefully", () => {
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {})
;(EditorUtils.getEffectiveRange as jest.Mock).mockImplementation(() => {
throw new Error("Test error")
})
const actions = provider.provideCodeActions(mockDocument, mockRange, mockContext)
expect(actions).toEqual([])
expect(consoleErrorSpy).toHaveBeenCalledWith("Error providing code actions:", expect.any(Error))
consoleErrorSpy.mockRestore() consoleErrorSpy.mockRestore()
}) })

View File

@@ -0,0 +1,75 @@
import * as vscode from "vscode"
import { EditorUtils } from "../EditorUtils"
// Mock VSCode API
jest.mock("vscode", () => ({
Range: jest.fn().mockImplementation((startLine, startChar, endLine, endChar) => ({
start: { line: startLine, character: startChar },
end: { line: endLine, character: endChar },
})),
Position: jest.fn().mockImplementation((line, character) => ({
line,
character,
})),
workspace: {
getWorkspaceFolder: jest.fn(),
},
}))
describe("EditorUtils", () => {
let mockDocument: any
beforeEach(() => {
mockDocument = {
getText: jest.fn(),
lineAt: jest.fn(),
lineCount: 10,
uri: { fsPath: "/test/file.ts" },
}
})
describe("getEffectiveRange", () => {
it("should return selected text when available", () => {
const mockRange = new vscode.Range(0, 0, 0, 10)
mockDocument.getText.mockReturnValue("selected text")
const result = EditorUtils.getEffectiveRange(mockDocument, mockRange)
expect(result).toEqual({
range: mockRange,
text: "selected text",
})
})
it("should return null for empty line", () => {
const mockRange = new vscode.Range(0, 0, 0, 10)
mockDocument.getText.mockReturnValue("")
mockDocument.lineAt.mockReturnValue({ text: "", lineNumber: 0 })
const result = EditorUtils.getEffectiveRange(mockDocument, mockRange)
expect(result).toBeNull()
})
})
describe("getFilePath", () => {
it("should return relative path when in workspace", () => {
const mockWorkspaceFolder = {
uri: { fsPath: "/test" },
}
;(vscode.workspace.getWorkspaceFolder as jest.Mock).mockReturnValue(mockWorkspaceFolder)
const result = EditorUtils.getFilePath(mockDocument)
expect(result).toBe("file.ts")
})
it("should return absolute path when not in workspace", () => {
;(vscode.workspace.getWorkspaceFolder as jest.Mock).mockReturnValue(null)
const result = EditorUtils.getFilePath(mockDocument)
expect(result).toBe("/test/file.ts")
})
})
})

View File

@@ -238,6 +238,16 @@ export class ClineProvider implements vscode.WebviewViewProvider {
const prompt = supportPrompt.create(promptType, params, customSupportPrompts) const prompt = supportPrompt.create(promptType, params, customSupportPrompts)
if (command.endsWith("addToContext")) {
await visibleProvider.postMessageToWebview({
type: "invoke",
invoke: "setChatBoxMessage",
text: prompt,
})
return
}
if (visibleProvider.cline && command.endsWith("InCurrentTask")) { if (visibleProvider.cline && command.endsWith("InCurrentTask")) {
await visibleProvider.postMessageToWebview({ await visibleProvider.postMessageToWebview({
type: "invoke", type: "invoke",

View File

@@ -6,6 +6,7 @@ import { ClineProvider } from "./core/webview/ClineProvider"
import { createClineAPI } from "./exports" import { createClineAPI } from "./exports"
import "./utils/path" // necessary to have access to String.prototype.toPosix import "./utils/path" // necessary to have access to String.prototype.toPosix
import { ACTION_NAMES, CodeActionProvider } from "./core/CodeActionProvider" import { ACTION_NAMES, CodeActionProvider } from "./core/CodeActionProvider"
import { EditorUtils } from "./core/EditorUtils"
import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider" import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
/* /*
@@ -171,16 +172,13 @@ export function activate(context: vscode.ExtensionContext) {
context: vscode.ExtensionContext, context: vscode.ExtensionContext,
command: string, command: string,
promptType: keyof typeof ACTION_NAMES, promptType: keyof typeof ACTION_NAMES,
inNewTask: boolean,
inputPrompt?: string, inputPrompt?: string,
inputPlaceholder?: string, inputPlaceholder?: string,
) => { ) => {
let userInput: string | undefined let userInput: string | undefined
context.subscriptions.push( context.subscriptions.push(
vscode.commands.registerCommand( vscode.commands.registerCommand(command, async (...args: any[]) => {
command,
async (filePath: string, selectedText: string, diagnostics?: any[]) => {
if (inputPrompt) { if (inputPrompt) {
userInput = await vscode.window.showInputBox({ userInput = await vscode.window.showInputBox({
prompt: inputPrompt, prompt: inputPrompt,
@@ -188,16 +186,29 @@ export function activate(context: vscode.ExtensionContext) {
}) })
} }
// 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 = { const params = {
filePath, ...{ filePath, selectedText },
selectedText,
...(diagnostics ? { diagnostics } : {}), ...(diagnostics ? { diagnostics } : {}),
...(userInput ? { userInput } : {}), ...(userInput ? { userInput } : {}),
} }
await ClineProvider.handleCodeAction(command, promptType, params) await ClineProvider.handleCodeAction(command, promptType, params)
}, }),
),
) )
} }
@@ -210,10 +221,10 @@ export function activate(context: vscode.ExtensionContext) {
inputPlaceholder?: string, inputPlaceholder?: string,
) => { ) => {
// Register new task version // Register new task version
registerCodeAction(context, baseCommand, promptType, true, inputPrompt, inputPlaceholder) registerCodeAction(context, baseCommand, promptType, inputPrompt, inputPlaceholder)
// Register current task version // Register current task version
registerCodeAction(context, `${baseCommand}InCurrentTask`, promptType, false, inputPrompt, inputPlaceholder) registerCodeAction(context, `${baseCommand}InCurrentTask`, promptType, inputPrompt, inputPlaceholder)
} }
// Register code action commands // Register code action commands
@@ -241,6 +252,8 @@ export function activate(context: vscode.ExtensionContext) {
"E.g. Focus on performance optimization", "E.g. Focus on performance optimization",
) )
registerCodeAction(context, "roo-cline.addToContext", "ADD_TO_CONTEXT")
return createClineAPI(outputChannel, sidebarProvider) return createClineAPI(outputChannel, sidebarProvider)
} }

View File

@@ -50,7 +50,7 @@ export interface ExtensionMessage {
| "historyButtonClicked" | "historyButtonClicked"
| "promptsButtonClicked" | "promptsButtonClicked"
| "didBecomeVisible" | "didBecomeVisible"
invoke?: "sendMessage" | "primaryButtonClick" | "secondaryButtonClick" invoke?: "sendMessage" | "primaryButtonClick" | "secondaryButtonClick" | "setChatBoxMessage"
state?: ExtensionState state?: ExtensionState
images?: string[] images?: string[]
ollamaModels?: string[] ollamaModels?: string[]

View File

@@ -18,8 +18,8 @@ export const createPrompt = (template: string, params: PromptParams): string =>
} }
} }
// Replace any remaining user_input placeholders with empty string // Replace any remaining placeholders with empty strings
result = result.replaceAll("${userInput}", "") result = result.replaceAll(/\${[^}]*}/g, "")
return result return result
} }
@@ -42,7 +42,7 @@ const supportPromptConfigs: Record<string, SupportPromptConfig> = {
EXPLAIN: { EXPLAIN: {
label: "Explain Code", label: "Explain Code",
description: description:
"Get detailed explanations of code snippets, functions, or entire files. Useful for understanding complex code or learning new patterns. Available in the editor context menu (right-click on selected code).", "Get detailed explanations of code snippets, functions, or entire files. Useful for understanding complex code or learning new patterns. Available in code actions (lightbulb icon in the editor) and the editor context menu (right-click on selected code).",
template: `Explain the following code from file path @/\${filePath}: template: `Explain the following code from file path @/\${filePath}:
\${userInput} \${userInput}
@@ -58,7 +58,7 @@ Please provide a clear and concise explanation of what this code does, including
FIX: { FIX: {
label: "Fix Issues", label: "Fix Issues",
description: description:
"Get help identifying and resolving bugs, errors, or code quality issues. Provides step-by-step guidance for fixing problems. Available in the editor context menu (right-click on selected code).", "Get help identifying and resolving bugs, errors, or code quality issues. Provides step-by-step guidance for fixing problems. Available in code actions (lightbulb icon in the editor) and the editor context menu (right-click on selected code).",
template: `Fix any issues in the following code from file path @/\${filePath} template: `Fix any issues in the following code from file path @/\${filePath}
\${diagnosticText} \${diagnosticText}
\${userInput} \${userInput}
@@ -76,7 +76,7 @@ Please:
IMPROVE: { IMPROVE: {
label: "Improve Code", label: "Improve Code",
description: description:
"Receive suggestions for code optimization, better practices, and architectural improvements while maintaining functionality. Available in the editor context menu (right-click on selected code).", "Receive suggestions for code optimization, better practices, and architectural improvements while maintaining functionality. Available in code actions (lightbulb icon in the editor) and the editor context menu (right-click on selected code).",
template: `Improve the following code from file path @/\${filePath}: template: `Improve the following code from file path @/\${filePath}:
\${userInput} \${userInput}
@@ -92,6 +92,15 @@ Please suggest improvements for:
Provide the improved code along with explanations for each enhancement.`, Provide the improved code along with explanations for each enhancement.`,
}, },
ADD_TO_CONTEXT: {
label: "Add to Context",
description:
"Add context to your current task or conversation. Useful for providing additional information or clarifications. Available in code actions (lightbulb icon in the editor). and the editor context menu (right-click on selected code).",
template: `@/\${filePath}:
\`\`\`
\${selectedText}
\`\`\``,
},
} as const } as const
type SupportPromptType = keyof typeof supportPromptConfigs type SupportPromptType = keyof typeof supportPromptConfigs

View File

@@ -330,6 +330,20 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
[messages.length, clineAsk], [messages.length, clineAsk],
) )
const handleSetChatBoxMessage = useCallback(
(text: string, images: string[]) => {
// Avoid nested template literals by breaking down the logic
let newValue = text
if (inputValue !== "") {
newValue = inputValue + " " + text
}
setInputValue(newValue)
setSelectedImages([...selectedImages, ...images])
},
[inputValue, selectedImages],
)
const startNewTask = useCallback(() => { const startNewTask = useCallback(() => {
vscode.postMessage({ type: "clearTask" }) vscode.postMessage({ type: "clearTask" })
}, []) }, [])
@@ -469,6 +483,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
case "sendMessage": case "sendMessage":
handleSendMessage(message.text ?? "", message.images ?? []) handleSendMessage(message.text ?? "", message.images ?? [])
break break
case "setChatBoxMessage":
handleSetChatBoxMessage(message.text ?? "", message.images ?? [])
break
case "primaryButtonClick": case "primaryButtonClick":
handlePrimaryButtonClick(message.text ?? "", message.images ?? []) handlePrimaryButtonClick(message.text ?? "", message.images ?? [])
break break
@@ -484,6 +501,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
textAreaDisabled, textAreaDisabled,
enableButtons, enableButtons,
handleSendMessage, handleSendMessage,
handleSetChatBoxMessage,
handlePrimaryButtonClick, handlePrimaryButtonClick,
handleSecondaryButtonClick, handleSecondaryButtonClick,
], ],