mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
Merge pull request #730 from nissa-seru/fix-editorutils
Update EditorUtils
This commit is contained in:
commit
6da05598bf
2 changed files with 162 additions and 32 deletions
|
|
@ -1,29 +1,63 @@
|
|||
import * as vscode from "vscode"
|
||||
import * as path from "path"
|
||||
|
||||
/**
|
||||
* Represents an effective range in a document along with the corresponding text.
|
||||
*/
|
||||
export interface EffectiveRange {
|
||||
/** The range within the document. */
|
||||
range: vscode.Range
|
||||
/** The text within the specified range. */
|
||||
text: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents diagnostic information extracted from a VSCode diagnostic.
|
||||
*/
|
||||
export interface DiagnosticData {
|
||||
/** The diagnostic message. */
|
||||
message: string
|
||||
/** The severity level of the diagnostic. */
|
||||
severity: vscode.DiagnosticSeverity
|
||||
/**
|
||||
* Optional diagnostic code.
|
||||
* Can be a string, number, or an object with value and target.
|
||||
*/
|
||||
code?: string | number | { value: string | number; target: vscode.Uri }
|
||||
/** Optional source identifier for the diagnostic (e.g., the extension name). */
|
||||
source?: string
|
||||
/** The range within the document where the diagnostic applies. */
|
||||
range: vscode.Range
|
||||
}
|
||||
|
||||
/**
|
||||
* Contextual information for a VSCode text editor.
|
||||
*/
|
||||
export interface EditorContext {
|
||||
/** The file path of the current document. */
|
||||
filePath: string
|
||||
/** The effective text selected or derived from the document. */
|
||||
selectedText: string
|
||||
/** Optional list of diagnostics associated with the effective range. */
|
||||
diagnostics?: DiagnosticData[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class providing helper methods for working with VSCode editors and documents.
|
||||
*/
|
||||
export class EditorUtils {
|
||||
// Cache file paths for performance
|
||||
/** Cache mapping text documents to their computed file paths. */
|
||||
private static readonly filePathCache = new WeakMap<vscode.TextDocument, string>()
|
||||
|
||||
/**
|
||||
* Computes the effective range of text from the given document based on the user's selection.
|
||||
* If the selection is non-empty, returns that directly.
|
||||
* Otherwise, if the current line is non-empty, expands the range to include the adjacent lines.
|
||||
*
|
||||
* @param document - The text document to extract text from.
|
||||
* @param range - The user selected range or selection.
|
||||
* @returns An EffectiveRange object containing the effective range and its text, or null if no valid text is found.
|
||||
*/
|
||||
static getEffectiveRange(
|
||||
document: vscode.TextDocument,
|
||||
range: vscode.Range | vscode.Selection,
|
||||
|
|
@ -39,16 +73,12 @@ export class EditorUtils {
|
|||
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)
|
||||
const startLineIndex = Math.max(0, currentLine.lineNumber - 1)
|
||||
const endLineIndex = 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),
|
||||
new vscode.Position(startLineIndex, 0),
|
||||
new vscode.Position(endLineIndex, document.lineAt(endLineIndex).text.length),
|
||||
)
|
||||
|
||||
return {
|
||||
|
|
@ -61,8 +91,15 @@ export class EditorUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the file path of a given text document.
|
||||
* Utilizes an internal cache to avoid redundant computations.
|
||||
* If the document belongs to a workspace, attempts to compute a relative path; otherwise, returns the absolute fsPath.
|
||||
*
|
||||
* @param document - The text document for which to retrieve the file path.
|
||||
* @returns The file path as a string.
|
||||
*/
|
||||
static getFilePath(document: vscode.TextDocument): string {
|
||||
// Check cache first
|
||||
let filePath = this.filePathCache.get(document)
|
||||
if (filePath) {
|
||||
return filePath
|
||||
|
|
@ -77,7 +114,6 @@ export class EditorUtils {
|
|||
filePath = !relativePath || relativePath.startsWith("..") ? document.uri.fsPath : relativePath
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
this.filePathCache.set(document, filePath)
|
||||
return filePath
|
||||
} catch (error) {
|
||||
|
|
@ -86,6 +122,12 @@ export class EditorUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a VSCode Diagnostic object to a local DiagnosticData instance.
|
||||
*
|
||||
* @param diagnostic - The VSCode diagnostic to convert.
|
||||
* @returns The corresponding DiagnosticData object.
|
||||
*/
|
||||
static createDiagnosticData(diagnostic: vscode.Diagnostic): DiagnosticData {
|
||||
return {
|
||||
message: diagnostic.message,
|
||||
|
|
@ -96,15 +138,36 @@ export class EditorUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether two VSCode ranges intersect.
|
||||
*
|
||||
* @param range1 - The first range.
|
||||
* @param range2 - The second range.
|
||||
* @returns True if the ranges intersect; otherwise, false.
|
||||
*/
|
||||
static hasIntersectingRange(range1: vscode.Range, range2: vscode.Range): boolean {
|
||||
return !(
|
||||
if (
|
||||
range1.end.line < range2.start.line ||
|
||||
(range1.end.line === range2.start.line && range1.end.character <= range2.start.character)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
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)
|
||||
)
|
||||
(range2.end.line === range1.start.line && range2.end.character <= range1.start.character)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the editor context from the provided text editor or from the active text editor.
|
||||
* The context includes file path, effective selected text, and any diagnostics that intersect with the effective range.
|
||||
*
|
||||
* @param editor - (Optional) A specific text editor instance. If not provided, the active text editor is used.
|
||||
* @returns An EditorContext object if successful; otherwise, null.
|
||||
*/
|
||||
static getEditorContext(editor?: vscode.TextEditor): EditorContext | null {
|
||||
try {
|
||||
if (!editor) {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,35 @@
|
|||
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(),
|
||||
},
|
||||
}))
|
||||
// Use simple classes to simulate VSCode's Range and Position behavior.
|
||||
jest.mock("vscode", () => {
|
||||
class MockPosition {
|
||||
constructor(
|
||||
public line: number,
|
||||
public character: number,
|
||||
) {}
|
||||
}
|
||||
class MockRange {
|
||||
start: MockPosition
|
||||
end: MockPosition
|
||||
constructor(start: MockPosition, end: MockPosition) {
|
||||
this.start = start
|
||||
this.end = end
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
Range: MockRange,
|
||||
Position: MockPosition,
|
||||
workspace: {
|
||||
getWorkspaceFolder: jest.fn(),
|
||||
},
|
||||
window: { activeTextEditor: undefined },
|
||||
languages: {
|
||||
getDiagnostics: jest.fn(() => []),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe("EditorUtils", () => {
|
||||
let mockDocument: any
|
||||
|
|
@ -30,7 +45,7 @@ describe("EditorUtils", () => {
|
|||
|
||||
describe("getEffectiveRange", () => {
|
||||
it("should return selected text when available", () => {
|
||||
const mockRange = new vscode.Range(0, 0, 0, 10)
|
||||
const mockRange = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 10))
|
||||
mockDocument.getText.mockReturnValue("selected text")
|
||||
|
||||
const result = EditorUtils.getEffectiveRange(mockDocument, mockRange)
|
||||
|
|
@ -42,7 +57,7 @@ describe("EditorUtils", () => {
|
|||
})
|
||||
|
||||
it("should return null for empty line", () => {
|
||||
const mockRange = new vscode.Range(0, 0, 0, 10)
|
||||
const mockRange = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 10))
|
||||
mockDocument.getText.mockReturnValue("")
|
||||
mockDocument.lineAt.mockReturnValue({ text: "", lineNumber: 0 })
|
||||
|
||||
|
|
@ -50,6 +65,58 @@ describe("EditorUtils", () => {
|
|||
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it("should expand empty selection to full lines", () => {
|
||||
// Simulate a caret (empty selection) on line 2 at character 5.
|
||||
const initialRange = new vscode.Range(new vscode.Position(2, 5), new vscode.Position(2, 5))
|
||||
// Return non-empty text for any line with text (lines 1, 2, and 3).
|
||||
mockDocument.lineAt.mockImplementation((line: number) => {
|
||||
return { text: `Line ${line} text`, lineNumber: line }
|
||||
})
|
||||
mockDocument.getText.mockImplementation((range: any) => {
|
||||
// If the range is exactly the empty initial selection, return an empty string.
|
||||
if (
|
||||
range.start.line === initialRange.start.line &&
|
||||
range.start.character === initialRange.start.character &&
|
||||
range.end.line === initialRange.end.line &&
|
||||
range.end.character === initialRange.end.character
|
||||
) {
|
||||
return ""
|
||||
}
|
||||
return "expanded text"
|
||||
})
|
||||
|
||||
const result = EditorUtils.getEffectiveRange(mockDocument, initialRange)
|
||||
|
||||
expect(result).not.toBeNull()
|
||||
// Expected effective range: from the beginning of line 1 to the end of line 3.
|
||||
expect(result?.range.start).toEqual({ line: 1, character: 0 })
|
||||
expect(result?.range.end).toEqual({ line: 3, character: 11 })
|
||||
expect(result?.text).toBe("expanded text")
|
||||
})
|
||||
})
|
||||
|
||||
describe("hasIntersectingRange", () => {
|
||||
it("should return false for ranges that only touch boundaries", () => {
|
||||
// Range1: [0, 0) - [0, 10) and Range2: [0, 10) - [0, 20)
|
||||
const range1 = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 10))
|
||||
const range2 = new vscode.Range(new vscode.Position(0, 10), new vscode.Position(0, 20))
|
||||
expect(EditorUtils.hasIntersectingRange(range1, range2)).toBe(false)
|
||||
})
|
||||
|
||||
it("should return true for overlapping ranges", () => {
|
||||
// Range1: [0, 0) - [0, 15) and Range2: [0, 10) - [0, 20)
|
||||
const range1 = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 15))
|
||||
const range2 = new vscode.Range(new vscode.Position(0, 10), new vscode.Position(0, 20))
|
||||
expect(EditorUtils.hasIntersectingRange(range1, range2)).toBe(true)
|
||||
})
|
||||
|
||||
it("should return false for non-overlapping ranges", () => {
|
||||
// Range1: [0, 0) - [0, 10) and Range2: [1, 0) - [1, 5)
|
||||
const range1 = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 10))
|
||||
const range2 = new vscode.Range(new vscode.Position(1, 0), new vscode.Position(1, 5))
|
||||
expect(EditorUtils.hasIntersectingRange(range1, range2)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getFilePath", () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue