debt: remove user settings!
This commit is contained in:
parent
0c6503e12b
commit
a3b83e9d2d
|
@ -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<ISettings>;
|
|
||||||
|
|
||||||
constructor(appSettingsPath: string, appKeybindingsPath: string) {
|
|
||||||
this.appSettingsPath = appSettingsPath;
|
|
||||||
this.appKeybindingsPath = appKeybindingsPath;
|
|
||||||
this._onChange = new Emitter<ISettings>();
|
|
||||||
|
|
||||||
this.registerWatchers();
|
|
||||||
}
|
|
||||||
|
|
||||||
static getValue(userDataPath: string, key: string, fallback?: any): TPromise<any> {
|
|
||||||
// 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<ISettings> {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,14 +6,22 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { app } from 'electron';
|
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 { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { UserSettings, ISettings } from 'vs/base/node/userSettings';
|
|
||||||
import { IEnvService } from 'vs/code/electron-main/env';
|
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<ISettingsService>('settingsService');
|
export const ISettingsService = createDecorator<ISettingsService>('settingsService');
|
||||||
|
|
||||||
// TODO@Joao TODO@Ben - this needs to die
|
export interface ISettings {
|
||||||
|
settings: any;
|
||||||
|
settingsParseErrors?: string[];
|
||||||
|
keybindings: any;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ISettingsService {
|
export interface ISettingsService {
|
||||||
_serviceBrand: any;
|
_serviceBrand: any;
|
||||||
globalSettings: ISettings;
|
globalSettings: ISettings;
|
||||||
|
@ -22,20 +30,47 @@ export interface ISettingsService {
|
||||||
onChange: Event<ISettings>;
|
onChange: Event<ISettings>;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
_serviceBrand: any;
|
||||||
|
|
||||||
constructor(@IEnvService envService: IEnvService) {
|
private static CHANGE_BUFFER_DELAY = 300;
|
||||||
super(envService.appSettingsPath, envService.appKeybindingsPath);
|
|
||||||
|
|
||||||
app.on('will-quit', () => {
|
globalSettings: ISettings;
|
||||||
this.dispose();
|
|
||||||
});
|
private timeoutHandle: number;
|
||||||
|
private watcher: fs.FSWatcher;
|
||||||
|
private appSettingsPath: string;
|
||||||
|
private appKeybindingsPath: string;
|
||||||
|
|
||||||
|
private _onChange: Emitter<ISettings>;
|
||||||
|
|
||||||
|
constructor(@IEnvService envService: IEnvService) {
|
||||||
|
this.appSettingsPath = envService.appSettingsPath;
|
||||||
|
this.appKeybindingsPath = envService.appKeybindingsPath;
|
||||||
|
this._onChange = new Emitter<ISettings>();
|
||||||
|
|
||||||
|
this.registerWatchers();
|
||||||
|
app.on('will-quit', () => this.dispose());
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSync(): boolean {
|
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()
|
// Store into global so that any renderer can access the value with remote.getGlobal()
|
||||||
if (settingsChanged) {
|
if (settingsChanged) {
|
||||||
|
@ -44,4 +79,158 @@ export class SettingsManager extends UserSettings implements ISettingsService {
|
||||||
|
|
||||||
return settingsChanged;
|
return settingsChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get onChange(): Event<ISettings> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue