From a3b83e9d2dcd7094f5be05794d29e9d68de5a8b8 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 17 Aug 2016 16:46:45 +0200 Subject: [PATCH] debt: remove user settings! --- src/vs/base/node/userSettings.ts | 234 -------------------------- src/vs/code/electron-main/settings.ts | 209 +++++++++++++++++++++-- 2 files changed, 199 insertions(+), 244 deletions(-) delete mode 100644 src/vs/base/node/userSettings.ts diff --git a/src/vs/base/node/userSettings.ts b/src/vs/base/node/userSettings.ts deleted file mode 100644 index c858a4572f5..00000000000 --- a/src/vs/base/node/userSettings.ts +++ /dev/null @@ -1,234 +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 * 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 {TPromise} from 'vs/base/common/winjs.base'; -import Event, {Emitter} from 'vs/base/common/event'; - -export interface ISettings { - settings: any; - settingsParseErrors?: string[]; - keybindings: any; -} - -export class UserSettings { - - private static CHANGE_BUFFER_DELAY = 300; - - globalSettings: ISettings; - - private timeoutHandle: number; - private watcher: fs.FSWatcher; - private appSettingsPath: string; - private appKeybindingsPath: string; - - private _onChange: Emitter; - - constructor(appSettingsPath: string, appKeybindingsPath: string) { - this.appSettingsPath = appSettingsPath; - this.appKeybindingsPath = appKeybindingsPath; - this._onChange = new Emitter(); - - this.registerWatchers(); - } - - static getValue(userDataPath: string, key: string, fallback?: any): TPromise { - // TODO@joao cleanup! - const appSettingsPath = path.join(userDataPath, 'User', 'settings.json'); - - return new TPromise((c, e) => { - fs.readFile(appSettingsPath, (error /* ignore */, fileContents) => { - let root = Object.create(null); - let content = fileContents ? fileContents.toString() : '{}'; - - let contents = Object.create(null); - try { - contents = json.parse(content); - } catch (error) { - // ignore parse problem - } - - for (let key in contents) { - UserSettings.setNode(root, key, contents[key]); - } - - return c(UserSettings.doGetValue(root, key, fallback)); - }); - }); - } - - get onChange(): Event { - return this._onChange.event; - } - - getValue(key: string, fallback?: any): any { - return UserSettings.doGetValue(this.globalSettings.settings, key, fallback); - } - - private static doGetValue(globalSettings: any, key: string, fallback?: any): any { - if (!key) { - return fallback; - } - - let value = globalSettings; - - let parts = key.split('\.'); - while (parts.length && value) { - let part = parts.shift(); - value = value[part]; - } - - return typeof value !== 'undefined' ? value : fallback; - } - - private registerWatchers(): void { - let 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 for settings and keybindings and attach watchers if they resolve - let followSymlinkPaths = [ - this.appSettingsPath, - this.appKeybindingsPath - ]; - followSymlinkPaths.forEach((path) => { - fs.lstat(path, (err, stats) => { - if (err) { - return; - } - if (stats.isSymbolicLink() && !stats.isDirectory()) { - fs.readlink(path, (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 - let didChange = this.loadSync(); - - // Emit event - if (didChange) { - this._onChange.fire(this.globalSettings); - } - - }, UserSettings.CHANGE_BUFFER_DELAY); - } - - loadSync(): boolean { - let loadedSettings = this.doLoadSync(); - if (!objects.equals(loadedSettings, this.globalSettings)) { - - // Keep in class - this.globalSettings = loadedSettings; - - return true; // changed value - } - - return false; // no changed value - } - - private doLoadSync(): ISettings { - let settings = this.doLoadSettingsSync(); - - return { - settings: settings.contents, - settingsParseErrors: settings.parseErrors, - keybindings: this.doLoadKeybindingsSync() - }; - } - - private doLoadSettingsSync(): { contents: any; parseErrors?: string[]; } { - let 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) { - UserSettings.setNode(root, key, contents[key]); - } - - return { - contents: root - }; - } - - private static setNode(root: any, key: string, value: any): any { - let segments = key.split('.'); - let 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; - } - - private doLoadKeybindingsSync(): any { - try { - return json.parse(fs.readFileSync(this.appKeybindingsPath).toString()); - } catch (error) { - // Ignore loading and parsing errors - } - - return []; - } - - 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/settings.ts b/src/vs/code/electron-main/settings.ts index f35795c25e2..ebc210bf4b6 100644 --- a/src/vs/code/electron-main/settings.ts +++ b/src/vs/code/electron-main/settings.ts @@ -6,14 +6,22 @@ '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 { UserSettings, ISettings } from 'vs/base/node/userSettings'; import { IEnvService } from 'vs/code/electron-main/env'; -import Event from 'vs/base/common/event'; +import Event, {Emitter} from 'vs/base/common/event'; export const ISettingsService = createDecorator('settingsService'); -// TODO@Joao TODO@Ben - this needs to die +export interface ISettings { + settings: any; + settingsParseErrors?: string[]; + keybindings: any; +} + export interface ISettingsService { _serviceBrand: any; globalSettings: ISettings; @@ -22,20 +30,47 @@ export interface ISettingsService { onChange: Event; } -export class SettingsManager extends UserSettings implements ISettingsService { +/** + * TODO@Joao TODO@Ben - this needs to die + * + * We need to decouple the settings with the keybindings. + * 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; - constructor(@IEnvService envService: IEnvService) { - super(envService.appSettingsPath, envService.appKeybindingsPath); + private static CHANGE_BUFFER_DELAY = 300; - app.on('will-quit', () => { - this.dispose(); - }); + globalSettings: ISettings; + + private timeoutHandle: number; + private watcher: fs.FSWatcher; + private appSettingsPath: string; + private appKeybindingsPath: string; + + private _onChange: Emitter; + + constructor(@IEnvService envService: IEnvService) { + this.appSettingsPath = envService.appSettingsPath; + this.appKeybindingsPath = envService.appKeybindingsPath; + this._onChange = new Emitter(); + + this.registerWatchers(); + app.on('will-quit', () => this.dispose()); } loadSync(): boolean { - const settingsChanged = super.loadSync(); + let settingsChanged = false; + let 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) { @@ -44,4 +79,158 @@ export class SettingsManager extends UserSettings implements ISettingsService { return settingsChanged; } + + get onChange(): Event { + return this._onChange.event; + } + + getValue(key: string, fallback?: any): any { + return SettingsManager.doGetValue(this.globalSettings.settings, key, fallback); + } + + private static doGetValue(globalSettings: any, key: string, fallback?: any): any { + if (!key) { + return fallback; + } + + let value = globalSettings; + + let parts = key.split('\.'); + while (parts.length && value) { + let part = parts.shift(); + value = value[part]; + } + + return typeof value !== 'undefined' ? value : fallback; + } + + private registerWatchers(): void { + let 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 for settings and keybindings and attach watchers if they resolve + let followSymlinkPaths = [ + this.appSettingsPath, + this.appKeybindingsPath + ]; + followSymlinkPaths.forEach((path) => { + fs.lstat(path, (err, stats) => { + if (err) { + return; + } + if (stats.isSymbolicLink() && !stats.isDirectory()) { + fs.readlink(path, (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 + let didChange = this.loadSync(); + + // Emit event + if (didChange) { + this._onChange.fire(this.globalSettings); + } + + }, SettingsManager.CHANGE_BUFFER_DELAY); + } + + private doLoadSync(): ISettings { + let settings = this.doLoadSettingsSync(); + + return { + settings: settings.contents, + settingsParseErrors: settings.parseErrors, + keybindings: this.doLoadKeybindingsSync() + }; + } + + private doLoadSettingsSync(): { contents: any; parseErrors?: string[]; } { + let 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) { + SettingsManager.setNode(root, key, contents[key]); + } + + return { + contents: root + }; + } + + private static setNode(root: any, key: string, value: any): any { + let segments = key.split('.'); + let 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; + } + + private doLoadKeybindingsSync(): any { + try { + return json.parse(fs.readFileSync(this.appKeybindingsPath).toString()); + } catch (error) { + // Ignore loading and parsing errors + } + + return []; + } + + dispose(): void { + if (this.watcher) { + this.watcher.close(); + this.watcher = null; + } + } } \ No newline at end of file