diff --git a/build/win32/code.iss b/build/win32/code.iss index d180ca4ae9b..cad2e27d65e 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -92,7 +92,7 @@ Filename: "{app}\{#ExeBasename}.exe"; Description: "{cm:LaunchProgram,{#NameLong #if "user" == InstallTarget #define SoftwareClassesRootKey "HKCU" #else -#define SoftwareClassesRootKey "HKCR" +#define SoftwareClassesRootKey "HKLM" #endif Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ascx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index fe7be1d3a35..2eb053fbdc0 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -13,7 +13,7 @@ import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes'; import { Event } from 'vs/base/common/event'; import { addClass, EventType, EventHelper, EventLike } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { $ } from 'vs/base/browser/builder'; +import { $, Builder } from 'vs/base/browser/builder'; export interface IMenuOptions { context?: any; @@ -152,6 +152,7 @@ class MenuActionItem extends ActionItem { class SubmenuActionItem extends MenuActionItem { private mysubmenu: Menu; + private submenuContainer: Builder; private mouseOver: boolean; constructor( @@ -224,18 +225,23 @@ class SubmenuActionItem extends MenuActionItem { if (this.parentData.submenu && (force || (this.parentData.submenu !== this.mysubmenu))) { this.parentData.submenu.dispose(); this.parentData.submenu = null; + + if (this.submenuContainer) { + this.submenuContainer.dispose(); + this.submenuContainer = null; + } } } private createSubmenu() { if (!this.parentData.submenu) { - const submenuContainer = $(this.builder).div({ class: 'monaco-submenu menubar-menu-items-holder context-view' }); + this.submenuContainer = $(this.builder).div({ class: 'monaco-submenu menubar-menu-items-holder context-view' }); - $(submenuContainer).style({ + $(this.submenuContainer).style({ 'left': `${$(this.builder).getClientArea().width}px` }); - $(submenuContainer).on(EventType.KEY_UP, (e) => { + $(this.submenuContainer).on(EventType.KEY_UP, (e) => { let event = new StandardKeyboardEvent(e as KeyboardEvent); if (event.equals(KeyCode.LeftArrow)) { EventHelper.stop(e, true); @@ -243,10 +249,13 @@ class SubmenuActionItem extends MenuActionItem { this.parentData.parent.focus(); this.parentData.submenu.dispose(); this.parentData.submenu = null; + + this.submenuContainer.dispose(); + this.submenuContainer = null; } }); - $(submenuContainer).on(EventType.KEY_DOWN, (e) => { + $(this.submenuContainer).on(EventType.KEY_DOWN, (e) => { let event = new StandardKeyboardEvent(e as KeyboardEvent); if (event.equals(KeyCode.LeftArrow)) { EventHelper.stop(e, true); @@ -254,7 +263,7 @@ class SubmenuActionItem extends MenuActionItem { }); - this.parentData.submenu = new Menu(submenuContainer.getHTMLElement(), this.submenuActions, this.submenuOptions); + this.parentData.submenu = new Menu(this.submenuContainer.getHTMLElement(), this.submenuActions, this.submenuOptions); this.parentData.submenu.focus(); this.mysubmenu = this.parentData.submenu; @@ -268,5 +277,10 @@ class SubmenuActionItem extends MenuActionItem { this.mysubmenu.dispose(); this.mysubmenu = null; } + + if (this.submenuContainer) { + this.submenuContainer.dispose(); + this.submenuContainer = null; + } } } \ No newline at end of file diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index 12a15db8b3c..0ec12af97a1 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -23,7 +23,7 @@ export interface IQuickNavigateConfiguration { keybindings: ResolvedKeybinding[]; } -export interface IPickOptions { +export interface IPickOptions { /** * an optional string to show as place holder in the input box to guide the user what she picks on @@ -49,6 +49,8 @@ export interface IPickOptions { * an optional flag to make this picker multi-select */ canPickMany?: boolean; + + onDidFocus?: (entry: T) => void; } export interface IInputOptions { @@ -177,7 +179,7 @@ export interface IQuickInputService { /** * Opens the quick input box for selecting items and returns a promise with the user selected item(s) if any. */ - pick(picks: TPromise, options?: O, token?: CancellationToken): TPromise; + pick>(picks: TPromise, options?: O, token?: CancellationToken): TPromise; /** * Opens the quick input box for text input and returns a promise with the user typed value if any. diff --git a/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts b/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts index 5afe23566d3..7435dda07b6 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts @@ -38,7 +38,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { public dispose(): void { } - $show(options: IPickOptions): TPromise { + $show(options: IPickOptions): TPromise { const myToken = ++this._token; this._contents = new TPromise((c, e) => { @@ -55,16 +55,21 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { }; }); + options = { + ...options, + onDidFocus: el => { + if (el) { + this._proxy.$onItemSelected((el).handle); + } + } + }; + if (options.canPickMany) { return asWinJsPromise(token => this._quickInputService.pick(this._contents, options as { canPickMany: true }, token)).then(items => { if (items) { return items.map(item => item.handle); } return undefined; - }, undefined, progress => { - if (progress) { - this._proxy.$onItemSelected((progress).handle); - } }); } else { return asWinJsPromise(token => this._quickInputService.pick(this._contents, options, token)).then(item => { @@ -72,10 +77,6 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { return item.handle; } return undefined; - }, undefined, progress => { - if (progress) { - this._proxy.$onItemSelected((progress).handle); - } }); } } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 053a2e768b6..c124332150f 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -402,7 +402,7 @@ export interface TransferInputBox extends BaseTransferQuickInput { } export interface MainThreadQuickOpenShape extends IDisposable { - $show(options: IPickOptions): TPromise; + $show(options: IPickOptions): TPromise; $setItems(items: TransferQuickPickItems[]): TPromise; $setError(error: Error): TPromise; $input(options: vscode.InputBoxOptions, validateInput: boolean): TPromise; diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 6979382b238..01fe21c6a4c 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -219,7 +219,7 @@ export class WorkbenchLayout extends Disposable implements IVerticalSashLayoutPr } @memoize - private get partLayoutInfo() { + public get partLayoutInfo() { return { titlebar: { height: TITLE_BAR_HEIGHT diff --git a/src/vs/workbench/browser/parts/menubar/media/menubarpart.css b/src/vs/workbench/browser/parts/menubar/media/menubarpart.css index 3f1a72cca49..62b8c849063 100644 --- a/src/vs/workbench/browser/parts/menubar/media/menubarpart.css +++ b/src/vs/workbench/browser/parts/menubar/media/menubarpart.css @@ -8,7 +8,7 @@ position: absolute; font-size: 12px; box-sizing: border-box; - padding-left: 30px; + padding-left: 35px; padding-right: 138px; height: 30px; } @@ -25,7 +25,7 @@ flex-shrink: 0; align-items: center; box-sizing: border-box; - padding: 0px 5px; + padding: 0px 8px; position: relative; cursor: default; -webkit-app-region: no-drag; diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index c790e200b18..b56ee969d43 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -891,8 +891,8 @@ export class QuickInputService extends Component implements IQuickInputService { this.updateStyles(); } - pick(picks: TPromise, options: O = {}, token: CancellationToken = CancellationToken.None): TPromise { - return new TPromise((resolve, reject, progress) => { + pick>(picks: TPromise, options: O = {}, token: CancellationToken = CancellationToken.None): TPromise { + return new TPromise((resolve, reject) => { if (token.isCancellationRequested) { resolve(undefined); return; @@ -914,8 +914,8 @@ export class QuickInputService extends Component implements IQuickInputService { }), input.onDidChangeActive(items => { const focused = items[0]; - if (focused) { - progress(focused); + if (focused && options.onDidFocus) { + options.onDidFocus(focused); } }), input.onDidChangeSelection(items => { diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index 178903e64f2..e944fda2f69 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -181,13 +181,13 @@ export class QuickOpenController extends Component implements IQuickOpenService this.pickOpenWidget.hide(HideReason.CANCELED); } - return new TPromise((resolve, reject, progress) => { + return new TPromise((resolve, reject) => { function onItem(item: IPickOpenEntry): string | IPickOpenEntry { return item && isAboutStrings ? item.label : item; } - this.doPick(entryPromise, options, token).then(item => resolve(onItem(item)), err => reject(err), item => progress(onItem(item))); + this.doPick(entryPromise, options, token).then(item => resolve(onItem(item)), err => reject(err)); }); } diff --git a/src/vs/workbench/electron-browser/media/shell.css b/src/vs/workbench/electron-browser/media/shell.css index 3b5709bfcac..64869f0de97 100644 --- a/src/vs/workbench/electron-browser/media/shell.css +++ b/src/vs/workbench/electron-browser/media/shell.css @@ -69,11 +69,16 @@ padding: .5em 0; } -.monaco-shell .monaco-menu .monaco-action-bar.vertical .action-label, +.monaco-shell .monaco-menu .monaco-action-bar.vertical .action-label:not(.separator), .monaco-shell .monaco-menu .monaco-action-bar.vertical .keybinding { padding: 0.5em 2em; } +.monaco-shell .monaco-menu .monaco-action-bar.vertical .action-label.separator { + padding: 0.2em 0 0 0; + margin-bottom: 0.2em; +} + .monaco-shell .monaco-menu .monaco-action-bar.vertical .submenu-indicator { padding: 0.5em 1em; } diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 4dc1424ef1c..7191ec03a8c 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -1205,7 +1205,7 @@ export class Workbench extends Disposable implements IPartService { getTitleBarOffset(): number { let offset = 0; if (this.isVisible(Parts.TITLEBAR_PART)) { - offset = this.getContainer(Parts.TITLEBAR_PART).getBoundingClientRect().height; + offset = this.workbenchLayout.partLayoutInfo.titlebar.height; } return offset; diff --git a/src/vs/workbench/parts/debug/browser/breakpointsView.ts b/src/vs/workbench/parts/debug/browser/breakpointsView.ts index ba6e7cb7b92..77f2cd56085 100644 --- a/src/vs/workbench/parts/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/parts/debug/browser/breakpointsView.ts @@ -35,7 +35,7 @@ import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; const $ = dom.$; @@ -58,7 +58,7 @@ export class BreakpointsView extends ViewletPanel { @IContextViewService private contextViewService: IContextViewService, @IConfigurationService configurationService: IConfigurationService ) { - super(options, keybindingService, contextMenuService, configurationService); + super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService); this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize(); this.settings = options.viewletSettings; diff --git a/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts b/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts index c1b3079a4c2..5a517047652 100644 --- a/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts @@ -50,7 +50,7 @@ export class WatchExpressionsView extends TreeViewsViewletPanel { @IInstantiationService private instantiationService: IInstantiationService, @IConfigurationService configurationService: IConfigurationService ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('expressionsSection', "Expressions Section") }, keybindingService, contextMenuService, configurationService); + super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService); this.settings = options.viewletSettings; this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => { diff --git a/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts index d18cd6f8988..8b16509b50c 100644 --- a/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts @@ -10,7 +10,7 @@ import URI from 'vs/base/common/uri'; import * as paths from 'vs/base/common/paths'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -27,11 +27,14 @@ import { IWindowService } from 'vs/platform/windows/common/windows'; import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/parts/files/common/files'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; +import { ResourceQueue } from 'vs/base/common/async'; +import { onUnexpectedError } from 'vs/base/common/errors'; export class FileEditorTracker extends Disposable implements IWorkbenchContribution { protected closeOnFileDelete: boolean; + private modelLoadQueue: ResourceQueue; private activeOutOfWorkspaceWatchers: ResourceMap; constructor( @@ -47,6 +50,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut ) { super(); + this.modelLoadQueue = new ResourceQueue(); this.activeOutOfWorkspaceWatchers = new ResourceMap(); this.onConfigurationUpdated(configurationService.getValue()); @@ -97,7 +101,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut }) .filter(model => model && !model.isDirty()), m => m.getResource().toString() - ).forEach(model => this.textFileService.models.reload(model)); + ).forEach(model => this.queueModelLoad(model)); } } @@ -300,7 +304,18 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut distinct([...e.getUpdated(), ...e.getAdded()] .map(u => this.textFileService.models.get(u.resource)) .filter(model => model && !model.isDirty()), m => m.getResource().toString()) - .forEach(model => this.textFileService.models.reload(model)); + .forEach(model => this.queueModelLoad(model)); + } + + private queueModelLoad(model: ITextFileEditorModel): void { + + // Load model to update (use a queue to prevent accumulation of loads + // when the load actually takes long. At most we only want the queue + // to have a size of 2 (1 running load and 1 queued load). + const queue = this.modelLoadQueue.queueFor(model.getResource()); + if (queue.size <= 1) { + queue.queue(() => model.load().then(null, onUnexpectedError)); + } } private handleUpdatesToVisibleBinaryEditors(e: FileChangesEvent): void { diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts index b11e518b965..3dbfb49213a 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts @@ -9,7 +9,7 @@ import * as path from 'path'; import * as platform from 'vs/base/common/platform'; import * as watcher from 'vs/workbench/services/files/node/watcher/common'; import * as nsfw from 'vscode-nsfw'; -import { IWatcherService, IWatcherRequest } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; +import { IWatcherService, IWatcherRequest, IWatcherOptions } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; import { TPromise, ProgressCallback, TValueCallback, ErrorCallback } from 'vs/base/common/winjs.base'; import { ThrottledDelayer } from 'vs/base/common/async'; import { FileChangeType } from 'vs/platform/files/common/files'; @@ -41,7 +41,7 @@ export class NsfwWatcherService implements IWatcherService { private _verboseLogging: boolean; private enospcErrorLogged: boolean; - public initialize(verboseLogging: boolean): TPromise { + public initialize(options: IWatcherOptions): TPromise { this._verboseLogging = true; this._watcherPromise = new TPromise((c, e, p) => { this._errorCallback = e; diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts index 7e571c74ea4..efad426ca5a 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts @@ -12,7 +12,11 @@ export interface IWatcherRequest { ignored: string[]; } +export interface IWatcherOptions { + verboseLogging: boolean; +} + export interface IWatcherService { - initialize(verboseLogging: boolean): TPromise; + initialize(options: IWatcherOptions): TPromise; setRoots(roots: IWatcherRequest[]): TPromise; } \ No newline at end of file diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts index 849173e7c70..844a9e65e82 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts @@ -7,7 +7,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IWatcherRequest, IWatcherService } from './watcher'; +import { IWatcherRequest, IWatcherService, IWatcherOptions } from './watcher'; import { Event } from 'vs/base/common/event'; export interface IWatcherChannel extends IChannel { @@ -37,8 +37,8 @@ export class WatcherChannelClient implements IWatcherService { constructor(private channel: IWatcherChannel) { } - initialize(verboseLogging: boolean): TPromise { - return this.channel.call('initialize', verboseLogging); + initialize(options: IWatcherOptions): TPromise { + return this.channel.call('initialize', options); } setRoots(roots: IWatcherRequest[]): TPromise { diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts index e5403578de2..d758ddadfc6 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts @@ -55,10 +55,14 @@ export class FileWatcher { ); this.toDispose.push(client); + const options = { + verboseLogging: this.verboseLogging + }; + // Initialize watcher const channel = getNextTickChannel(client.getChannel('watcher')); this.service = new WatcherChannelClient(channel); - this.service.initialize(this.verboseLogging).then(null, err => { + this.service.initialize(options).then(null, err => { if (!this.isDisposed && !isPromiseCanceledError(err)) { return TPromise.wrapError(err); // the service lib uses the promise cancel error to indicate the process died, we do not want to bubble this up } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index cf8b858ec15..256b69426a8 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -319,42 +319,55 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil etag = this.lastResolvedDiskStat.etag; // otherwise respect etag to support caching } + // Ensure to track the versionId before doing a long running operation + // to make sure the model was not changed in the meantime which would + // indicate that the user or program has made edits. If we would ignore + // this, we could potentially loose the changes that were made because + // after resolving the content we update the model and reset the dirty + // flag. + const currentVersionId = this.versionId; + // Resolve Content return this.textFileService .resolveTextContent(this.resource, { acceptTextOnly: !allowBinary, etag, encoding: this.preferredEncoding }) - .then(content => this.handleLoadSuccess(content), error => this.handleLoadError(error)); - } + .then(content => { - private handleLoadSuccess(content: IRawTextContent): TPromise { + // Clear orphaned state when loading was successful + this.setOrphaned(false); - // Clear orphaned state when load was successful - this.setOrphaned(false); + // Guard against the model having changed in the meantime + if (currentVersionId === this.versionId) { + return this.loadWithContent(content); + } - return this.loadWithContent(content); - } + return this; + }, error => { + const result = error.fileOperationResult; - private handleLoadError(error: FileOperationError): TPromise { - const result = error.fileOperationResult; + // Apply orphaned state based on error code + this.setOrphaned(result === FileOperationResult.FILE_NOT_FOUND); - // Apply orphaned state based on error code - this.setOrphaned(result === FileOperationResult.FILE_NOT_FOUND); + // NotModified status is expected and can be handled gracefully + if (result === FileOperationResult.FILE_NOT_MODIFIED_SINCE) { - // NotModified status is expected and can be handled gracefully - if (result === FileOperationResult.FILE_NOT_MODIFIED_SINCE) { - this.setDirty(false); // Ensure we are not tracking a stale state + // Guard against the model having changed in the meantime + if (currentVersionId === this.versionId) { + this.setDirty(false); // Ensure we are not tracking a stale state + } - return TPromise.as(this); - } + return TPromise.as(this); + } - // Ignore when a model has been resolved once and the file was deleted meanwhile. Since - // we already have the model loaded, we can return to this state and update the orphaned - // flag to indicate that this model has no version on disk anymore. - if (this.isResolved() && result === FileOperationResult.FILE_NOT_FOUND) { - return TPromise.as(this); - } + // Ignore when a model has been resolved once and the file was deleted meanwhile. Since + // we already have the model loaded, we can return to this state and update the orphaned + // flag to indicate that this model has no version on disk anymore. + if (this.isResolved() && result === FileOperationResult.FILE_NOT_FOUND) { + return TPromise.as(this); + } - // Otherwise bubble up the error - return TPromise.wrapError(error); + // Otherwise bubble up the error + return TPromise.wrapError(error); + }); } private loadWithContent(content: IRawTextContent, backup?: URI): TPromise { @@ -385,7 +398,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil diag('load() - resolved content', this.resource, new Date()); // Update our resolved disk stat model - const resolvedStat: IFileStat = { + this.updateLastResolvedDiskStat({ resource: this.resource, name: content.name, mtime: content.mtime, @@ -394,8 +407,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil isSymbolicLink: false, children: void 0, isReadonly: content.isReadonly - }; - this.updateLastResolvedDiskStat(resolvedStat); + } as IFileStat); // Keep the original encoding to not loose it when saving const oldEncoding = this.contentEncoding; diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 2836c9adcce..32e0164ecae 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -14,7 +14,6 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResourceMap } from 'vs/base/common/map'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { ResourceQueue } from 'vs/base/common/async'; export class TextFileEditorModelManager extends Disposable implements ITextFileEditorModelManager { @@ -53,8 +52,6 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE private mapResourceToModel: ResourceMap; private mapResourceToPendingModelLoaders: ResourceMap>; - private modelReloadQueue: ResourceQueue = new ResourceQueue(); - constructor( @ILifecycleService private lifecycleService: ILifecycleService, @IInstantiationService private instantiationService: IInstantiationService @@ -150,7 +147,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // async reload: trigger a reload but return immediately if (options.reload.async) { modelPromise = TPromise.as(model); - this.reload(model); + model.load(options).then(null, onUnexpectedError); } // sync reload: do not return until model reloaded @@ -227,17 +224,6 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE }); } - reload(model: ITextFileEditorModel): void { - - // Load model to reload (use a queue to prevent accumulation of loads - // when the load actually takes long. At most we only want the queue - // to have a size of 2 (1 running load and 1 queued load). - const queue = this.modelReloadQueue.queueFor(model.getResource()); - if (queue.size <= 1) { - queue.queue(() => model.load().then(null, onUnexpectedError)); - } - } - getAll(resource?: URI, filter?: (model: ITextFileEditorModel) => boolean): ITextFileEditorModel[] { if (resource) { const res = this.mapResourceToModel.get(resource); diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index a2f5beedbb1..716b6c1eb36 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -186,7 +186,6 @@ export interface ITextFileEditorModelManager { getAll(resource?: URI): ITextFileEditorModel[]; loadOrCreate(resource: URI, options?: IModelLoadOrCreateOptions): TPromise; - reload(model: ITextFileEditorModel): void; disposeModel(model: ITextFileEditorModel): void; }