Skip to content

Commit 5afee2a

Browse files
committed
fix: prevent TextEditor disposed warnings by adding proper disposal checks
- Add isDisposed flag and dispose() method to DecorationController - Add isEditorValid() checks before accessing TextEditor methods - Properly dispose decoration controllers in DiffViewProvider.reset() - Handle test environments where visibleTextEditors may be empty Fixes #5954
1 parent 37300ef commit 5afee2a

File tree

2 files changed

+82
-10
lines changed

2 files changed

+82
-10
lines changed

src/integrations/editor/DecorationController.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export class DecorationController {
1919
private decorationType: DecorationType
2020
private editor: vscode.TextEditor
2121
private ranges: vscode.Range[] = []
22+
private isDisposed: boolean = false
2223

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

3738
addLines(startIndex: number, numLines: number) {
38-
// Guard against invalid inputs
39-
if (startIndex < 0 || numLines <= 0) {
39+
// Guard against invalid inputs or disposed editor
40+
if (startIndex < 0 || numLines <= 0 || this.isDisposed || !this.isEditorValid()) {
4041
return
4142
}
4243

@@ -52,11 +53,18 @@ export class DecorationController {
5253
}
5354

5455
clear() {
56+
if (this.isDisposed || !this.isEditorValid()) {
57+
return
58+
}
5559
this.ranges = []
5660
this.editor.setDecorations(this.getDecoration(), this.ranges)
5761
}
5862

5963
updateOverlayAfterLine(line: number, totalLines: number) {
64+
if (this.isDisposed || !this.isEditorValid()) {
65+
return
66+
}
67+
6068
// Remove any existing ranges that start at or after the current line
6169
this.ranges = this.ranges.filter((range) => range.end.line < line)
6270

@@ -75,7 +83,39 @@ export class DecorationController {
7583
}
7684

7785
setActiveLine(line: number) {
86+
if (this.isDisposed || !this.isEditorValid()) {
87+
return
88+
}
7889
this.ranges = [new vscode.Range(line, 0, line, Number.MAX_SAFE_INTEGER)]
7990
this.editor.setDecorations(this.getDecoration(), this.ranges)
8091
}
92+
93+
dispose() {
94+
this.isDisposed = true
95+
// Clear decorations before disposing
96+
if (this.isEditorValid()) {
97+
this.editor.setDecorations(this.getDecoration(), [])
98+
}
99+
}
100+
101+
private isEditorValid(): boolean {
102+
// Check if the editor is still valid by verifying it exists in visible editors
103+
// and its document hasn't been closed
104+
try {
105+
// In test environments, visibleTextEditors might be empty, so also check if document exists
106+
const isInVisibleEditors = vscode.window.visibleTextEditors.includes(this.editor)
107+
const hasValidDocument = this.editor.document && !this.editor.document.isClosed
108+
109+
// If we're in a test environment (no visible editors), rely on document validity
110+
// Otherwise, check both conditions
111+
if (vscode.window.visibleTextEditors.length === 0) {
112+
return hasValidDocument
113+
}
114+
115+
return isInVisibleEditors && hasValidDocument
116+
} catch {
117+
// If accessing editor properties throws, it's disposed
118+
return false
119+
}
120+
}
81121
}

src/integrations/editor/DiffViewProvider.ts

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export class DiffViewProvider {
119119
const diffEditor = this.activeDiffEditor
120120
const document = diffEditor?.document
121121

122-
if (!diffEditor || !document) {
122+
if (!diffEditor || !document || !this.isEditorValid(diffEditor)) {
123123
throw new Error("User closed text editor, unable to edit file...")
124124
}
125125

@@ -181,7 +181,10 @@ export class DiffViewProvider {
181181
}
182182
}
183183

184-
async saveChanges(diagnosticsEnabled: boolean = true, writeDelayMs: number = DEFAULT_WRITE_DELAY_MS): Promise<{
184+
async saveChanges(
185+
diagnosticsEnabled: boolean = true,
186+
writeDelayMs: number = DEFAULT_WRITE_DELAY_MS,
187+
): Promise<{
185188
newProblemsMessage: string | undefined
186189
userEdits: string | undefined
187190
finalContent: string | undefined
@@ -216,22 +219,22 @@ export class DiffViewProvider {
216219
// and can address them accordingly. If problems don't change immediately after
217220
// applying a fix, won't be notified, which is generally fine since the
218221
// initial fix is usually correct and it may just take time for linters to catch up.
219-
222+
220223
let newProblemsMessage = ""
221-
224+
222225
if (diagnosticsEnabled) {
223226
// Add configurable delay to allow linters time to process and clean up issues
224227
// like unused imports (especially important for Go and other languages)
225228
// Ensure delay is non-negative
226229
const safeDelayMs = Math.max(0, writeDelayMs)
227-
230+
228231
try {
229232
await delay(safeDelayMs)
230233
} catch (error) {
231234
// Log error but continue - delay failure shouldn't break the save operation
232235
console.warn(`Failed to apply write delay: ${error}`)
233236
}
234-
237+
235238
const postDiagnostics = vscode.languages.getDiagnostics()
236239

237240
const newProblems = await diagnosticsToProblemsString(
@@ -549,7 +552,7 @@ export class DiffViewProvider {
549552
}
550553

551554
private scrollEditorToLine(line: number) {
552-
if (this.activeDiffEditor) {
555+
if (this.activeDiffEditor && this.isEditorValid(this.activeDiffEditor)) {
553556
const scrollLine = line + 4
554557

555558
this.activeDiffEditor.revealRange(
@@ -560,7 +563,7 @@ export class DiffViewProvider {
560563
}
561564

562565
scrollToFirstDiff() {
563-
if (!this.activeDiffEditor) {
566+
if (!this.activeDiffEditor || !this.isEditorValid(this.activeDiffEditor)) {
564567
return
565568
}
566569

@@ -599,6 +602,14 @@ export class DiffViewProvider {
599602
}
600603

601604
async reset(): Promise<void> {
605+
// Dispose decoration controllers before clearing references
606+
if (this.fadedOverlayController) {
607+
this.fadedOverlayController.dispose()
608+
}
609+
if (this.activeLineController) {
610+
this.activeLineController.dispose()
611+
}
612+
602613
await this.closeAllDiffViews()
603614
this.editType = undefined
604615
this.isEditing = false
@@ -611,4 +622,25 @@ export class DiffViewProvider {
611622
this.streamedLines = []
612623
this.preDiagnostics = []
613624
}
625+
626+
private isEditorValid(editor: vscode.TextEditor): boolean {
627+
// Check if the editor is still valid by verifying it exists in visible editors
628+
// and its document hasn't been closed
629+
try {
630+
// In test environments, visibleTextEditors might be empty, so also check if document exists
631+
const isInVisibleEditors = vscode.window.visibleTextEditors.includes(editor)
632+
const hasValidDocument = editor.document && !editor.document.isClosed
633+
634+
// If we're in a test environment (no visible editors), rely on document validity
635+
// Otherwise, check both conditions
636+
if (vscode.window.visibleTextEditors.length === 0) {
637+
return hasValidDocument
638+
}
639+
640+
return isInVisibleEditors && hasValidDocument
641+
} catch {
642+
// If accessing editor properties throws, it's disposed
643+
return false
644+
}
645+
}
614646
}

0 commit comments

Comments
 (0)