Change:
Issue Type: <bBug</bChanges to the file by external sources (extensions/manual edit) are not picked up anymore until VSCode is relaunched. For instance, if you use notepad to add a workspace folder to the file, VSCode won't show the new workspace folder until the workspace is relaunched in VSCode. This also means that if you then add another workspace folder through VSCode, and save the workspace, the workspace folder with notepad is removed. Using the button doesn't trigger a re-read of the file either. This renders the extension rather useless. VS Code version: Code 1.31.0 (, 2019-02-05T22:35:56.624Z) OS version: Windows_NT x64 10.0. Does this issue occur when all extensions are disabled?: Yes Steps to Reproduce: a new workspace 2 folders to the workspace, called and the workspace and save the file, changing the order of the folders using notepad or the extension that the order in VSCode has not been changed and reopen VSCode, notice that the order in VSCode did change
(Experimental duplicate detection) Thanks for submitting this issue. Please also check if it is already covered by an existing one, like: <!-- score: 0.51 --- <!-- score: 0.471 --- <!-- score: 0.46 --<!-- potentialduplicatescomment --
I was not able to reproduce this on macOS at least.
In the screenshot below you can see that VSCode shows a different order of the folders than the file. !
Can you reproduce this with latest vscode insiders?
Yes, I can. The screenshot below shows the difference in the order of the folders between the content of the file within the latest insiders, in this case there are also no extensions active. Changes are not shown in VSCode until it is restarted. !
I can reproduce this also on the mac. The issue here is that the does not fire a event when the order of workspace roots is changed in the code-workspace file. Since the order changed I would expect this event to arrive. Thus forwarding to and
could be the case of a missing file event (given I could not reproduce when I tried on macOS), if so, please forward back to me. did you check if the file event made it through? --verbose
The file event does not seem to go through. if I start out of source with --verbose flag.
I assume you setup custom file watching for the workspace file right? Where is that code? Does it check for changes?
I think this would have caused by my changes moving away from node based file watching to file service watch. Will investigate further and figure it out.
Thanks for the fix, I just tested it in the new insiders build and everything seems to be working correctly. I am looking forward to the next release!

Discussion:
import * as glob from 'vs/base/common/glob'; import { isLinux } from 'vs/base/common/platform'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; <del> import { Event } from 'vs/base/common/event'; </del> <ins> import { Event, Emitter } from 'vs/base/common/event'; </ins> import { startsWithIgnoreCase } from 'vs/base/common/strings'; <del> import { IDisposable } from 'vs/base/common/lifecycle'; </del> <ins> import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; </ins> import { isEqualOrParent, isEqual } from 'vs/base/common/resources'; import { isUndefinedOrNull } from 'vs/base/common/types'; <ins> import { ThrottledDelayer } from 'vs/base/common/async'; </ins> export const IFileService = createDecorator<IFileService>('fileService'); <ins> export class FileListener extends Disposable { private readonly _onDidContentChange = new Emitter<IFileStat>(); readonly onDidContentChange: Event<IFileStat> = this._onDidContentChange.event; private watching: boolean = false; private delayer: ThrottledDelayer<void>; private etag: string | undefined; constructor( private readonly file: URI, private readonly fileService: IFileService ) { super(); this.delayer = new ThrottledDelayer<void>(500); } watch(eTag?: string): void { if (!this.watching) { this.etag = eTag; this.poll(); this.watching = true; } } private poll(): void { const loop = () => this.doWatch().then(() => this.poll()); this.delayer.trigger(loop); } private doWatch(): Promise<void> { return this.fileService.resolveFile(this.file) .then(stat => { if (stat.etag !== this.etag) { this.etag = stat.etag; this._onDidContentChange.fire(stat); } }); } unwatch(): void { if (this.watching) { this.delayer.cancel(); this.watching = false; } } dispose(): void { this.unwatch(); super.dispose(); } } </ins> export interface IResourceEncodings { getWriteEncoding(resource: URI, preferredEncoding?: string): string; }

import { RunOnceScheduler, ThrottledDelayer } from 'vs/base/common/async'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; <del> import { IFileService } from 'vs/platform/files/common/files'; </del> <ins> import { IFileService, FileListener } from 'vs/platform/files/common/files'; </ins> import { IPanel } from 'vs/workbench/common/panel'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { IEnvironmentService } from 'vs/platform/environment/common/environment';

} } <del> class OutputFileListener extends Disposable { private readonly _onDidContentChange = new Emitter<number>(); readonly onDidContentChange: Event<number> = this._onDidContentChange.event; private watching: boolean = false; private syncDelayer: ThrottledDelayer<void>; private etag: string; constructor( private readonly file: URI, private readonly fileService: IFileService ) { super(); this.syncDelayer = new ThrottledDelayer<void>(500); } watch(eTag: string): void { if (!this.watching) { this.etag = eTag; this.poll(); this.watching = true; } } private poll(): void { const loop = () => this.doWatch().then(() => this.poll()); this.syncDelayer.trigger(loop); } private doWatch(): Promise<void> { return this.fileService.resolveFile(this.file) .then(stat => { if (stat.etag !== this.etag) { this.etag = stat.etag; this._onDidContentChange.fire(stat.size); } }); } unwatch(): void { if (this.watching) { this.syncDelayer.cancel(); this.watching = false; } } dispose(): void { this.unwatch(); super.dispose(); } } </del> /** * An output channel driven by a file and does not support appending messages. */ class FileOutputChannel extends AbstractFileOutputChannel implements OutputChannel { <del> private readonly fileHandler: OutputFileListener; </del> <ins> private readonly fileHandler: FileListener; </ins> private updateInProgress: boolean = false; private etag: string = '';

) { super(outputChannelDescriptor, modelUri, fileService, modelService, modeService); <del> this.fileHandler = this._register(new OutputFileListener(this.file, this.fileService)); this._register(this.fileHandler.onDidContentChange(size => this.update(size))); </del> <ins> this.fileHandler = this._register(new FileListener(this.file, this.fileService)); this._register(this.fileHandler.onDidContentChange(({ size }) => this.update(size))); </ins> this._register(toDisposable(() => this.fileHandler.unwatch())); }

import * as pfs from 'vs/base/node/pfs'; import * as errors from 'vs/base/common/errors'; import * as collections from 'vs/base/common/collections'; <del> import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; </del> <ins> import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; </ins> import { RunOnceScheduler, Delayer } from 'vs/base/common/async'; <del> import { FileChangeType, FileChangesEvent, IContent, IFileService } from 'vs/platform/files/common/files'; </del> <ins> import { FileChangeType, FileChangesEvent, IContent, IFileService, FileListener } from 'vs/platform/files/common/files'; </ins> import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, FolderSettingsModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration';

private workspaceConfig: URI | null = null; private readonly reloadConfigurationScheduler: RunOnceScheduler; <ins> private fileListener: FileListener | null; private fileListenerDisposables: IDisposable[] = []; </ins> constructor(private fileService: IFileService, from?: AbstractWorkspaceConfiguration) { super(from); this.workspaceConfig = from && from.workspaceIdentifier ? from.workspaceIdentifier.configPath : null; <del> this._register(fileService.onFileChanges(e => this.handleWorkspaceFileEvents(e))); </del> this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50)); <ins> this.listenToWorkspaceConfigurationFile(); this._register(toDisposable(() => dispose(this.fileListenerDisposables))); </ins> } protected loadWorkspaceConfigurationContents(workspaceIdentifier: IWorkspaceIdentifier): Promise<string> { <del> this.workspaceConfig = workspaceIdentifier.configPath; </del> <ins> if (!(this.workspaceConfig && resources.isEqual(this.workspaceConfig, workspaceIdentifier.configPath))) { this.workspaceConfig = workspaceIdentifier.configPath; this.listenToWorkspaceConfigurationFile(); } </ins> return this.fileService.resolveContent(this.workspaceConfig) .then(content => content.value, e => { errors.onUnexpectedError(e);

}); } <del> private handleWorkspaceFileEvents(event: FileChangesEvent): void { </del> <ins> private listenToWorkspaceConfigurationFile(): void { if (this.fileListener) { this.fileListenerDisposables = dispose(this.fileListenerDisposables); this.fileListener = null; } </ins> if (this.workspaceConfig) { <del> const events = event.changes; let affectedByChanges = false; // Find changes that affect workspace file for (let i = 0, len = events.length; i < len && !affectedByChanges; i++) { affectedByChanges = resources.isEqual(this.workspaceConfig, events[i].resource); } if (affectedByChanges) { this.reloadConfigurationScheduler.schedule(); } </del> <ins> this.fileListener = new FileListener(this.workspaceConfig, this.fileService); this.fileListenerDisposables.push(this.fileListener); this.fileListener.watch(); this.fileListener.onDidContentChange(() => this.reloadConfigurationScheduler.schedule(), this, this.fileListenerDisposables); </ins> } } }