Update EditorUtils

This commit is contained in:
Nissa Seru 2025-02-02 01:02:35 -05:00
parent 730bf8de97
commit e8a5e280b3
2 changed files with 103 additions and 29 deletions

View file

@ -39,16 +39,14 @@ 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)
// Always expand an empty selection to include full lines,
// using the full previous and next lines where available.
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 {
@ -97,12 +95,21 @@ export class EditorUtils {
}
static hasIntersectingRange(range1: vscode.Range, range2: vscode.Range): boolean {
return !(
// Use half-open interval semantics:
// If one range ends at or before the other's start, there's no intersection.
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
}
static getEditorContext(editor?: vscode.TextEditor): EditorContext | null {

View file

@ -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", () => {