diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 170c4e62257..fb92cd661bb 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -16,7 +16,6 @@ import { IProcessEnvironment, IEnvService, EnvService } from 'vs/code/electron-m import { IWindowsService, WindowsManager } from 'vs/code/electron-main/windows'; import { ILifecycleService, LifecycleService } from 'vs/code/electron-main/lifecycle'; import { VSCodeMenu } from 'vs/code/electron-main/menus'; -import { ISettingsService, SettingsManager } from 'vs/code/electron-main/settings'; import { IUpdateService, UpdateManager } from 'vs/code/electron-main/update-manager'; import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/common/ipc.electron'; import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net'; @@ -70,7 +69,7 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: IProce const windowsService = accessor.get(IWindowsService); const lifecycleService = accessor.get(ILifecycleService); const updateService = accessor.get(IUpdateService); - const settingsService = accessor.get(ISettingsService); + const configurationService = accessor.get(IConfigurationService); // We handle uncaught exceptions here to prevent electron from opening a dialog to the user process.on('uncaughtException', (err: any) => { @@ -146,6 +145,8 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: IProce if (windowsMutex) { windowsMutex.release(); } + + configurationService.dispose(); } // Dispose on app quit @@ -166,8 +167,8 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: IProce // Lifecycle lifecycleService.ready(); - // Load settings - settingsService.loadSync(); + // Load settings (TODO@Ben remove) + global.globalSettingsValue = JSON.stringify(configurationService.getConfiguration()); // Propagate to clients windowsService.ready(userEnv); @@ -396,7 +397,6 @@ function start(): void { services.set(IConfigurationService, new SyncDescriptor(ConfigurationService)); services.set(IRequestService, new SyncDescriptor(RequestService)); services.set(IUpdateService, new SyncDescriptor(UpdateManager)); - services.set(ISettingsService, new SyncDescriptor(SettingsManager)); const instantiationService = new InstantiationService(services); diff --git a/src/vs/code/electron-main/settings.ts b/src/vs/code/electron-main/settings.ts deleted file mode 100644 index 4f298370338..00000000000 --- a/src/vs/code/electron-main/settings.ts +++ /dev/null @@ -1,218 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import { app } from 'electron'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as json from 'vs/base/common/json'; -import * as objects from 'vs/base/common/objects'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IEnvService } from 'vs/code/electron-main/env'; -import Event, {Emitter} from 'vs/base/common/event'; - -export const ISettingsService = createDecorator('settingsService'); - -export interface ISettings { - settings: any; - settingsParseErrors?: string[]; -} - -export interface ISettingsService { - _serviceBrand: any; - globalSettings: ISettings; - loadSync(): boolean; - getValue(key: string, fallback?: T): T; - onChange: Event; -} - -/** - * TODO@Joao TODO@Ben - this needs to die - * - * We need to have each participant (renderer, main, cli, shared) be able - * to listen on changes to each of these files, independently. - */ - -export class SettingsManager implements ISettingsService { - - _serviceBrand: any; - - private static CHANGE_BUFFER_DELAY = 300; - - globalSettings: ISettings; - - private timeoutHandle: number; - private watcher: fs.FSWatcher; - private appSettingsPath: string; - - private _onChange: Emitter; - - constructor(@IEnvService envService: IEnvService) { - this.appSettingsPath = envService.appSettingsPath; - this._onChange = new Emitter(); - - this.registerWatchers(); - app.on('will-quit', () => this.dispose()); - } - - loadSync(): boolean { - let settingsChanged = false; - - const loadedSettings = this.doLoadSync(); - if (!objects.equals(loadedSettings, this.globalSettings)) { - - // Keep in class - this.globalSettings = loadedSettings; - settingsChanged = true; // changed value - } - - // Store into global so that any renderer can access the value with remote.getGlobal() - if (settingsChanged) { - global.globalSettingsValue = JSON.stringify(this.globalSettings); - } - - return settingsChanged; - } - - get onChange(): Event { - return this._onChange.event; - } - - getValue(key: string, fallback?: any): any { - return this.doGetValue(key, fallback); - } - - private doGetValue(key: string, fallback?: any): any { - if (!key) { - return fallback; - } - - let value = this.globalSettings.settings; - - const parts = key.split('\.'); - while (parts.length && value) { - const part = parts.shift(); - value = value[part]; - } - - return typeof value !== 'undefined' ? value : fallback; - } - - private registerWatchers(): void { - const self = this; - function attachSettingsChangeWatcher(watchPath: string): void { - self.watcher = fs.watch(watchPath); - self.watcher.on('change', () => self.onSettingsFileChange()); - } - - // Attach a watcher to the settings directory - attachSettingsChangeWatcher(path.dirname(this.appSettingsPath)); - - // Follow symlinks and attach watchers if they resolve - fs.lstat(this.appSettingsPath, (err, stats) => { - if (err) { - return; - } - - if (stats.isSymbolicLink() && !stats.isDirectory()) { - fs.readlink(this.appSettingsPath, (err, realPath) => { - if (err) { - return; - } - - attachSettingsChangeWatcher(realPath); - }); - } - }); - } - - private onSettingsFileChange(): void { - - // we can get multiple change events for one change, so we buffer through a timeout - if (this.timeoutHandle) { - global.clearTimeout(this.timeoutHandle); - this.timeoutHandle = null; - } - - this.timeoutHandle = global.setTimeout(() => { - - // Reload - const didChange = this.loadSync(); - - // Emit event - if (didChange) { - this._onChange.fire(this.globalSettings); - } - - }, SettingsManager.CHANGE_BUFFER_DELAY); - } - - private doLoadSync(): ISettings { - const settings = this.doLoadSettingsSync(); - - return { - settings: settings.contents, - settingsParseErrors: settings.parseErrors - }; - } - - private doLoadSettingsSync(): { contents: any; parseErrors?: string[]; } { - const root = Object.create(null); - let content = '{}'; - try { - content = fs.readFileSync(this.appSettingsPath).toString(); - } catch (error) { - // ignore - } - - let contents = Object.create(null); - try { - contents = json.parse(content); - } catch (error) { - // parse problem - return { - contents: Object.create(null), - parseErrors: [this.appSettingsPath] - }; - } - - for (let key in contents) { - this.setNode(root, key, contents[key]); - } - - return { - contents: root - }; - } - - private setNode(root: any, key: string, value: any): any { - const segments = key.split('.'); - const last = segments.pop(); - - let curr = root; - segments.forEach((s) => { - let obj = curr[s]; - switch (typeof obj) { - case 'undefined': - obj = curr[s] = {}; - break; - case 'object': - break; - default: - console.log('Conflicting user settings: ' + key + ' at ' + s + ' with ' + JSON.stringify(obj)); - } - curr = obj; - }); - curr[last] = value; - } - - dispose(): void { - if (this.watcher) { - this.watcher.close(); - this.watcher = null; - } - } -} \ No newline at end of file diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 61c9b097a5e..776123a17f4 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -13,7 +13,7 @@ import { shell, screen, BrowserWindow } from 'electron'; import { TPromise, TValueCallback } from 'vs/base/common/winjs.base'; import { ICommandLineArguments, IEnvService, IProcessEnvironment } from 'vs/code/electron-main/env'; import { ILogService } from 'vs/code/electron-main/log'; -import { ISettingsService } from 'vs/code/electron-main/settings'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { parseArgs } from 'vs/code/node/argv'; export interface IWindowState { @@ -106,6 +106,13 @@ export interface IWindowConfiguration extends ICommandLineArguments { extensionsToInstall: string[]; } +export interface IWindowSettings { + openFilesInNewWindow: boolean; + reopenFolders: 'all' | 'one' | 'none'; + restoreFullscreen: boolean; + zoomLevel: number; +} + export class VSCodeWindow { public static menuBarHiddenKey = 'menuBarHidden'; @@ -133,7 +140,7 @@ export class VSCodeWindow { config: IWindowCreationOptions, @ILogService private logService: ILogService, @IEnvService private envService: IEnvService, - @ISettingsService private settingsService: ISettingsService, + @IConfigurationService private configurationService: IConfigurationService, @IStorageService private storageService: IStorageService ) { this.options = config; @@ -395,7 +402,8 @@ export class VSCodeWindow { let url = require.toUrl('vs/workbench/electron-browser/bootstrap/index.html'); // Set zoomlevel - const zoomLevel = this.settingsService.getValue('window.zoomLevel'); + const windowConfig = this.configurationService.getConfiguration('window'); + const zoomLevel = windowConfig && windowConfig.zoomLevel; if (typeof zoomLevel === 'number') { windowConfiguration.zoomLevel = zoomLevel; } diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 97e60bfe248..191694056b7 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -14,11 +14,11 @@ import * as arrays from 'vs/base/common/arrays'; import { assign, mixin } from 'vs/base/common/objects'; import { EventEmitter } from 'events'; import { IStorageService } from 'vs/code/electron-main/storage'; -import { IPath, VSCodeWindow, ReadyState, IWindowConfiguration, IWindowState as ISingleWindowState, defaultWindowState } from 'vs/code/electron-main/window'; +import { IPath, VSCodeWindow, ReadyState, IWindowConfiguration, IWindowState as ISingleWindowState, defaultWindowState, IWindowSettings } from 'vs/code/electron-main/window'; import { ipcMain as ipc, app, screen, crashReporter, BrowserWindow, dialog } from 'electron'; import { ICommandLineArguments, IProcessEnvironment, IEnvService, IParsedPath, parseLineAndColumnAware } from 'vs/code/electron-main/env'; import { ILifecycleService } from 'vs/code/electron-main/lifecycle'; -import { ISettingsService } from 'vs/code/electron-main/settings'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IUpdateService, IUpdate } from 'vs/code/electron-main/update-manager'; import { ILogService } from 'vs/code/electron-main/log'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -132,8 +132,8 @@ export class WindowsManager implements IWindowsService { @IEnvService private envService: IEnvService, @ILifecycleService private lifecycleService: ILifecycleService, @IUpdateService private updateService: IUpdateService, - @ISettingsService private settingsService: ISettingsService - ) { } + @IConfigurationService private configurationService: IConfigurationService + ) { } onOpen(clb: (path: IPath) => void): () => void { this.eventEmitter.addListener(EventTypes.OPEN, clb); @@ -193,8 +193,9 @@ export class WindowsManager implements IWindowsService { }, 100); }); - this.settingsService.onChange((newSettings) => { - this.sendToAll('vscode:optionsChange', JSON.stringify({ globalSettings: newSettings })); + this.configurationService.onDidUpdateConfiguration(event => { + global.globalSettingsValue = JSON.stringify(event.config); // Load settings (TODO@Ben remove) + this.sendToAll('vscode:optionsChange', JSON.stringify(event.config)); }, this); ipc.on('vscode:startCrashReporter', (event: any, config: any) => { @@ -516,7 +517,7 @@ export class WindowsManager implements IWindowsService { // Warn if the requested path to open does not exist if (!iPath) { - let options:Electron.ShowMessageBoxOptions = { + let options: Electron.ShowMessageBoxOptions = { title: this.envService.product.nameLong, type: 'info', buttons: [nls.localize('ok', "OK")], @@ -589,7 +590,10 @@ export class WindowsManager implements IWindowsService { } else { openFilesInNewWindow = openConfig.preferNewWindow; if (openFilesInNewWindow && !openConfig.cli.extensionDevelopmentPath) { // can be overriden via settings (not for PDE though!) - openFilesInNewWindow = this.settingsService.getValue('window.openFilesInNewWindow', openFilesInNewWindow); + const windowConfig = this.configurationService.getConfiguration('window'); + if (windowConfig && !windowConfig.openFilesInNewWindow) { + openFilesInNewWindow = false; // do not open in new window if user configured this explicitly + } } } @@ -831,7 +835,8 @@ export class WindowsManager implements IWindowsService { if (this.lifecycleService.wasUpdated) { reopenFolders = ReopenFoldersSetting.ALL; // always reopen all folders when an update was applied } else { - reopenFolders = this.settingsService.getValue('window.reopenFolders', ReopenFoldersSetting.ONE); + const windowConfig = this.configurationService.getConfiguration('window'); + reopenFolders = (windowConfig && windowConfig.reopenFolders) || ReopenFoldersSetting.ONE; } let lastActiveFolder = this.windowsState.lastActiveWindow && this.windowsState.lastActiveWindow.workspacePath; @@ -877,10 +882,12 @@ export class WindowsManager implements IWindowsService { // New window if (!vscodeWindow) { + const windowConfig = this.configurationService.getConfiguration('window'); + vscodeWindow = this.instantiationService.createInstance(VSCodeWindow, { state: this.getNewWindowState(configuration), extensionDevelopmentPath: configuration.extensionDevelopmentPath, - allowFullscreen: this.lifecycleService.wasUpdated || this.settingsService.getValue('window.restoreFullscreen', false) + allowFullscreen: this.lifecycleService.wasUpdated || (windowConfig && windowConfig.restoreFullscreen) }); WindowsManager.WINDOWS.push(vscodeWindow); @@ -1000,7 +1007,7 @@ export class WindowsManager implements IWindowsService { } public openFileFolderPicker(forceNewWindow?: boolean): void { - this.doPickAndOpen({ pickFolders: true, pickFiles: true , forceNewWindow}); + this.doPickAndOpen({ pickFolders: true, pickFiles: true, forceNewWindow }); } public openFilePicker(forceNewWindow?: boolean, path?: string): void { @@ -1020,7 +1027,7 @@ export class WindowsManager implements IWindowsService { } private getFileOrFolderPaths(options: INativeOpenDialogOptions, clb: (paths: string[]) => void): void { - let workingDir = options.path || this.storageService.getItem(WindowsManager.workingDirPickerStorageKey); + let workingDir = options.path ||  this.storageService.getItem(WindowsManager.workingDirPickerStorageKey); let focussedWindow = this.getFocusedWindow(); let pickerProperties: ('openFile' | 'openDirectory' | 'multiSelections' | 'createDirectory')[]; diff --git a/src/vs/test/utils/servicesTestUtils.ts b/src/vs/test/utils/servicesTestUtils.ts index bc488073732..01a86a97ecc 100644 --- a/src/vs/test/utils/servicesTestUtils.ts +++ b/src/vs/test/utils/servicesTestUtils.ts @@ -59,9 +59,7 @@ export class TestContextService implements WorkspaceContextService.IWorkspaceCon constructor(workspace: any = TestWorkspace, options: any = null) { this.workspace = workspace; this.options = options || { - globalSettings: { - settings: {} - } + globalSettings: {} }; } diff --git a/src/vs/workbench/common/options.ts b/src/vs/workbench/common/options.ts index fcdee85e403..edca5ac4349 100644 --- a/src/vs/workbench/common/options.ts +++ b/src/vs/workbench/common/options.ts @@ -6,11 +6,6 @@ import {IResourceInput} from 'vs/platform/editor/common/editor'; -export interface IGlobalSettings { - settings: any; - settingsParseErrors?: string[]; -} - export interface IOptions { /** @@ -36,5 +31,5 @@ export interface IOptions { /** * The global application settings if any. */ - globalSettings?: IGlobalSettings; + globalSettings?: any; } \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/bootstrap/index.js b/src/vs/workbench/electron-browser/bootstrap/index.js index 592c2560728..e7ebf493c57 100644 --- a/src/vs/workbench/electron-browser/bootstrap/index.js +++ b/src/vs/workbench/electron-browser/bootstrap/index.js @@ -122,7 +122,7 @@ function main() { // We get the global settings through a remote call from the browser // because its value can change dynamically. - const rawGlobalSettings = remote.getGlobal('globalSettingsValue') || '{"settings":{}}'; + const rawGlobalSettings = remote.getGlobal('globalSettingsValue') || '{}'; const globalSettings = JSON.parse(rawGlobalSettings); // disable pinch zoom & apply zoom level early to avoid glitches diff --git a/src/vs/workbench/electron-browser/integration.ts b/src/vs/workbench/electron-browser/integration.ts index a108d1cf8d3..3cca2ab3a24 100644 --- a/src/vs/workbench/electron-browser/integration.ts +++ b/src/vs/workbench/electron-browser/integration.ts @@ -77,12 +77,7 @@ export class ElectronIntegration { // Support options change ipc.on('vscode:optionsChange', (event, options: string) => { const optionsData = JSON.parse(options); - for (let key in optionsData) { - if (optionsData.hasOwnProperty(key)) { - const value = optionsData[key]; - (this.contextService).updateOptions(key, value); - } - } + (this.contextService).updateOptions('globalSettings', optionsData); }); // Support resolve keybindings event diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 94a06321b7f..b68dede5c69 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -7,7 +7,7 @@ import winjs = require('vs/base/common/winjs.base'); import {WorkbenchShell} from 'vs/workbench/electron-browser/shell'; -import {IOptions, IGlobalSettings} from 'vs/workbench/common/options'; +import {IOptions} from 'vs/workbench/common/options'; import errors = require('vs/base/common/errors'); import platform = require('vs/base/common/platform'); import paths = require('vs/base/common/paths'); @@ -62,7 +62,7 @@ export interface IWindowConfiguration extends ParsedArgs { extensionsToInstall?: string[]; } -export function startup(configuration: IWindowConfiguration, globalSettings: IGlobalSettings): winjs.TPromise { +export function startup(configuration: IWindowConfiguration, globalSettings: any): winjs.TPromise { // Shell Options const filesToOpen = configuration.filesToOpen && configuration.filesToOpen.length ? toInputs(configuration.filesToOpen) : null; diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index 9e4f2315c45..abd86dd19dd 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -117,10 +117,10 @@ export class ConfigurationService extends CommonConfigurationService { return { contents: objects.mixin( objects.clone(defaults.contents), // target: default values (but don't modify!) - globalSettings.settings, // source: global configured values + globalSettings, // source: global configured values true // overwrite ), - parseErrors: globalSettings.settingsParseErrors + parseErrors: [] }; }