From 2a8dafd9e9e0f536088af53fb340d027961af3db Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Thu, 8 Jun 2017 00:54:46 -0400 Subject: [PATCH] Fixes #83 - Close Unchanged Files command can infinitely loop --- src/activeEditorTracker.ts | 15 +++++---- src/commands/closeUnchangedFiles.ts | 50 +++++++++++++++++------------ src/constants.ts | 3 +- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/activeEditorTracker.ts b/src/activeEditorTracker.ts index daaeaf1..6d9297b 100644 --- a/src/activeEditorTracker.ts +++ b/src/activeEditorTracker.ts @@ -1,5 +1,6 @@ 'use strict'; +import { Functions } from './system'; import { commands, Disposable, TextEditor, window } from 'vscode'; import { BuiltInCommands } from './constants'; @@ -11,19 +12,20 @@ export class ActiveEditorTracker extends Disposable { constructor() { super(() => this.dispose()); - this._disposable = window.onDidChangeActiveTextEditor(e => this._resolver && this._resolver(e)); + const fn = Functions.debounce((e: TextEditor) => this._resolver && this._resolver(e), 50); + this._disposable = window.onDidChangeActiveTextEditor(fn); } dispose() { this._disposable && this._disposable.dispose(); } - async awaitClose(timeout: number = 500): Promise { + async awaitClose(timeout: number = 500): Promise { this.close(); return this.wait(timeout); } - async awaitNext(timeout: number = 500): Promise { + async awaitNext(timeout: number = 500): Promise { this.next(); return this.wait(timeout); } @@ -36,15 +38,15 @@ export class ActiveEditorTracker extends Disposable { return commands.executeCommand(BuiltInCommands.NextEditor); } - async wait(timeout: number = 500): Promise { + async wait(timeout: number = 500): Promise { const editor = await new Promise((resolve, reject) => { let timer: any; - this._resolver = (editor: TextEditor) => { + this._resolver = (e: TextEditor) => { if (timer) { clearTimeout(timer as any); timer = 0; - resolve(editor); + resolve(e); } }; @@ -53,6 +55,7 @@ export class ActiveEditorTracker extends Disposable { timer = 0; }, timeout) as any; }); + this._resolver = undefined; return editor; } diff --git a/src/commands/closeUnchangedFiles.ts b/src/commands/closeUnchangedFiles.ts index c94eace..ed69ff7 100644 --- a/src/commands/closeUnchangedFiles.ts +++ b/src/commands/closeUnchangedFiles.ts @@ -1,8 +1,9 @@ 'use strict'; -import { TextEditor, Uri, window } from 'vscode'; +import { commands, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorTracker } from '../activeEditorTracker'; import { ActiveEditorCommand, Commands, getCommandUri } from './common'; import { TextEditorComparer, UriComparer } from '../comparers'; +import { BuiltInCommands } from '../constants'; import { GitService } from '../gitService'; import { Logger } from '../logger'; @@ -30,34 +31,41 @@ export class CloseUnchangedFilesCommand extends ActiveEditorCommand { args.uris = status.files.map(_ => _.Uri); } + if (args.uris.length === 0) return commands.executeCommand(BuiltInCommands.CloseAllEditors); + const editorTracker = new ActiveEditorTracker(); - let active = window.activeTextEditor; - let editor = active; - do { + let count = 0; + let previous = undefined; + let editor = window.activeTextEditor; + while (true) { if (editor !== undefined) { - if ((editor.document !== undefined && editor.document.isDirty) || - args.uris.some(_ => UriComparer.equals(_, editor!.document && editor!.document.uri))) { - // If we didn't start with a valid editor, set one once we find it - if (active === undefined) { - active = editor; - } - editor = await editorTracker.awaitNext(500); + if (TextEditorComparer.equals(previous, editor, { useId: true, usePosition: true })) { + break; } - else { - if (active === editor) { - active = undefined; - } - editor = await editorTracker.awaitClose(500); + + if (editor.document !== undefined && + (editor.document.isDirty || args.uris.some(_ => UriComparer.equals(_, editor!.document && editor!.document.uri)))) { + previous = editor; + editor = await editorTracker.awaitNext(500); + continue; + } + } + + previous = editor; + editor = await editorTracker.awaitClose(500); + + if (previous === undefined && editor === undefined) { + count++; + // This is such a shitty hack, but I can't figure out any other reliable way to know that we've cycled through all the editors :( + if (count >= 4) { + break; } } else { - if (active === editor) { - active = undefined; - } - editor = await editorTracker.awaitClose(500); + count = 0; } - } while ((active === undefined && editor === undefined) || !TextEditorComparer.equals(active, editor, { useId: true, usePosition: true })); + } editorTracker.dispose(); diff --git a/src/constants.ts b/src/constants.ts index c467dc5..e106098 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -7,9 +7,10 @@ export const QualifiedExtensionId = `eamodio.${ExtensionId}`; export const ApplicationInsightsKey = 'a9c302f8-6483-4d01-b92c-c159c799c679'; -export type BuiltInCommands = 'cursorMove' | 'editor.action.showReferences' | 'editor.action.toggleRenderWhitespace' | 'editorScroll' | 'revealLine' | 'setContext' | 'vscode.diff' | 'vscode.executeDocumentSymbolProvider' | 'vscode.executeCodeLensProvider' | 'vscode.open' | 'vscode.previewHtml' | 'workbench.action.closeActiveEditor' | 'workbench.action.nextEditor'; +export type BuiltInCommands = 'cursorMove' | 'editor.action.showReferences' | 'editor.action.toggleRenderWhitespace' | 'editorScroll' | 'revealLine' | 'setContext' | 'vscode.diff' | 'vscode.executeDocumentSymbolProvider' | 'vscode.executeCodeLensProvider' | 'vscode.open' | 'vscode.previewHtml' | 'workbench.action.closeActiveEditor' | 'workbench.action.closeAllEditors' | 'workbench.action.nextEditor'; export const BuiltInCommands = { CloseActiveEditor: 'workbench.action.closeActiveEditor' as BuiltInCommands, + CloseAllEditors: 'workbench.action.closeAllEditors' as BuiltInCommands, CursorMove: 'cursorMove' as BuiltInCommands, Diff: 'vscode.diff' as BuiltInCommands, EditorScroll: 'editorScroll' as BuiltInCommands,