Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions src/integrations/editor/DecorationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class DecorationController {
private decorationType: DecorationType
private editor: vscode.TextEditor
private ranges: vscode.Range[] = []
private isDisposed: boolean = false

constructor(decorationType: DecorationType, editor: vscode.TextEditor) {
this.decorationType = decorationType
Expand All @@ -35,8 +36,8 @@ export class DecorationController {
}

addLines(startIndex: number, numLines: number) {
// Guard against invalid inputs
if (startIndex < 0 || numLines <= 0) {
// Guard against invalid inputs or disposed editor
if (startIndex < 0 || numLines <= 0 || this.isDisposed || !this.isEditorValid()) {
return
}

Expand All @@ -52,11 +53,18 @@ export class DecorationController {
}

clear() {
if (this.isDisposed || !this.isEditorValid()) {
return
}
this.ranges = []
this.editor.setDecorations(this.getDecoration(), this.ranges)
}

updateOverlayAfterLine(line: number, totalLines: number) {
if (this.isDisposed || !this.isEditorValid()) {
return
}

// Remove any existing ranges that start at or after the current line
this.ranges = this.ranges.filter((range) => range.end.line < line)

Expand All @@ -75,7 +83,39 @@ export class DecorationController {
}

setActiveLine(line: number) {
if (this.isDisposed || !this.isEditorValid()) {
return
}
this.ranges = [new vscode.Range(line, 0, line, Number.MAX_SAFE_INTEGER)]
this.editor.setDecorations(this.getDecoration(), this.ranges)
}

dispose() {
this.isDisposed = true
// Clear decorations before disposing
if (this.isEditorValid()) {
this.editor.setDecorations(this.getDecoration(), [])
}
}

private isEditorValid(): boolean {
// Check if the editor is still valid by verifying it exists in visible editors
// and its document hasn't been closed
try {
// In test environments, visibleTextEditors might be empty, so also check if document exists
const isInVisibleEditors = vscode.window.visibleTextEditors.includes(this.editor)
const hasValidDocument = this.editor.document && !this.editor.document.isClosed

// If we're in a test environment (no visible editors), rely on document validity
// Otherwise, check both conditions
if (vscode.window.visibleTextEditors.length === 0) {
return hasValidDocument
}

return isInVisibleEditors && hasValidDocument
} catch {
// If accessing editor properties throws, it's disposed
return false
}
}
}
46 changes: 39 additions & 7 deletions src/integrations/editor/DiffViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,10 @@ export class DiffViewProvider {
}
}

async saveChanges(diagnosticsEnabled: boolean = true, writeDelayMs: number = DEFAULT_WRITE_DELAY_MS): Promise<{
async saveChanges(
diagnosticsEnabled: boolean = true,
writeDelayMs: number = DEFAULT_WRITE_DELAY_MS,
): Promise<{
newProblemsMessage: string | undefined
userEdits: string | undefined
finalContent: string | undefined
Expand Down Expand Up @@ -216,22 +219,22 @@ export class DiffViewProvider {
// and can address them accordingly. If problems don't change immediately after
// applying a fix, won't be notified, which is generally fine since the
// initial fix is usually correct and it may just take time for linters to catch up.

let newProblemsMessage = ""

if (diagnosticsEnabled) {
// Add configurable delay to allow linters time to process and clean up issues
// like unused imports (especially important for Go and other languages)
// Ensure delay is non-negative
const safeDelayMs = Math.max(0, writeDelayMs)

try {
await delay(safeDelayMs)
} catch (error) {
// Log error but continue - delay failure shouldn't break the save operation
console.warn(`Failed to apply write delay: ${error}`)
}

const postDiagnostics = vscode.languages.getDiagnostics()

const newProblems = await diagnosticsToProblemsString(
Expand Down Expand Up @@ -549,7 +552,7 @@ export class DiffViewProvider {
}

private scrollEditorToLine(line: number) {
if (this.activeDiffEditor) {
if (this.activeDiffEditor && this.isEditorValid(this.activeDiffEditor)) {
const scrollLine = line + 4

this.activeDiffEditor.revealRange(
Expand All @@ -560,7 +563,7 @@ export class DiffViewProvider {
}

scrollToFirstDiff() {
if (!this.activeDiffEditor) {
if (!this.activeDiffEditor || !this.isEditorValid(this.activeDiffEditor)) {
return
}

Expand Down Expand Up @@ -599,6 +602,14 @@ export class DiffViewProvider {
}

async reset(): Promise<void> {
// Dispose decoration controllers before clearing references
if (this.fadedOverlayController) {
this.fadedOverlayController.dispose()
}
if (this.activeLineController) {
this.activeLineController.dispose()
}

await this.closeAllDiffViews()
this.editType = undefined
this.isEditing = false
Expand All @@ -611,4 +622,25 @@ export class DiffViewProvider {
this.streamedLines = []
this.preDiagnostics = []
}

private isEditorValid(editor: vscode.TextEditor): boolean {
// Check if the editor is still valid by verifying it exists in visible editors
// and its document hasn't been closed
try {
// In test environments, visibleTextEditors might be empty, so also check if document exists
const isInVisibleEditors = vscode.window.visibleTextEditors.includes(editor)
const hasValidDocument = editor.document && !editor.document.isClosed

// If we're in a test environment (no visible editors), rely on document validity
// Otherwise, check both conditions
if (vscode.window.visibleTextEditors.length === 0) {
return hasValidDocument
}

return isInVisibleEditors && hasValidDocument
} catch {
// If accessing editor properties throws, it's disposed
return false
}
}
}
Loading