Skip to content

Commit

Permalink
extract reusable userSettings library
Browse files Browse the repository at this point in the history
  • Loading branch information
bpasero committed Nov 20, 2015
1 parent b703e67 commit d8a325a
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 154 deletions.
160 changes: 8 additions & 152 deletions src/vs/workbench/electron-main/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,176 +3,32 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/


'use strict';

import fs = require('fs');
import events = require('events');
import path = require('path');
import app = require('app');

import env = require('vs/workbench/electron-main/env');
import json = require('vs/base/common/json');
import objects = require('vs/base/common/objects');

const eventEmitter = new events.EventEmitter();

const EventTypes = {
CHANGE: 'change'
};

export interface ISettings {
settings: any;
settingsParseErrors?: string[];
keybindings: any
}

export function onChange<T>(clb: (settings: ISettings) => void): () => void {
eventEmitter.addListener(EventTypes.CHANGE, clb);
import {UserSettings} from 'vs/workbench/node/userSettings';

return () => eventEmitter.removeListener(EventTypes.CHANGE, clb);
}

export class SettingsManager {

private static CHANGE_BUFFER_DELAY = 300;

private timeoutHandle: number;
public globalSettings: ISettings;
export class SettingsManager extends UserSettings {

constructor() {
this.registerWatchers();
}

public getValue(key: string, fallback?:any): any {
if (!key) {
return fallback;
}

let value = this.globalSettings.settings;

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 watcher = fs.watch(path.dirname(env.appSettingsPath));
watcher.on('change', (eventType: string, fileName: string) => this.onSettingsFileChange(eventType, fileName));
super(env.appSettingsPath, env.appKeybindingsPath);

app.on('will-quit', () => {
watcher.close();
this.dispose();
});
}

private onSettingsFileChange(eventType: string, fileName: string): void {

// we can get multiple change events for one change, so we buffer through a timeout
if (this.timeoutHandle) {
global.clearTimeout(this.timeoutHandle);
delete this.timeoutHandle;
}

this.timeoutHandle = global.setTimeout(() => {

// Reload
let didChange = this.load();

// Emit event
if (didChange) {
eventEmitter.emit(EventTypes.CHANGE, this.globalSettings);
}

}, SettingsManager.CHANGE_BUFFER_DELAY);
}

public load(): boolean {
let loadedSettings = this.doLoad();
if (!objects.equals(loadedSettings, this.globalSettings)) {

// Keep in class
this.globalSettings = loadedSettings;
const settingsChanged = super.load();

// 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) {
global.globalSettingsValue = JSON.stringify(this.globalSettings);

return true; // changed value
}

return false; // no changed value
}

private doLoad(): ISettings {
let settings = this.doLoadSettings();

return {
settings: settings.contents,
settingsParseErrors: settings.parseErrors,
keybindings: this.doLoadKeybindings()
};
}

private doLoadSettings(): { contents: any; parseErrors?: string[]; } {

function 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;
}


try {
let root = {};
let content = '{}';
try {
content = fs.readFileSync(env.appSettingsPath).toString();
} catch (error) {
// ignore
}

let contents = json.parse(content) || {};
for (let key in contents) {
setNode(root, key, contents[key]);
}
return {
contents: root
};
} catch (error) {
// parse problem
return {
contents: {},
parseErrors: [env.appSettingsPath]
};
}
}

private doLoadKeybindings(): any {
try {
return json.parse(fs.readFileSync(env.appKeybindingsPath).toString());
} catch (error) {
// Ignore loading and parsing errors
}

return [];
return settingsChanged;
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/electron-main/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,9 @@ export class WindowsManager {
}, 100);
});

settings.onChange((newSettings) => {
settings.manager.onChange.add((newSettings) => {
this.sendToAll('vscode:optionsChange', JSON.stringify({ globalSettings: newSettings }));
});
}, this);

ipc.on('vscode:startCrashReporter', (event: any, config: any) => {
crashReporter.start(config);
Expand Down
178 changes: 178 additions & 0 deletions src/vs/workbench/node/userSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*---------------------------------------------------------------------------------------------
* 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 fs = require('fs');
import path = require('path');

import json = require('vs/base/common/json');
import objects = require('vs/base/common/objects');
import {EventProvider} from 'vs/base/common/eventProvider';
import {EventSource} from 'vs/base/common/eventSource';

export interface ISettings {
settings: any;
settingsParseErrors?: string[];
keybindings: any
}

export class UserSettings {

private static CHANGE_BUFFER_DELAY = 300;

public globalSettings: ISettings;

private timeoutHandle: number;
private watcher: fs.FSWatcher;
private appSettingsPath: string;
private appKeybindingsPath: string;

private _onChange: EventSource<(settings: ISettings) => void>;

constructor(appSettingsPath:string, appKeybindingsPath: string) {
this.appSettingsPath = appSettingsPath;
this.appKeybindingsPath = appKeybindingsPath;
this._onChange = new EventSource<(settings: ISettings) => void>();

this.registerWatchers();
}

public get onChange(): EventProvider<(settings: ISettings) => void> {
return this._onChange.value;
}

public getValue(key: string, fallback?: any): any {
if (!key) {
return fallback;
}

let value = this.globalSettings.settings;

let parts = key.split('\.');
while (parts.length && value) {
let part = parts.shift();
value = value[part];
}

return typeof value !== 'undefined' ? value : fallback;
}

private registerWatchers(): void {
this.watcher = fs.watch(path.dirname(this.appSettingsPath));
this.watcher.on('change', (eventType: string, fileName: string) => this.onSettingsFileChange(eventType, fileName));
}

private onSettingsFileChange(eventType: string, fileName: string): void {

// we can get multiple change events for one change, so we buffer through a timeout
if (this.timeoutHandle) {
global.clearTimeout(this.timeoutHandle);
delete this.timeoutHandle;
}

this.timeoutHandle = global.setTimeout(() => {

// Reload
let didChange = this.load();

// Emit event
if (didChange) {
this._onChange.fire(this.globalSettings);
}

}, UserSettings.CHANGE_BUFFER_DELAY);
}

public load(): boolean {
let loadedSettings = this.doLoad();
if (!objects.equals(loadedSettings, this.globalSettings)) {

// Keep in class
this.globalSettings = loadedSettings;

return true; // changed value
}

return false; // no changed value
}

private doLoad(): ISettings {
let settings = this.doLoadSettings();

return {
settings: settings.contents,
settingsParseErrors: settings.parseErrors,
keybindings: this.doLoadKeybindings()
};
}

private doLoadSettings(): { contents: any; parseErrors?: string[]; } {

function 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;
}


try {
let root = {};
let content = '{}';
try {
content = fs.readFileSync(this.appSettingsPath).toString();
} catch (error) {
// ignore
}

let contents = json.parse(content) || {};
for (let key in contents) {
setNode(root, key, contents[key]);
}
return {
contents: root
};
} catch (error) {
// parse problem
return {
contents: {},
parseErrors: [this.appSettingsPath]
};
}
}

private doLoadKeybindings(): any {
try {
return json.parse(fs.readFileSync(this.appKeybindingsPath).toString());
} catch (error) {
// Ignore loading and parsing errors
}

return [];
}

public dispose(): void {
if (this.watcher) {
this.watcher.close();
this.watcher = null;
}
}
}

0 comments on commit d8a325a

Please sign in to comment.