Merge pull request #60875 from Microsoft/ben/sqlite

Introduce SQLite (in-memory) for localStorage
This commit is contained in:
Benjamin Pasero 2018-10-15 09:10:24 +02:00 committed by GitHub
commit 05e1c11111
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
131 changed files with 2386 additions and 1169 deletions

View file

@ -310,19 +310,20 @@ function packageTask(platform, arch, opts) {
const deps = gulp.src(depsSrc, { base: '.', dot: true })
.pipe(filter(['**', '!**/package-lock.json']))
.pipe(util.cleanNodeModule('fsevents', ['binding.gyp', 'fsevents.cc', 'build/**', 'src/**', 'test/**'], ['**/*.node']))
.pipe(util.cleanNodeModule('oniguruma', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['**/*.node', 'src/*.js']))
.pipe(util.cleanNodeModule('vscode-sqlite3', ['binding.gyp', 'benchmark/**', 'cloudformation/**', 'deps/**', 'test/**', 'build/**', 'src/**'], ['build/Release/*.node']))
.pipe(util.cleanNodeModule('oniguruma', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['build/Release/*.node', 'src/*.js']))
.pipe(util.cleanNodeModule('windows-mutex', ['binding.gyp', 'build/**', 'src/**'], ['**/*.node']))
.pipe(util.cleanNodeModule('native-keymap', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['**/*.node']))
.pipe(util.cleanNodeModule('native-is-elevated', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['**/*.node']))
.pipe(util.cleanNodeModule('native-watchdog', ['binding.gyp', 'build/**', 'src/**'], ['**/*.node']))
.pipe(util.cleanNodeModule('spdlog', ['binding.gyp', 'build/**', 'deps/**', 'src/**', 'test/**'], ['**/*.node']))
.pipe(util.cleanNodeModule('native-keymap', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['build/Release/*.node']))
.pipe(util.cleanNodeModule('native-is-elevated', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['build/Release/*.node']))
.pipe(util.cleanNodeModule('native-watchdog', ['binding.gyp', 'build/**', 'src/**'], ['build/Release/*.node']))
.pipe(util.cleanNodeModule('spdlog', ['binding.gyp', 'build/**', 'deps/**', 'src/**', 'test/**'], ['build/Release/*.node']))
.pipe(util.cleanNodeModule('jschardet', ['dist/**']))
.pipe(util.cleanNodeModule('windows-foreground-love', ['binding.gyp', 'build/**', 'src/**'], ['**/*.node']))
.pipe(util.cleanNodeModule('windows-process-tree', ['binding.gyp', 'build/**', 'src/**'], ['**/*.node']))
.pipe(util.cleanNodeModule('gc-signals', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['**/*.node', 'src/index.js']))
.pipe(util.cleanNodeModule('gc-signals', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['build/Release/*.node', 'src/index.js']))
.pipe(util.cleanNodeModule('keytar', ['binding.gyp', 'build/**', 'src/**', 'script/**', 'node_modules/**'], ['**/*.node']))
.pipe(util.cleanNodeModule('node-pty', ['binding.gyp', 'build/**', 'src/**', 'tools/**'], ['build/Release/*.exe', 'build/Release/*.dll', 'build/Release/*.node']))
.pipe(util.cleanNodeModule('vscode-nsfw', ['binding.gyp', 'build/**', 'src/**', 'openpa/**', 'includes/**'], ['**/*.node', '**/*.a']))
.pipe(util.cleanNodeModule('vscode-nsfw', ['binding.gyp', 'build/**', 'src/**', 'openpa/**', 'includes/**'], ['build/Release/*.node', '**/*.a']))
.pipe(util.cleanNodeModule('vsda', ['binding.gyp', 'README.md', 'build/**', '*.bat', '*.sh', '*.cpp', '*.h'], ['build/Release/vsda.node']))
.pipe(createAsar(path.join(process.cwd(), 'node_modules'), ['**/*.node', '**/vscode-ripgrep/bin/*', '**/node-pty/build/Release/*'], 'app/node_modules.asar'));

View file

@ -43,7 +43,7 @@ function createCompile(src, build, emitError) {
const opts = _.clone(getTypeScriptCompilerOptions(src));
opts.inlineSources = !!build;
opts.noFilesystemLookup = true;
const ts = tsb.create(opts, true, undefined, err => reporter(err.toString()));
const ts = tsb.create(opts, true, undefined, err => new reporter(err.toString()));
return function (token) {
const utf8Filter = util.filter(data => /(\/|\\)test(\/|\\).*utf8/.test(data.path));
const tsFilter = util.filter(data => /\.ts$/.test(data.path));

View file

@ -47,7 +47,7 @@ function createCompile(src: string, build: boolean, emitError?: boolean): (token
opts.inlineSources = !!build;
opts.noFilesystemLookup = true;
const ts = tsb.create(opts, true, undefined, err => reporter(err.toString()));
const ts = tsb.create(opts, true, undefined, err => new (<any>reporter)(err.toString()));
return function (token?: util.ICancellationToken) {

View file

@ -49,6 +49,7 @@
"vscode-debugprotocol": "1.32.0",
"vscode-nsfw": "1.0.17",
"vscode-ripgrep": "^1.2.2",
"vscode-sqlite3": "4.0.2",
"vscode-textmate": "^4.0.1",
"vscode-xterm": "3.9.0-beta7",
"winreg": "^1.2.4",

3
src/bootstrap.js vendored
View file

@ -37,7 +37,9 @@ exports.enableASARSupport = function (nodeModulesPath) {
const NODE_MODULES_ASAR_PATH = NODE_MODULES_PATH + '.asar';
// @ts-ignore
const originalResolveLookupPaths = Module._resolveLookupPaths;
// @ts-ignore
Module._resolveLookupPaths = function (request, parent, newReturn) {
const result = originalResolveLookupPaths(request, parent, newReturn);
@ -225,6 +227,7 @@ exports.configurePortable = function () {
* This should be called before importing the applicationinsights module
*/
exports.avoidMonkeyPatchFromAppInsights = function () {
// @ts-ignore
process.env['APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL'] = true; // Skip monkey patching of 3rd party modules by appinsights
global['diagnosticsSource'] = {}; // Prevents diagnostic channel (which patches "require") from initializing entirely
};

109
src/typings/vscode-sqlite3.d.ts vendored Normal file
View file

@ -0,0 +1,109 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Type definitions for sqlite3 3.1
// Project: https://github.com/mapbox/node-sqlite3
// Definitions by: Nick Malaguti <https://github.com/nmalaguti>
// Sumant Manne <https://github.com/dpyro>
// Behind The Math <https://github.com/BehindTheMath>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/// <reference types="node" />
declare module 'vscode-sqlite3' {
import events = require("events");
export const OPEN_READONLY: number;
export const OPEN_READWRITE: number;
export const OPEN_CREATE: number;
export const cached: {
Database(filename: string, callback?: (this: Database, err: Error | null) => void): Database;
Database(filename: string, mode?: number, callback?: (this: Database, err: Error | null) => void): Database;
};
export interface RunResult extends Statement {
lastID: number;
changes: number;
}
export class Statement extends events.EventEmitter {
bind(callback?: (err: Error | null) => void): this;
bind(...params: any[]): this;
reset(callback?: (err: null) => void): this;
finalize(callback?: (err: Error) => void): Database;
run(callback?: (err: Error | null) => void): this;
run(params: any, callback?: (this: RunResult, err: Error | null) => void): this;
run(...params: any[]): this;
get(callback?: (err: Error | null, row?: any) => void): this;
get(params: any, callback?: (this: RunResult, err: Error | null, row?: any) => void): this;
get(...params: any[]): this;
all(callback?: (err: Error | null, rows: any[]) => void): this;
all(params: any, callback?: (this: RunResult, err: Error | null, rows: any[]) => void): this;
all(...params: any[]): this;
each(callback?: (err: Error | null, row: any) => void, complete?: (err: Error | null, count: number) => void): this;
each(params: any, callback?: (this: RunResult, err: Error | null, row: any) => void, complete?: (err: Error | null, count: number) => void): this;
each(...params: any[]): this;
}
export class Database extends events.EventEmitter {
constructor(filename: string, callback?: (err: Error | null) => void);
constructor(filename: string, mode?: number, callback?: (err: Error | null) => void);
close(callback?: (err: Error | null) => void): void;
run(sql: string, callback?: (this: RunResult, err: Error | null) => void): this;
run(sql: string, params: any, callback?: (this: RunResult, err: Error | null) => void): this;
run(sql: string, ...params: any[]): this;
get(sql: string, callback?: (this: Statement, err: Error | null, row: any) => void): this;
get(sql: string, params: any, callback?: (this: Statement, err: Error | null, row: any) => void): this;
get(sql: string, ...params: any[]): this;
all(sql: string, callback?: (this: Statement, err: Error | null, rows: any[]) => void): this;
all(sql: string, params: any, callback?: (this: Statement, err: Error | null, rows: any[]) => void): this;
all(sql: string, ...params: any[]): this;
each(sql: string, callback?: (this: Statement, err: Error | null, row: any) => void, complete?: (err: Error | null, count: number) => void): this;
each(sql: string, params: any, callback?: (this: Statement, err: Error | null, row: any) => void, complete?: (err: Error | null, count: number) => void): this;
each(sql: string, ...params: any[]): this;
exec(sql: string, callback?: (this: Statement, err: Error | null) => void): this;
prepare(sql: string, callback?: (this: Statement, err: Error | null) => void): Statement;
prepare(sql: string, params: any, callback?: (this: Statement, err: Error | null) => void): Statement;
prepare(sql: string, ...params: any[]): Statement;
serialize(callback?: () => void): void;
parallelize(callback?: () => void): void;
on(event: "trace", listener: (sql: string) => void): this;
on(event: "profile", listener: (sql: string, time: number) => void): this;
on(event: "error", listener: (err: Error) => void): this;
on(event: "open" | "close", listener: () => void): this;
on(event: string, listener: (...args: any[]) => void): this;
configure(option: "busyTimeout", value: number): void;
}
export function verbose(): sqlite3;
export interface sqlite3 {
OPEN_READONLY: number;
OPEN_READWRITE: number;
OPEN_CREATE: number;
cached: typeof cached;
RunResult: RunResult;
Statement: typeof Statement;
Database: typeof Database;
verbose(): this;
}
}

View file

@ -172,7 +172,7 @@ export class SimpleThrottler {
* delayer.trigger(() => { return makeTheTrip(); });
* }
*/
export class Delayer<T> {
export class Delayer<T> implements IDisposable {
private timeout: any;
private completionPromise: TPromise | null;
@ -232,6 +232,10 @@ export class Delayer<T> {
this.timeout = null;
}
}
dispose(): void {
this.cancelTimeout();
}
}
/**

View file

@ -32,6 +32,24 @@ export function getOrSet<K, V>(map: Map<K, V>, key: K, value: V): V {
return result;
}
export function mapToString<K, V>(map: Map<K, V>): string {
const entries: string[] = [];
map.forEach((value, key) => {
entries.push(`${key} => ${value}`);
});
return `Map(${map.size}) {${entries.join(', ')}}`;
}
export function setToString<K>(set: Set<K>): string {
const entries: K[] = [];
set.forEach(value => {
entries.push(value);
});
return `Set(${set.size}) {${entries.join(', ')}}`;
}
export interface IKeyIterator {
reset(key: string): this;
next(): this;

450
src/vs/base/node/storage.ts Normal file
View file

@ -0,0 +1,450 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Database, Statement } from 'vscode-sqlite3';
import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { RunOnceScheduler } from 'vs/base/common/async';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { mapToString, setToString } from 'vs/base/common/map';
import { basename } from 'path';
export interface IStorageOptions {
path: string;
logging?: IStorageLoggingOptions;
}
export interface IStorageLoggingOptions {
errorLogger?: (error: string | Error) => void;
infoLogger?: (msg: string) => void;
info?: boolean;
trace?: boolean;
profile?: boolean;
}
enum StorageState {
None,
Initialized,
Closed
}
export class Storage extends Disposable {
_serviceBrand: any;
private static readonly FLUSH_DELAY = 100;
private _onDidChangeStorage: Emitter<string> = this._register(new Emitter<string>());
get onDidChangeStorage(): Event<string> { return this._onDidChangeStorage.event; }
private state = StorageState.None;
private storage: SQLiteStorageImpl;
private cache: Map<string, string> = new Map<string, string>();
private pendingScheduler: RunOnceScheduler;
private pendingDeletes: Set<string> = new Set<string>();
private pendingInserts: Map<string, string> = new Map();
private pendingPromises: { resolve: Function, reject: Function }[] = [];
constructor(options: IStorageOptions) {
super();
this.storage = new SQLiteStorageImpl(options);
this.pendingScheduler = new RunOnceScheduler(() => this.flushPending(), Storage.FLUSH_DELAY);
}
init(): Promise<void> {
if (this.state !== StorageState.None) {
return Promise.resolve(); // either closed or already initialized
}
this.state = StorageState.Initialized;
return this.storage.getItems().then(items => {
this.cache = items;
});
}
get(key: string, fallbackValue?: any): string {
const value = this.cache.get(key);
if (isUndefinedOrNull(value)) {
return fallbackValue;
}
return value;
}
getBoolean(key: string, fallbackValue?: boolean): boolean {
const value = this.get(key);
if (isUndefinedOrNull(value)) {
return fallbackValue;
}
return value === 'true';
}
getInteger(key: string, fallbackValue?: number): number {
const value = this.get(key);
if (isUndefinedOrNull(value)) {
return fallbackValue;
}
return parseInt(value, 10);
}
set(key: string, value: any): Promise<void> {
if (this.state === StorageState.Closed) {
return Promise.resolve(); // Return early if we are already closed
}
// We remove the key for undefined/null values
if (isUndefinedOrNull(value)) {
return this.delete(key);
}
// Otherwise, convert to String and store
const valueStr = String(value);
// Return early if value already set
const currentValue = this.cache.get(key);
if (currentValue === valueStr) {
return Promise.resolve();
}
// Update in cache and pending
this.cache.set(key, valueStr);
this.pendingInserts.set(key, valueStr);
this.pendingDeletes.delete(key);
// Event
this._onDidChangeStorage.fire(key);
return this.update();
}
delete(key: string): Promise<void> {
if (this.state === StorageState.Closed) {
return Promise.resolve(); // Return early if we are already closed
}
// Remove from cache and add to pending
const wasDeleted = this.cache.delete(key);
if (!wasDeleted) {
return Promise.resolve(); // Return early if value already deleted
}
if (!this.pendingDeletes.has(key)) {
this.pendingDeletes.add(key);
}
this.pendingInserts.delete(key);
// Event
this._onDidChangeStorage.fire(key);
return this.update();
}
private update(): Promise<void> {
// Schedule
if (!this.pendingScheduler.isScheduled()) {
this.pendingScheduler.schedule();
}
return new Promise((resolve, reject) => this.pendingPromises.push({ resolve, reject }));
}
close(): Promise<void> {
if (this.state === StorageState.Closed) {
return Promise.resolve(); // return if already closed
}
// Update state
this.state = StorageState.Closed;
// Dispose scheduler (no more scheduling possible)
this.pendingScheduler.dispose();
// Flush & close
return this.flushPending().then(() => {
return this.storage.close();
});
}
private flushPending(): Promise<void> {
// Get pending data
const pendingPromises = this.pendingPromises;
const pendingDeletes = this.pendingDeletes;
const pendingInserts = this.pendingInserts;
// Reset pending data for next run
this.pendingPromises = [];
this.pendingDeletes = new Set<string>();
this.pendingInserts = new Map<string, string>();
return this.storage.updateItems({ insert: pendingInserts, delete: pendingDeletes }).then(() => {
// Resolve pending
pendingPromises.forEach(promise => promise.resolve());
}, error => {
// Forward error to pending
pendingPromises.forEach(promise => promise.reject(error));
});
}
}
export interface IUpdateRequest {
insert?: Map<string, string>;
delete?: Set<string>;
}
export class SQLiteStorageImpl {
private db: Promise<Database>;
private name: string;
private logger: SQLiteStorageLogger;
constructor(private options: IStorageOptions) {
this.name = basename(options.path);
this.logger = new SQLiteStorageLogger(options.logging);
this.db = this.open();
}
getItems(): Promise<Map<string, string>> {
return this.db.then(db => {
const items = new Map<string, string>();
return this.each(db, 'SELECT * FROM ItemTable', row => {
items.set(row.key, row.value);
}).then(() => {
if (this.logger.verbose) {
this.logger.info(`[storage ${this.name}] getItems(): ${mapToString(items)}`);
}
return items;
});
});
}
updateItems(request: IUpdateRequest): Promise<void> {
let updateCount = 0;
if (request.insert) {
updateCount += request.insert.size;
}
if (request.delete) {
updateCount += request.delete.size;
}
if (updateCount === 0) {
return Promise.resolve();
}
if (this.logger.verbose) {
this.logger.info(`[storage ${this.name}] updateItems(): insert(${request.insert ? mapToString(request.insert) : '0'}), delete(${request.delete ? setToString(request.delete) : '0'})`);
}
return this.db.then(db => {
return this.transaction(db, () => {
if (request.insert && request.insert.size > 0) {
this.prepare(db, 'INSERT INTO ItemTable VALUES (?,?)', stmt => {
request.insert.forEach((value, key) => {
stmt.run([key, value]);
});
});
}
if (request.delete && request.delete.size) {
this.prepare(db, 'DELETE FROM ItemTable WHERE key=?', stmt => {
request.delete.forEach(key => {
stmt.run(key);
});
});
}
});
});
}
close(): Promise<void> {
this.logger.info(`[storage ${this.name}] close()`);
return this.db.then(db => {
return new Promise((resolve, reject) => {
db.close(error => {
if (error) {
this.logger.error(`[storage ${this.name}] close(): ${error}`);
return reject(error);
}
resolve();
});
});
});
}
private open(): Promise<Database> {
this.logger.info(`[storage ${this.name}] open()`);
return new Promise((resolve, reject) => {
this.doOpen(this.options.path).then(resolve, error => {
this.logger.error(`[storage ${this.name}] open(): Error (open DB): ${error}`);
this.logger.error(`[storage ${this.name}] open(): Falling back to in-memory DB`);
// In case of any error to open the DB, use an in-memory
// DB so that we always have a valid DB to talk to.
this.doOpen(':memory:').then(resolve, reject);
});
});
}
private doOpen(path: string): Promise<Database> {
return new Promise((resolve, reject) => {
import('vscode-sqlite3').then(sqlite3 => {
const db = new (this.logger.verbose ? sqlite3.verbose().Database : sqlite3.Database)(path, error => {
if (error) {
return reject(error);
}
// Setup schema
this.exec(db, [
'PRAGMA user_version = 1;',
'CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB)'
].join('')).then(() => resolve(db), error => reject(error));
});
// Check for errors
db.on('error', error => this.logger.error(`[storage ${this.name}] Error (event): ${error}`));
// Tracing
if (this.logger.trace) {
db.on('trace', sql => this.logger.info(`[storage ${this.name}] Trace (event): ${sql}`));
}
// Profiling
if (this.logger.profile) {
db.on('profile', (sql, time) => this.logger.info(`[storage ${this.name}] Profile (event): ${sql} (${time}ms)`));
}
});
});
}
private exec(db: Database, sql: string): Promise<void> {
return new Promise((resolve, reject) => {
db.exec(sql, error => {
if (error) {
this.logger.error(`[storage ${this.name}] exec(): ${error}`);
return reject(error);
}
resolve();
});
});
}
private each(db: Database, sql: string, callback: (row: any) => void): Promise<void> {
return new Promise((resolve, reject) => {
db.each(sql, (error, row) => {
if (error) {
this.logger.error(`[storage ${this.name}] each(): ${error}`);
return reject(error);
}
callback(row);
}, error => {
if (error) {
this.logger.error(`[storage ${this.name}] each(): ${error}`);
return reject(error);
}
resolve();
});
});
}
private transaction(db: Database, transactions: () => void): Promise<void> {
return new Promise((resolve, reject) => {
db.serialize(() => {
db.run('BEGIN TRANSACTION');
transactions();
db.run('END TRANSACTION', error => {
if (error) {
this.logger.error(`[storage ${this.name}] transaction(): ${error}`);
return reject(error);
}
resolve();
});
});
});
}
private prepare(db: Database, sql: string, runCallback: (stmt: Statement) => void): void {
const stmt = db.prepare(sql);
runCallback(stmt);
const statementErrorListener = error => {
this.logger.error(`[storage ${this.name}] prepare(): ${error} (${sql})`);
};
stmt.on('error', statementErrorListener);
stmt.finalize(error => {
if (error) {
statementErrorListener(error);
}
stmt.removeListener('error', statementErrorListener);
});
}
}
class SQLiteStorageLogger {
private logInfo: boolean;
private logError: boolean;
constructor(private options?: IStorageLoggingOptions) {
this.logInfo = this.verbose && options && !!options.infoLogger;
this.logError = options && !!options.errorLogger;
}
get verbose(): boolean {
return this.options && (this.options.info || this.options.trace || this.options.profile);
}
get trace(): boolean {
return this.options && this.options.trace;
}
get profile(): boolean {
return this.options && this.options.profile;
}
info(msg: string): void {
if (this.logInfo) {
this.options.infoLogger(msg);
}
}
error(error: string | Error): void {
if (this.logError) {
this.options.errorLogger(error);
}
}
}

View file

@ -0,0 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
this is not a sqlite DB

File diff suppressed because one or more lines are too long

View file

@ -53,14 +53,9 @@ bootstrapWindow.load([
function showPartsSplash(configuration) {
perf.mark('willShowPartsSplash');
// TODO@Ben remove me after a while
perf.mark('willAccessLocalStorage');
let storage = window.localStorage;
perf.mark('didAccessLocalStorage');
let data;
try {
let raw = storage.getItem('storage://global/parts-splash-data');
let raw = window.localStorage.getItem('storage://global/parts-splash-data');
data = JSON.parse(raw);
} catch (e) {
// ignore
@ -134,6 +129,7 @@ function getLazyEnv() {
ipc.once('vscode:acceptShellEnv', function (event, shellEnv) {
clearTimeout(handle);
bootstrapWindow.assign(process.env, shellEnv);
// @ts-ignore
resolve(process.env);
});

View file

@ -322,7 +322,7 @@ export class WindowsManager implements IWindowsMainService {
// See note on #onBeforeShutdown() for details how these events are flowing
private onBeforeWindowClose(win: ICodeWindow): void {
if (this.lifecycleService.isQuitRequested) {
if (this.lifecycleService.quitRequested) {
return; // during quit, many windows close in parallel so let it be handled in the before-quit handler
}
@ -1174,6 +1174,7 @@ export class WindowsManager implements IWindowsMainService {
const configuration: IWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI
configuration.appRoot = this.environmentService.appRoot;
configuration.machineId = this.machineId;
configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir;
configuration.mainPid = process.pid;
configuration.execPath = process.execPath;
configuration.userEnv = assign({}, this.initialUserEnv, options.userEnv || {});
@ -1188,7 +1189,6 @@ export class WindowsManager implements IWindowsMainService {
configuration.filesToDiff = fileInputs.filesToDiff;
configuration.filesToWait = fileInputs.filesToWait;
}
configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir;
// if we know the backup folder upfront (for empty windows to restore), we can set it
// directly here which helps for restoring UI state associated with that window.

View file

@ -72,7 +72,7 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
protected _state: FindReplaceState;
protected _updateHistoryDelayer: Delayer<void>;
private _model: FindModelBoundToEditorModel;
protected _storageService: IStorageService;
private _storageService: IStorageService;
private _clipboardService: IClipboardService;
protected readonly _contextKeyService: IContextKeyService;

View file

@ -15,6 +15,7 @@ import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Event } from 'vs/base/common/event';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { Delayer } from 'vs/base/common/async';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
@ -59,9 +60,14 @@ suite('FindController', () => {
let clipboardState = '';
let serviceCollection = new ServiceCollection();
serviceCollection.set(IStorageService, {
_serviceBrand: undefined,
onDidChangeStorage: Event.None,
onWillSaveState: Event.None,
get: (key: string) => queryState[key],
getBoolean: (key: string) => !!queryState[key],
store: (key: string, value: any) => { queryState[key] = value; }
getInteger: (key: string) => undefined,
store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); },
remove: (key) => void 0
} as IStorageService);
if (platform.isMacintosh) {
@ -429,9 +435,14 @@ suite('FindController query options persistence', () => {
queryState['editor.wholeWord'] = false;
let serviceCollection = new ServiceCollection();
serviceCollection.set(IStorageService, {
_serviceBrand: undefined,
onDidChangeStorage: Event.None,
onWillSaveState: Event.None,
get: (key: string) => queryState[key],
getBoolean: (key: string) => !!queryState[key],
store: (key: string, value: any) => { queryState[key] = value; }
getInteger: (key: string) => undefined,
store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); },
remove: (key) => void 0
} as IStorageService);
test('matchCase', () => {

View file

@ -10,6 +10,7 @@ import { InsertCursorAbove, InsertCursorBelow, MultiCursorSelectionController, S
import { Handler } from 'vs/editor/common/editorCommon';
import { EndOfLineSequence } from 'vs/editor/common/model';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Event } from 'vs/base/common/event';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { CommonFindController } from 'vs/editor/contrib/find/findController';
@ -59,9 +60,14 @@ suite('Multicursor selection', () => {
let queryState: { [key: string]: any; } = {};
let serviceCollection = new ServiceCollection();
serviceCollection.set(IStorageService, {
_serviceBrand: undefined,
onDidChangeStorage: Event.None,
onWillSaveState: Event.None,
get: (key: string) => queryState[key],
getBoolean: (key: string) => !!queryState[key],
store: (key: string, value: any) => { queryState[key] = value; }
getInteger: (key: string) => undefined,
store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); },
remove: (key) => void 0
} as IStorageService);
test('issue #8817: Cursor position changes when you cancel multicursor', () => {

View file

@ -10,7 +10,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ReferencesModel } from './referencesModel';
@ -95,14 +95,14 @@ export abstract class ReferencesController implements editorCommon.IEditorContri
}
}));
const storageKey = 'peekViewLayout';
const data = <LayoutData>JSON.parse(this._storageService.get(storageKey, undefined, '{}'));
const data = <LayoutData>JSON.parse(this._storageService.get(storageKey, StorageScope.GLOBAL, '{}'));
this._widget = this._instantiationService.createInstance(ReferenceWidget, this._editor, this._defaultTreeKeyboardSupport, data);
this._widget.setTitle(nls.localize('labelLoading', "Loading..."));
this._widget.show(range);
this._disposables.push(this._widget.onDidClose(() => {
modelPromise.cancel();
this._storageService.store(storageKey, JSON.stringify(this._widget.layoutData));
this._storageService.store(storageKey, JSON.stringify(this._widget.layoutData), StorageScope.GLOBAL);
this._widget = null;
this.closeWidget();
}));

View file

@ -8,10 +8,9 @@ import { LRUCache, TernarySearchTree } from 'vs/base/common/map';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ITextModel } from 'vs/editor/common/model';
import { IPosition } from 'vs/editor/common/core/position';
import { RunOnceScheduler } from 'vs/base/common/async';
import { CompletionItemKind, completionKindFromLegacyString } from 'vs/editor/common/modes';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
export abstract class Memory {
@ -194,26 +193,22 @@ export class PrefixMemory extends Memory {
export type MemMode = 'first' | 'recentlyUsed' | 'recentlyUsedByPrefix';
export class SuggestMemories {
export class SuggestMemories extends Disposable {
private readonly _storagePrefix = 'suggest/memories';
private _mode: MemMode;
private _strategy: Memory;
private readonly _persistSoon: RunOnceScheduler;
private readonly _listener: IDisposable;
constructor(
editor: ICodeEditor,
@IStorageService private readonly _storageService: IStorageService,
) {
this._persistSoon = new RunOnceScheduler(() => this._flush(), 3000);
this._setMode(editor.getConfiguration().contribInfo.suggestSelection);
this._listener = editor.onDidChangeConfiguration(e => e.contribInfo && this._setMode(editor.getConfiguration().contribInfo.suggestSelection));
}
super();
dispose(): void {
this._listener.dispose();
this._setMode(editor.getConfiguration().contribInfo.suggestSelection);
this._register(editor.onDidChangeConfiguration(e => e.contribInfo && this._setMode(editor.getConfiguration().contribInfo.suggestSelection)));
this._register(_storageService.onWillSaveState(() => this._saveState()));
}
private _setMode(mode: MemMode): void {
@ -235,15 +230,14 @@ export class SuggestMemories {
memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void {
this._strategy.memorize(model, pos, item);
this._persistSoon.schedule();
}
select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number {
return this._strategy.select(model, pos, items);
}
private _flush() {
private _saveState() {
const raw = JSON.stringify(this._strategy);
this._storageService.store(`${this._storagePrefix}/${this._mode}`, raw, StorageScope.WORKSPACE);
this._storageService.store(`${this._storagePrefix}/${this._mode}`, raw, StorageScope.WORKSPACE); //
}
}

View file

@ -401,9 +401,6 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
private detailsFocusBorderColor: string;
private detailsBorderColor: string;
private storageServiceAvailable: boolean = true;
private expandSuggestionDocs: boolean = false;
private firstFocusInCurrentList: boolean = false;
constructor(
@ -424,13 +421,6 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
this.focusedItem = null;
this.storageService = storageService;
if (this.expandDocsSettingFromStorage() === undefined) {
this.storageService.store('expandSuggestionDocs', expandSuggestionDocsByDefault, StorageScope.GLOBAL);
if (this.expandDocsSettingFromStorage() === undefined) {
this.storageServiceAvailable = false;
}
}
this.element = $('.editor-widget.suggest-widget');
if (!this.editor.getConfiguration().contribInfo.iconsInSuggestions) {
addClass(this.element, 'no-icons');
@ -1053,22 +1043,12 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
return 'suggestion';
}
// Monaco Editor does not have a storage service
private expandDocsSettingFromStorage(): boolean {
if (this.storageServiceAvailable) {
return this.storageService.getBoolean('expandSuggestionDocs', StorageScope.GLOBAL);
} else {
return this.expandSuggestionDocs;
}
return this.storageService.getBoolean('expandSuggestionDocs', StorageScope.GLOBAL, expandSuggestionDocsByDefault);
}
// Monaco Editor does not have a storage service
private updateExpandDocsSetting(value: boolean) {
if (this.storageServiceAvailable) {
this.storageService.store('expandSuggestionDocs', value, StorageScope.GLOBAL);
} else {
this.expandSuggestionDocs = value;
}
this.storageService.store('expandSuggestionDocs', value, StorageScope.GLOBAL);
}
dispose(): void {

View file

@ -32,7 +32,7 @@ export class StandaloneReferencesController extends ReferencesController {
notificationService,
instantiationService,
storageService,
configurationService,
configurationService
);
}
}

View file

@ -35,6 +35,7 @@ export interface ParsedArgs {
'trace-options'?: string;
log?: string;
logExtensionHostCommunication?: boolean;
logStorage?: boolean;
'extensions-dir'?: string;
'builtin-extensions-dir'?: string;
extensionDevelopmentPath?: string;
@ -117,8 +118,8 @@ export interface IEnvironmentService {
debugExtensionHost: IExtensionHostDebugParams;
debugSearch: IDebugParams;
logExtensionHostCommunication: boolean;
logStorage: boolean;
isBuilt: boolean;
wait: boolean;

View file

@ -54,6 +54,7 @@ const options: minimist.Opts = {
'prof-startup',
'verbose',
'logExtensionHostCommunication',
'logStorage',
'disable-extensions',
'list-extensions',
'show-versions',

View file

@ -206,7 +206,9 @@ export class EnvironmentService implements IEnvironmentService {
get log(): string { return this._args.log; }
get wait(): boolean { return this._args.wait; }
get logExtensionHostCommunication(): boolean { return this._args.logExtensionHostCommunication; }
get logStorage(): boolean { return this._args.logStorage; }
get performance(): boolean { return this._args.performance; }
get status(): boolean { return this._args.status; }

View file

@ -8,10 +8,10 @@ import { IExtensionManagementService, IExtensionEnablementService, DidUninstallE
import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { Emitter } from 'vs/base/common/event';
import { StorageService, InMemoryLocalStorage } from 'vs/platform/storage/common/storageService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { TestStorageService } from 'vs/workbench/test/workbenchTestServices';
function storageService(instantiationService: TestInstantiationService): IStorageService {
let service = instantiationService.get(IStorageService);
@ -22,12 +22,11 @@ function storageService(instantiationService: TestInstantiationService): IStorag
getWorkbenchState: () => WorkbenchState.FOLDER,
});
}
service = instantiationService.stub(IStorageService, instantiationService.createInstance(StorageService, new InMemoryLocalStorage(), new InMemoryLocalStorage()));
service = instantiationService.stub(IStorageService, new TestStorageService());
}
return service;
}
export class TestExtensionEnablementService extends ExtensionEnablementService {
constructor(instantiationService: TestInstantiationService) {
super(storageService(instantiationService), instantiationService.get(IWorkspaceContextService),

View file

@ -22,16 +22,16 @@ interface IStorageData {
class IntegrityStorage {
private static readonly KEY = 'integrityService';
private _storageService: IStorageService;
private _value: IStorageData;
private storageService: IStorageService;
private value: IStorageData;
constructor(storageService: IStorageService) {
this._storageService = storageService;
this._value = this._read();
this.storageService = storageService;
this.value = this._read();
}
private _read(): IStorageData {
let jsonValue = this._storageService.get(IntegrityStorage.KEY, StorageScope.GLOBAL);
let jsonValue = this.storageService.get(IntegrityStorage.KEY, StorageScope.GLOBAL);
if (!jsonValue) {
return null;
}
@ -42,19 +42,19 @@ class IntegrityStorage {
}
}
public get(): IStorageData {
return this._value;
get(): IStorageData {
return this.value;
}
public set(data: IStorageData): void {
this._value = data;
this._storageService.store(IntegrityStorage.KEY, JSON.stringify(this._value), StorageScope.GLOBAL);
set(data: IStorageData): void {
this.value = data;
this.storageService.store(IntegrityStorage.KEY, JSON.stringify(this.value), StorageScope.GLOBAL);
}
}
export class IntegrityServiceImpl implements IIntegrityService {
public _serviceBrand: any;
_serviceBrand: any;
private _storage: IntegrityStorage;
private _isPurePromise: Thenable<IntegrityTestResult>;
@ -101,7 +101,7 @@ export class IntegrityServiceImpl implements IIntegrityService {
);
}
public isPure(): Thenable<IntegrityTestResult> {
isPure(): Thenable<IntegrityTestResult> {
return this._isPurePromise;
}

View file

@ -11,19 +11,43 @@ import { isThenable } from 'vs/base/common/async';
export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');
/**
* An event that is send out when the window is about to close. Clients have a chance to veto the closing by either calling veto
* with a boolean "true" directly or with a promise that resolves to a boolean. Returning a promise is useful
* in cases of long running operations on shutdown.
* An event that is send out when the window is about to close. Clients have a chance to veto
* the closing by either calling veto with a boolean "true" directly or with a promise that
* resolves to a boolean. Returning a promise is useful in cases of long running operations
* on shutdown.
*
* Note: It is absolutely important to avoid long running promises on this call. Please try hard to return
* a boolean directly. Returning a promise has quite an impact on the shutdown sequence!
* Note: It is absolutely important to avoid long running promises if possible. Please try hard
* to return a boolean directly. Returning a promise has quite an impact on the shutdown sequence!
*/
export interface WillShutdownEvent {
/**
* Allows to veto the shutdown. The veto can be a long running operation but it
* will block the application from closing.
*/
veto(value: boolean | Thenable<boolean>): void;
/**
* The reason why Code will be shutting down.
*/
reason: ShutdownReason;
}
/**
* An event that is send out when the window closes. Clients have a chance to join the closing
* by providing a promise from the join method. Returning a promise is useful in cases of long
* running operations on shutdown.
*
* Note: It is absolutely important to avoid long running promises if possible. Please try hard
* to return a boolean directly. Returning a promise has quite an impact on the shutdown sequence!
*/
export interface ShutdownEvent {
/**
* Allows to veto the shutdown. The veto can be a long running operation.
* Allows to join the shutdown. The promise can be a long running operation but it
* will block the application from closing.
*/
veto(value: boolean | Thenable<boolean>): void;
join(promise: Thenable<void>): void;
/**
* The reason why Code is shutting down.
@ -51,6 +75,7 @@ export const enum StartupKind {
ReloadedWindow = 3,
ReopenedWindow = 4,
}
export function StartupKindToString(startupKind: StartupKind): string {
switch (startupKind) {
case StartupKind.NewWindow: return 'NewWindow';
@ -65,6 +90,7 @@ export const enum LifecyclePhase {
Running = 3,
Eventually = 4
}
export function LifecyclePhaseToString(phase: LifecyclePhase) {
switch (phase) {
case LifecyclePhase.Starting: return 'Starting';
@ -92,17 +118,11 @@ export interface ILifecycleService {
*/
readonly phase: LifecyclePhase;
/**
* Returns a promise that resolves when a certain lifecycle phase
* has started.
*/
when(phase: LifecyclePhase): Thenable<void>;
/**
* Fired before shutdown happens. Allows listeners to veto against the
* shutdown.
*/
readonly onWillShutdown: Event<ShutdownEvent>;
readonly onWillShutdown: Event<WillShutdownEvent>;
/**
* Fired when no client is preventing the shutdown from happening. Can be used to dispose heavy resources
@ -110,16 +130,22 @@ export interface ILifecycleService {
*
* The event carries a shutdown reason that indicates how the shutdown was triggered.
*/
readonly onShutdown: Event<ShutdownReason>;
readonly onShutdown: Event<ShutdownEvent>;
/**
* Returns a promise that resolves when a certain lifecycle phase
* has started.
*/
when(phase: LifecyclePhase): Thenable<void>;
}
export const NullLifecycleService: ILifecycleService = {
_serviceBrand: null,
phase: LifecyclePhase.Running,
when() { return Promise.resolve(); },
startupKind: StartupKind.NewWindow,
onWillShutdown: Event.None,
onShutdown: Event.None
onShutdown: Event.None,
phase: LifecyclePhase.Running,
startupKind: StartupKind.NewWindow,
when() { return Promise.resolve(); }
};
// Shared veto handling across main and renderer

View file

@ -5,7 +5,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { ILifecycleService, ShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, handleVetos, LifecyclePhaseToString } from 'vs/platform/lifecycle/common/lifecycle';
import { ILifecycleService, WillShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, handleVetos, LifecyclePhaseToString, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ipcRenderer as ipc } from 'electron';
import { Event, Emitter } from 'vs/base/common/event';
@ -14,59 +14,78 @@ import { mark } from 'vs/base/common/performance';
import { Barrier } from 'vs/base/common/async';
import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { Disposable } from 'vs/base/common/lifecycle';
import { onUnexpectedError } from 'vs/base/common/errors';
export class LifecycleService implements ILifecycleService {
export class LifecycleService extends Disposable implements ILifecycleService {
private static readonly _lastShutdownReasonKey = 'lifecyle.lastShutdownReason';
private static readonly LAST_SHUTDOWN_REASON_KEY = 'lifecyle.lastShutdownReason';
_serviceBrand: any;
private readonly _onWillShutdown = new Emitter<ShutdownEvent>();
private readonly _onShutdown = new Emitter<ShutdownReason>();
private readonly _onWillShutdown = this._register(new Emitter<WillShutdownEvent>());
get onWillShutdown(): Event<WillShutdownEvent> { return this._onWillShutdown.event; }
private readonly _onShutdown = this._register(new Emitter<ShutdownEvent>());
get onShutdown(): Event<ShutdownEvent> { return this._onShutdown.event; }
private readonly _startupKind: StartupKind;
get startupKind(): StartupKind { return this._startupKind; }
private _phase: LifecyclePhase = LifecyclePhase.Starting;
private _phaseWhen = new Map<LifecyclePhase, Barrier>();
get phase(): LifecyclePhase { return this._phase; }
private phaseWhen = new Map<LifecyclePhase, Barrier>();
constructor(
@INotificationService private readonly _notificationService: INotificationService,
@IWindowService private readonly _windowService: IWindowService,
@IStorageService private readonly _storageService: IStorageService,
@ILogService private readonly _logService: ILogService
@INotificationService private notificationService: INotificationService,
@IWindowService private windowService: IWindowService,
@IStorageService private storageService: IStorageService,
@ILogService private logService: ILogService
) {
const lastShutdownReason = this._storageService.getInteger(LifecycleService._lastShutdownReasonKey, StorageScope.WORKSPACE);
this._storageService.remove(LifecycleService._lastShutdownReasonKey, StorageScope.WORKSPACE);
if (lastShutdownReason === ShutdownReason.RELOAD) {
this._startupKind = StartupKind.ReloadedWindow;
} else if (lastShutdownReason === ShutdownReason.LOAD) {
this._startupKind = StartupKind.ReopenedWindow;
} else {
this._startupKind = StartupKind.NewWindow;
}
super();
this._logService.trace(`lifecycle: starting up (startup kind: ${this._startupKind})`);
this._startupKind = this.resolveStartupKind();
this._registerListeners();
this.registerListeners();
}
private _registerListeners(): void {
const windowId = this._windowService.getCurrentWindowId();
private resolveStartupKind(): StartupKind {
const lastShutdownReason = this.storageService.getInteger(LifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE);
this.storageService.remove(LifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE);
let startupKind: StartupKind;
if (lastShutdownReason === ShutdownReason.RELOAD) {
startupKind = StartupKind.ReloadedWindow;
} else if (lastShutdownReason === ShutdownReason.LOAD) {
startupKind = StartupKind.ReopenedWindow;
} else {
startupKind = StartupKind.NewWindow;
}
this.logService.trace(`lifecycle: starting up (startup kind: ${this._startupKind})`);
return startupKind;
}
private registerListeners(): void {
const windowId = this.windowService.getCurrentWindowId();
// Main side indicates that window is about to unload, check for vetos
ipc.on('vscode:onBeforeUnload', (event, reply: { okChannel: string, cancelChannel: string, reason: ShutdownReason }) => {
this._logService.trace(`lifecycle: onBeforeUnload (reason: ${reply.reason})`);
this.logService.trace(`lifecycle: onBeforeUnload (reason: ${reply.reason})`);
// store shutdown reason to retrieve next startup
this._storageService.store(LifecycleService._lastShutdownReasonKey, JSON.stringify(reply.reason), StorageScope.WORKSPACE);
this.storageService.store(LifecycleService.LAST_SHUTDOWN_REASON_KEY, JSON.stringify(reply.reason), StorageScope.WORKSPACE);
// trigger onWillShutdown events and veto collecting
this.onBeforeUnload(reply.reason).then(veto => {
this.handleWillShutdown(reply.reason).then(veto => {
if (veto) {
this._logService.trace('lifecycle: onBeforeUnload prevented via veto');
this._storageService.remove(LifecycleService._lastShutdownReasonKey, StorageScope.WORKSPACE);
this.logService.trace('lifecycle: onBeforeUnload prevented via veto');
this.storageService.remove(LifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE);
ipc.send(reply.cancelChannel, windowId);
} else {
this._logService.trace('lifecycle: onBeforeUnload continues without veto');
this.logService.trace('lifecycle: onBeforeUnload continues without veto');
ipc.send(reply.okChannel, windowId);
}
});
@ -74,14 +93,16 @@ export class LifecycleService implements ILifecycleService {
// Main side indicates that we will indeed shutdown
ipc.on('vscode:onWillUnload', (event, reply: { replyChannel: string, reason: ShutdownReason }) => {
this._logService.trace(`lifecycle: onWillUnload (reason: ${reply.reason})`);
this.logService.trace(`lifecycle: onWillUnload (reason: ${reply.reason})`);
this._onShutdown.fire(reply.reason);
ipc.send(reply.replyChannel, windowId);
// trigger onShutdown events and joining
return this.handleShutdown(reply.reason).then(() => {
ipc.send(reply.replyChannel, windowId);
});
});
}
private onBeforeUnload(reason: ShutdownReason): TPromise<boolean> {
private handleWillShutdown(reason: ShutdownReason): TPromise<boolean> {
const vetos: (boolean | Thenable<boolean>)[] = [];
this._onWillShutdown.fire({
@ -91,11 +112,28 @@ export class LifecycleService implements ILifecycleService {
reason
});
return handleVetos(vetos, err => this._notificationService.error(toErrorMessage(err)));
return handleVetos(vetos, err => {
this.notificationService.error(toErrorMessage(err));
onUnexpectedError(err);
});
}
get phase(): LifecyclePhase {
return this._phase;
private handleShutdown(reason: ShutdownReason): Thenable<void> {
const joiners: Thenable<void>[] = [];
this._onShutdown.fire({
join(promise) {
if (promise) {
joiners.push(promise);
}
},
reason
});
return TPromise.join(joiners).then(() => void 0, err => {
this.notificationService.error(toErrorMessage(err));
onUnexpectedError(err);
});
}
set phase(value: LifecyclePhase) {
@ -107,14 +145,14 @@ export class LifecycleService implements ILifecycleService {
return;
}
this._logService.trace(`lifecycle: phase changed (value: ${value})`);
this.logService.trace(`lifecycle: phase changed (value: ${value})`);
this._phase = value;
mark(`LifecyclePhase/${LifecyclePhaseToString(value)}`);
if (this._phaseWhen.has(this._phase)) {
this._phaseWhen.get(this._phase).open();
this._phaseWhen.delete(this._phase);
if (this.phaseWhen.has(this._phase)) {
this.phaseWhen.get(this._phase).open();
this.phaseWhen.delete(this._phase);
}
}
@ -123,24 +161,12 @@ export class LifecycleService implements ILifecycleService {
return Promise.resolve();
}
let barrier = this._phaseWhen.get(phase);
let barrier = this.phaseWhen.get(phase);
if (!barrier) {
barrier = new Barrier();
this._phaseWhen.set(phase, barrier);
this.phaseWhen.set(phase, barrier);
}
return barrier.wait();
}
get startupKind(): StartupKind {
return this._startupKind;
}
get onWillShutdown(): Event<ShutdownEvent> {
return this._onWillShutdown.event;
}
get onShutdown(): Event<ShutdownReason> {
return this._onShutdown.event;
}
}

View file

@ -13,6 +13,7 @@ import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { ReadyState } from 'vs/platform/windows/common/windows';
import { handleVetos } from 'vs/platform/lifecycle/common/lifecycle';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
import { Disposable } from 'vs/base/common/lifecycle';
export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');
@ -40,7 +41,7 @@ export interface ILifecycleService {
/**
* Will be true if the program was requested to quit.
*/
isQuitRequested: boolean;
quitRequested: boolean;
/**
* Due to the way we handle lifecycle with eventing, the general app.on('before-quit')
@ -68,53 +69,65 @@ export interface ILifecycleService {
*/
onBeforeWindowUnload: Event<IWindowUnloadEvent>;
ready(): void;
registerWindow(window: ICodeWindow): void;
/**
* Close a window for the provided reason. Shutdown handlers are triggered.
*/
unload(window: ICodeWindow, reason: UnloadReason): TPromise<boolean /* veto */>;
/**
* Restart the application with optional arguments (CLI). Shutdown handlers are triggered.
*/
relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): void;
/**
* Shutdown the application normally. Shutdown handlers are triggered.
*/
quit(fromUpdate?: boolean): TPromise<boolean /* veto */>;
/**
* Forcefully shutdown the application. No shutdown handlers are triggered.
*/
kill(code?: number): void;
ready(): void;
registerWindow(window: ICodeWindow): void;
}
export class LifecycleService implements ILifecycleService {
export class LifecycleService extends Disposable implements ILifecycleService {
_serviceBrand: any;
private static readonly QUIT_FROM_RESTART_MARKER = 'quit.from.restart'; // use a marker to find out if the session was restarted
private windowToCloseRequest: { [windowId: string]: boolean };
private quitRequested: boolean;
private windowToCloseRequest: { [windowId: string]: boolean } = Object.create(null);
private pendingQuitPromise: TPromise<boolean>;
private pendingQuitPromiseComplete: TValueCallback<boolean>;
private oneTimeListenerTokenGenerator: number;
private _wasRestarted: boolean;
private windowCounter: number;
private oneTimeListenerTokenGenerator = 0;
private windowCounter = 0;
private _onBeforeShutdown = new Emitter<void>();
onBeforeShutdown: Event<void> = this._onBeforeShutdown.event;
private _quitRequested = false;
get quitRequested(): boolean { return this._quitRequested; }
private _onShutdown = new Emitter<void>();
onShutdown: Event<void> = this._onShutdown.event;
private _wasRestarted: boolean = false;
get wasRestarted(): boolean { return this._wasRestarted; }
private _onBeforeWindowClose = new Emitter<ICodeWindow>();
onBeforeWindowClose: Event<ICodeWindow> = this._onBeforeWindowClose.event;
private readonly _onBeforeShutdown = this._register(new Emitter<void>());
readonly onBeforeShutdown: Event<void> = this._onBeforeShutdown.event;
private _onBeforeWindowUnload = new Emitter<IWindowUnloadEvent>();
onBeforeWindowUnload: Event<IWindowUnloadEvent> = this._onBeforeWindowUnload.event;
private readonly _onShutdown = this._register(new Emitter<void>());
readonly onShutdown: Event<void> = this._onShutdown.event;
private readonly _onBeforeWindowClose = this._register(new Emitter<ICodeWindow>());
readonly onBeforeWindowClose: Event<ICodeWindow> = this._onBeforeWindowClose.event;
private readonly _onBeforeWindowUnload = this._register(new Emitter<IWindowUnloadEvent>());
readonly onBeforeWindowUnload: Event<IWindowUnloadEvent> = this._onBeforeWindowUnload.event;
constructor(
@ILogService private logService: ILogService,
@IStateService private stateService: IStateService
) {
this.windowToCloseRequest = Object.create(null);
this.quitRequested = false;
this.oneTimeListenerTokenGenerator = 0;
this._wasRestarted = false;
this.windowCounter = 0;
super();
this.handleRestarted();
}
@ -127,14 +140,6 @@ export class LifecycleService implements ILifecycleService {
}
}
get wasRestarted(): boolean {
return this._wasRestarted;
}
get isQuitRequested(): boolean {
return !!this.quitRequested;
}
ready(): void {
this.registerListeners();
}
@ -145,12 +150,12 @@ export class LifecycleService implements ILifecycleService {
app.on('before-quit', e => {
this.logService.trace('Lifecycle#before-quit');
if (this.quitRequested) {
if (this._quitRequested) {
this.logService.trace('Lifecycle#before-quit - returning because quit was already requested');
return;
}
this.quitRequested = true;
this._quitRequested = true;
// Emit event to indicate that we are about to shutdown
this.logService.trace('Lifecycle#onBeforeShutdown.fire()');
@ -170,7 +175,7 @@ export class LifecycleService implements ILifecycleService {
// Windows/Linux: we quit when all windows have closed
// Mac: we only quit when quit was requested
if (this.quitRequested || process.platform !== 'darwin') {
if (this._quitRequested || process.platform !== 'darwin') {
app.quit();
}
});
@ -206,7 +211,7 @@ export class LifecycleService implements ILifecycleService {
window.close();
} else {
this.quitRequested = false;
this._quitRequested = false;
delete this.windowToCloseRequest[windowId];
}
});
@ -223,7 +228,7 @@ export class LifecycleService implements ILifecycleService {
// if there are no more code windows opened, fire the onShutdown event, unless
// we are on macOS where it is perfectly fine to close the last window and
// the application continues running (unless quit was actually requested)
if (this.windowCounter === 0 && (!isMacintosh || this.isQuitRequested)) {
if (this.windowCounter === 0 && (!isMacintosh || this._quitRequested)) {
this.logService.trace('Lifecycle#onShutdown.fire()');
this._onShutdown.fire();
}
@ -239,7 +244,7 @@ export class LifecycleService implements ILifecycleService {
this.logService.trace('Lifecycle#unload()', window.id);
const windowUnloadReason = this.quitRequested ? UnloadReason.QUIT : reason;
const windowUnloadReason = this._quitRequested ? UnloadReason.QUIT : reason;
// first ask the window itself if it vetos the unload
return this.onBeforeUnloadWindowInRenderer(window, windowUnloadReason).then(veto => {

View file

@ -4,59 +4,77 @@
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
export const ID = 'storageService';
export const IStorageService = createDecorator<IStorageService>(ID);
export const IStorageService = createDecorator<IStorageService>('storageService');
export interface IStorageService {
_serviceBrand: any;
/**
* Store a string value under the given key to local storage.
*
* The optional scope argument allows to define the scope of the operation.
* Emitted whenever data is updated or deleted.
*/
store(key: string, value: any, scope?: StorageScope): void;
readonly onDidChangeStorage: Event<IWorkspaceStorageChangeEvent>;
/**
* Delete an element stored under the provided key from local storage.
*
* The optional scope argument allows to define the scope of the operation.
* Emitted when the storage is about to persist. This is the right time
* to persist data to ensure it is stored before the application shuts
* down.
*/
remove(key: string, scope?: StorageScope): void;
readonly onWillSaveState: Event<ShutdownReason>;
/**
* Retrieve an element stored with the given key from local storage. Use
* Retrieve an element stored with the given key from storage. Use
* the provided defaultValue if the element is null or undefined.
*
* The optional scope argument allows to define the scope of the operation.
* The scope argument allows to define the scope of the storage
* operation to either the current workspace only or all workspaces.
*/
get(key: string, scope?: StorageScope, defaultValue?: string): string | undefined;
get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined;
/**
* Retrieve an element stored with the given key from local storage. Use
* the provided defaultValue if the element is null or undefined. The element
* will be converted to a number using parseInt with a base of 10.
*
* The optional scope argument allows to define the scope of the operation.
*/
getInteger(key: string, scope?: StorageScope, defaultValue?: number): number | undefined;
/**
* Retrieve an element stored with the given key from local storage. Use
* Retrieve an element stored with the given key from storage. Use
* the provided defaultValue if the element is null or undefined. The element
* will be converted to a boolean.
*
* The optional scope argument allows to define the scope of the operation.
* The scope argument allows to define the scope of the storage
* operation to either the current workspace only or all workspaces.
*/
getBoolean(key: string, scope?: StorageScope, defaultValue?: boolean): boolean | undefined;
getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean | undefined;
/**
* Retrieve an element stored with the given key from storage. Use
* the provided defaultValue if the element is null or undefined. The element
* will be converted to a number using parseInt with a base of 10.
*
* The scope argument allows to define the scope of the storage
* operation to either the current workspace only or all workspaces.
*/
getInteger(key: string, scope: StorageScope, fallbackValue?: number): number | undefined;
/**
* Store a string value under the given key to storage. The value will
* be converted to a string.
*
* The scope argument allows to define the scope of the storage
* operation to either the current workspace only or all workspaces.
*/
store(key: string, value: any, scope: StorageScope): Promise<void>;
/**
* Delete an element stored under the provided key from storage.
*
* The scope argument allows to define the scope of the storage
* operation to either the current workspace only or all workspaces.
*/
remove(key: string, scope: StorageScope): Promise<void>;
}
export const enum StorageScope {
/**
* The stored data will be scoped to all workspaces of this domain.
* The stored data will be scoped to all workspaces.
*/
GLOBAL,
@ -66,12 +84,34 @@ export const enum StorageScope {
WORKSPACE
}
export interface IWorkspaceStorageChangeEvent {
key: string;
scope: StorageScope;
}
export const NullStorageService: IStorageService = {
_serviceBrand: undefined,
store() { return undefined; },
remove() { return undefined; },
get(a, b, defaultValue) { return defaultValue; },
getInteger(a, b, defaultValue) { return defaultValue; },
getBoolean(a, b, defaultValue) { return defaultValue; }
};
onDidChangeStorage: Event.None,
onWillSaveState: Event.None,
get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined {
return fallbackValue;
},
getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean | undefined {
return fallbackValue;
},
getInteger(key: string, scope: StorageScope, fallbackValue?: number): number | undefined {
return fallbackValue;
},
store(key: string, value: any, scope: StorageScope): Promise<void> {
return Promise.resolve();
},
remove(key: string, scope: StorageScope): Promise<void> {
return Promise.resolve();
}
};

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IStorage, StorageService } from 'vs/platform/storage/common/storageService';
import { StorageLegacyService, IStorageLegacy } from 'vs/platform/storage/common/storageLegacyService';
import { endsWith, startsWith, rtrim } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
@ -29,8 +29,8 @@ import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
* => no longer being used (used for empty workspaces previously)
*/
const EMPTY_WORKSPACE_PREFIX = `${StorageService.COMMON_PREFIX}workspace/empty:`;
const MULTI_ROOT_WORKSPACE_PREFIX = `${StorageService.COMMON_PREFIX}workspace/root:`;
const EMPTY_WORKSPACE_PREFIX = `${StorageLegacyService.COMMON_PREFIX}workspace/empty:`;
const MULTI_ROOT_WORKSPACE_PREFIX = `${StorageLegacyService.COMMON_PREFIX}workspace/root:`;
export type StorageObject = { [key: string]: string };
@ -44,7 +44,7 @@ export interface IParsedStorage {
/**
* Parses the local storage implementation into global, multi root, folder and empty storage.
*/
export function parseStorage(storage: IStorage): IParsedStorage {
export function parseStorage(storage: IStorageLegacy): IParsedStorage {
const globalStorage = new Map<string, string>();
const folderWorkspacesStorage = new Map<string /* workspace file resource */, StorageObject>();
const emptyWorkspacesStorage = new Map<string /* empty workspace id */, StorageObject>();
@ -55,14 +55,14 @@ export function parseStorage(storage: IStorage): IParsedStorage {
const key = storage.key(i);
// Workspace Storage (storage://workspace/)
if (startsWith(key, StorageService.WORKSPACE_PREFIX)) {
if (startsWith(key, StorageLegacyService.WORKSPACE_PREFIX)) {
// We are looking for key: storage://workspace/<folder>/workspaceIdentifier to be able to find all folder
// paths that are known to the storage. is the only way how to parse all folder paths known in storage.
if (endsWith(key, StorageService.WORKSPACE_IDENTIFIER)) {
if (endsWith(key, StorageLegacyService.WORKSPACE_IDENTIFIER)) {
// storage://workspace/<folder>/workspaceIdentifier => <folder>/
let workspace = key.substring(StorageService.WORKSPACE_PREFIX.length, key.length - StorageService.WORKSPACE_IDENTIFIER.length);
let workspace = key.substring(StorageLegacyService.WORKSPACE_PREFIX.length, key.length - StorageLegacyService.WORKSPACE_IDENTIFIER.length);
// macOS/Unix: Users/name/folder/
// Windows: c%3A/Users/name/folder/
@ -76,7 +76,7 @@ export function parseStorage(storage: IStorage): IParsedStorage {
}
// storage://workspace/<folder>/workspaceIdentifier => storage://workspace/<folder>/
const prefix = key.substr(0, key.length - StorageService.WORKSPACE_IDENTIFIER.length);
const prefix = key.substr(0, key.length - StorageLegacyService.WORKSPACE_IDENTIFIER.length);
workspaces.push({ prefix, resource: workspace });
}
@ -120,11 +120,11 @@ export function parseStorage(storage: IStorage): IParsedStorage {
}
// Global Storage (storage://global)
else if (startsWith(key, StorageService.GLOBAL_PREFIX)) {
else if (startsWith(key, StorageLegacyService.GLOBAL_PREFIX)) {
// storage://global/someKey => someKey
const globalStorageKey = key.substr(StorageService.GLOBAL_PREFIX.length);
if (startsWith(globalStorageKey, StorageService.COMMON_PREFIX)) {
const globalStorageKey = key.substr(StorageLegacyService.GLOBAL_PREFIX.length);
if (startsWith(globalStorageKey, StorageLegacyService.COMMON_PREFIX)) {
continue; // filter out faulty keys that have the form storage://something/storage://
}
@ -168,7 +168,7 @@ export function parseStorage(storage: IStorage): IParsedStorage {
};
}
export function migrateStorageToMultiRootWorkspace(fromWorkspaceId: string, toWorkspace: IWorkspaceIdentifier, storage: IStorage): string {
export function migrateStorageToMultiRootWorkspace(fromWorkspaceId: string, toWorkspace: IWorkspaceIdentifier, storage: IStorageLegacy): string {
const parsed = parseStorage(storage);
const newWorkspaceId = URI.from({ path: toWorkspace.id, scheme: 'root' }).toString();
@ -186,11 +186,11 @@ export function migrateStorageToMultiRootWorkspace(fromWorkspaceId: string, toWo
// Migrate existing storage to new workspace id
if (storageForWorkspace) {
Object.keys(storageForWorkspace).forEach(key => {
if (key === StorageService.WORKSPACE_IDENTIFIER) {
if (key === StorageLegacyService.WORKSPACE_IDENTIFIER) {
return; // make sure to never migrate the workspace identifier
}
storage.setItem(`${StorageService.WORKSPACE_PREFIX}${newWorkspaceId}/${key}`, storageForWorkspace[key]);
storage.setItem(`${StorageLegacyService.WORKSPACE_PREFIX}${newWorkspaceId}/${key}`, storageForWorkspace[key]);
});
}

View file

@ -6,11 +6,11 @@
import * as types from 'vs/base/common/types';
import * as errors from 'vs/base/common/errors';
import * as strings from 'vs/base/common/strings';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import * as perf from 'vs/base/common/performance';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
// Browser localStorage interface
export interface IStorage {
export interface IStorageLegacy {
length: number;
key(index: number): string;
setItem(key: string, value: any): void;
@ -18,25 +18,87 @@ export interface IStorage {
removeItem(key: string): void;
}
export class StorageService implements IStorageService {
export const ID = 'storageLegacyService';
export const IStorageLegacyService = createDecorator<IStorageLegacyService>(ID);
export interface IStorageLegacyService {
_serviceBrand: any;
/**
* Store a string value under the given key to local storage.
*
* The optional scope argument allows to define the scope of the operation.
*/
store(key: string, value: any, scope?: StorageLegacyScope): void;
/**
* Delete an element stored under the provided key from local storage.
*
* The optional scope argument allows to define the scope of the operation.
*/
remove(key: string, scope?: StorageLegacyScope): void;
/**
* Retrieve an element stored with the given key from local storage. Use
* the provided defaultValue if the element is null or undefined.
*
* The optional scope argument allows to define the scope of the operation.
*/
get(key: string, scope?: StorageLegacyScope, defaultValue?: string): string | undefined;
/**
* Retrieve an element stored with the given key from local storage. Use
* the provided defaultValue if the element is null or undefined. The element
* will be converted to a number using parseInt with a base of 10.
*
* The optional scope argument allows to define the scope of the operation.
*/
getInteger(key: string, scope?: StorageLegacyScope, defaultValue?: number): number | undefined;
/**
* Retrieve an element stored with the given key from local storage. Use
* the provided defaultValue if the element is null or undefined. The element
* will be converted to a boolean.
*
* The optional scope argument allows to define the scope of the operation.
*/
getBoolean(key: string, scope?: StorageLegacyScope, defaultValue?: boolean): boolean | undefined;
}
export const enum StorageLegacyScope {
/**
* The stored data will be scoped to all workspaces of this domain.
*/
GLOBAL,
/**
* The stored data will be scoped to the current workspace.
*/
WORKSPACE
}
export class StorageLegacyService implements IStorageLegacyService {
_serviceBrand: any;
static readonly COMMON_PREFIX = 'storage://';
static readonly GLOBAL_PREFIX = `${StorageService.COMMON_PREFIX}global/`;
static readonly WORKSPACE_PREFIX = `${StorageService.COMMON_PREFIX}workspace/`;
static readonly GLOBAL_PREFIX = `${StorageLegacyService.COMMON_PREFIX}global/`;
static readonly WORKSPACE_PREFIX = `${StorageLegacyService.COMMON_PREFIX}workspace/`;
static readonly WORKSPACE_IDENTIFIER = 'workspaceidentifier';
static readonly NO_WORKSPACE_IDENTIFIER = '__$noWorkspace__';
private _workspaceStorage: IStorage;
private _globalStorage: IStorage;
private _workspaceStorage: IStorageLegacy;
private _globalStorage: IStorageLegacy;
private workspaceKey: string;
private _workspaceId: string;
constructor(
globalStorage: IStorage,
workspaceStorage: IStorage,
globalStorage: IStorageLegacy,
workspaceStorage: IStorageLegacy,
workspaceId?: string,
legacyWorkspaceId?: number
) {
@ -63,17 +125,17 @@ export class StorageService implements IStorageService {
}
}
get globalStorage(): IStorage {
get globalStorage(): IStorageLegacy {
return this._globalStorage;
}
get workspaceStorage(): IStorage {
get workspaceStorage(): IStorageLegacy {
return this._workspaceStorage;
}
private getWorkspaceKey(id?: string): string {
if (!id) {
return StorageService.NO_WORKSPACE_IDENTIFIER;
return StorageLegacyService.NO_WORKSPACE_IDENTIFIER;
}
// Special case file:// URIs: strip protocol from key to produce shorter key
@ -90,18 +152,18 @@ export class StorageService implements IStorageService {
// Get stored identifier from storage
perf.mark('willReadWorkspaceIdentifier');
const id = this.getInteger(StorageService.WORKSPACE_IDENTIFIER, StorageScope.WORKSPACE);
const id = this.getInteger(StorageLegacyService.WORKSPACE_IDENTIFIER, StorageLegacyScope.WORKSPACE);
perf.mark('didReadWorkspaceIdentifier');
// If identifier differs, assume the workspace got recreated and thus clean all storage for this workspace
if (types.isNumber(id) && workspaceUid !== id) {
const keyPrefix = this.toStorageKey('', StorageScope.WORKSPACE);
const keyPrefix = this.toStorageKey('', StorageLegacyScope.WORKSPACE);
const toDelete: string[] = [];
const length = this._workspaceStorage.length;
for (let i = 0; i < length; i++) {
const key = this._workspaceStorage.key(i);
if (key.indexOf(StorageService.WORKSPACE_PREFIX) < 0) {
if (key.indexOf(StorageLegacyService.WORKSPACE_PREFIX) < 0) {
continue; // ignore stored things that don't belong to storage service or are defined globally
}
@ -119,12 +181,12 @@ export class StorageService implements IStorageService {
// Store workspace identifier now
if (workspaceUid !== id) {
this.store(StorageService.WORKSPACE_IDENTIFIER, workspaceUid, StorageScope.WORKSPACE);
this.store(StorageLegacyService.WORKSPACE_IDENTIFIER, workspaceUid, StorageLegacyScope.WORKSPACE);
}
}
store(key: string, value: any, scope = StorageScope.GLOBAL): void {
const storage = (scope === StorageScope.GLOBAL) ? this._globalStorage : this._workspaceStorage;
store(key: string, value: any, scope = StorageLegacyScope.GLOBAL): void {
const storage = (scope === StorageLegacyScope.GLOBAL) ? this._globalStorage : this._workspaceStorage;
if (types.isUndefinedOrNull(value)) {
this.remove(key, scope); // we cannot store null or undefined, in that case we remove the key
@ -141,8 +203,8 @@ export class StorageService implements IStorageService {
}
}
get(key: string, scope = StorageScope.GLOBAL, defaultValue?: any): string {
const storage = (scope === StorageScope.GLOBAL) ? this._globalStorage : this._workspaceStorage;
get(key: string, scope = StorageLegacyScope.GLOBAL, defaultValue?: any): string {
const storage = (scope === StorageLegacyScope.GLOBAL) ? this._globalStorage : this._workspaceStorage;
const value = storage.getItem(this.toStorageKey(key, scope));
if (types.isUndefinedOrNull(value)) {
@ -152,7 +214,7 @@ export class StorageService implements IStorageService {
return value;
}
getInteger(key: string, scope = StorageScope.GLOBAL, defaultValue?: number): number {
getInteger(key: string, scope = StorageLegacyScope.GLOBAL, defaultValue?: number): number {
const value = this.get(key, scope, defaultValue);
if (types.isUndefinedOrNull(value)) {
@ -162,7 +224,7 @@ export class StorageService implements IStorageService {
return parseInt(value, 10);
}
getBoolean(key: string, scope = StorageScope.GLOBAL, defaultValue?: boolean): boolean {
getBoolean(key: string, scope = StorageLegacyScope.GLOBAL, defaultValue?: boolean): boolean {
const value = this.get(key, scope, defaultValue);
if (types.isUndefinedOrNull(value)) {
@ -176,24 +238,24 @@ export class StorageService implements IStorageService {
return value ? true : false;
}
remove(key: string, scope = StorageScope.GLOBAL): void {
const storage = (scope === StorageScope.GLOBAL) ? this._globalStorage : this._workspaceStorage;
remove(key: string, scope = StorageLegacyScope.GLOBAL): void {
const storage = (scope === StorageLegacyScope.GLOBAL) ? this._globalStorage : this._workspaceStorage;
const storageKey = this.toStorageKey(key, scope);
// Remove
storage.removeItem(storageKey);
}
private toStorageKey(key: string, scope: StorageScope): string {
if (scope === StorageScope.GLOBAL) {
return StorageService.GLOBAL_PREFIX + key.toLowerCase();
private toStorageKey(key: string, scope: StorageLegacyScope): string {
if (scope === StorageLegacyScope.GLOBAL) {
return StorageLegacyService.GLOBAL_PREFIX + key.toLowerCase();
}
return StorageService.WORKSPACE_PREFIX + this.workspaceKey + key.toLowerCase();
return StorageLegacyService.WORKSPACE_PREFIX + this.workspaceKey + key.toLowerCase();
}
}
export class InMemoryLocalStorage implements IStorage {
export class InMemoryLocalStorage implements IStorageLegacy {
private store: { [key: string]: string; };
constructor() {

View file

@ -0,0 +1,206 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { ILogService } from 'vs/platform/log/common/log';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { Storage, IStorageLoggingOptions } from 'vs/base/node/storage';
import { IStorageLegacyService, StorageLegacyScope } from 'vs/platform/storage/common/storageLegacyService';
import { addDisposableListener } from 'vs/base/browser/dom';
import { startsWith } from 'vs/base/common/strings';
import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
export class StorageService extends Disposable implements IStorageService {
_serviceBrand: any;
private static IN_MEMORY_PATH = ':memory:';
private _onDidChangeStorage: Emitter<IWorkspaceStorageChangeEvent> = this._register(new Emitter<IWorkspaceStorageChangeEvent>());
get onDidChangeStorage(): Event<IWorkspaceStorageChangeEvent> { return this._onDidChangeStorage.event; }
private _onWillClose: Emitter<ShutdownReason> = this._register(new Emitter<ShutdownReason>());
get onWillSaveState(): Event<ShutdownReason> { return this._onWillClose.event; }
private globalStorage: Storage;
private workspaceStorage: Storage;
constructor(
workspaceDBPath: string,
@ILogService logService: ILogService,
@IEnvironmentService environmentService: IEnvironmentService
) {
super();
const loggingOptions: IStorageLoggingOptions = {
info: environmentService.verbose || environmentService.logStorage,
infoLogger: msg => logService.info(msg),
errorLogger: error => logService.error(error)
};
const useInMemoryStorage = !!environmentService.extensionTestsPath; // never keep any state when running extension tests
this.globalStorage = new Storage({ path: useInMemoryStorage ? StorageService.IN_MEMORY_PATH : StorageService.IN_MEMORY_PATH, logging: loggingOptions });
this.workspaceStorage = new Storage({ path: useInMemoryStorage ? StorageService.IN_MEMORY_PATH : workspaceDBPath, logging: loggingOptions });
this.registerListeners();
}
private registerListeners(): void {
this._register(this.globalStorage.onDidChangeStorage(key => this.handleDidChangeStorage(key, StorageScope.GLOBAL)));
this._register(this.workspaceStorage.onDidChangeStorage(key => this.handleDidChangeStorage(key, StorageScope.WORKSPACE)));
}
private handleDidChangeStorage(key: string, scope: StorageScope): void {
this._onDidChangeStorage.fire({ key, scope });
}
init(): Promise<void> {
return Promise.all([this.globalStorage.init(), this.workspaceStorage.init()]).then(() => void 0);
}
get(key: string, scope: StorageScope, fallbackValue?: any): string {
return this.getStorage(scope).get(key, fallbackValue);
}
getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean {
return this.getStorage(scope).getBoolean(key, fallbackValue);
}
getInteger(key: string, scope: StorageScope, fallbackValue?: number): number {
return this.getStorage(scope).getInteger(key, fallbackValue);
}
store(key: string, value: any, scope: StorageScope): Promise<void> {
return this.getStorage(scope).set(key, value);
}
remove(key: string, scope: StorageScope): Promise<void> {
return this.getStorage(scope).delete(key);
}
close(reason: ShutdownReason): Promise<void> {
// Signal as event so that clients can still store data
this._onWillClose.fire(reason);
// Do it
return Promise.all([
this.globalStorage.close(),
this.workspaceStorage.close()
]).then(() => void 0);
}
private getStorage(scope: StorageScope): Storage {
return scope === StorageScope.GLOBAL ? this.globalStorage : this.workspaceStorage;
}
}
export class DelegatingStorageService extends Disposable implements IStorageService {
_serviceBrand: any;
private _onDidChangeStorage: Emitter<IWorkspaceStorageChangeEvent> = this._register(new Emitter<IWorkspaceStorageChangeEvent>());
get onDidChangeStorage(): Event<IWorkspaceStorageChangeEvent> { return this._onDidChangeStorage.event; }
private _onWillClose: Emitter<ShutdownReason> = this._register(new Emitter<ShutdownReason>());
get onWillSaveState(): Event<ShutdownReason> { return this._onWillClose.event; }
private closed: boolean;
constructor(
@IStorageService private storageService: StorageService,
@IStorageLegacyService private storageLegacyService: IStorageLegacyService,
@ILogService private logService: ILogService,
@IEnvironmentService environmentService: IEnvironmentService
) {
super();
this.registerListeners();
}
private registerListeners(): void {
this._register(this.storageService.onDidChangeStorage(e => this._onDidChangeStorage.fire(e)));
this._register(this.storageService.onWillSaveState(reason => this._onWillClose.fire(reason)));
const globalKeyMarker = 'storage://global/';
this._register(addDisposableListener(window, 'storage', (e: StorageEvent) => {
if (startsWith(e.key, globalKeyMarker)) {
const key = e.key.substr(globalKeyMarker.length);
this._onDidChangeStorage.fire({ key, scope: StorageScope.GLOBAL });
}
}));
}
get(key: string, scope: StorageScope, fallbackValue?: any): string {
const localStorageValue = this.storageLegacyService.get(key, this.convertScope(scope), fallbackValue);
const dbValue = this.storageService.get(key, scope, localStorageValue);
this.assertStorageValue(key, scope, dbValue, localStorageValue);
return dbValue;
}
getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean {
const localStorageValue = this.storageLegacyService.getBoolean(key, this.convertScope(scope), fallbackValue);
const dbValue = this.storageService.getBoolean(key, scope, localStorageValue);
this.assertStorageValue(key, scope, dbValue, localStorageValue);
return dbValue;
}
getInteger(key: string, scope: StorageScope, fallbackValue?: number): number {
const localStorageValue = this.storageLegacyService.getInteger(key, this.convertScope(scope), fallbackValue);
const dbValue = this.storageService.getInteger(key, scope, localStorageValue);
this.assertStorageValue(key, scope, dbValue, localStorageValue);
return dbValue;
}
private assertStorageValue(key: string, scope: StorageScope, dbValue: any, storageValue: any): void {
if (dbValue !== storageValue) {
this.logService.error(`Unexpected storage value (key: ${key}, scope: ${scope === StorageScope.GLOBAL ? 'global' : 'workspace'}), actual: ${dbValue}, expected: ${storageValue}`);
}
}
store(key: string, value: any, scope: StorageScope): Promise<void> {
if (this.closed) {
this.logService.warn(`Unsupported write (store) access after close (key: ${key})`);
return Promise.resolve(); // prevent writing after close to detect late write access
}
this.storageLegacyService.store(key, value, this.convertScope(scope));
return this.storageService.store(key, value, scope);
}
remove(key: string, scope: StorageScope): Promise<void> {
if (this.closed) {
this.logService.warn(`Unsupported write (remove) access after close (key: ${key})`);
return Promise.resolve(); // prevent writing after close to detect late write access
}
this.storageLegacyService.remove(key, this.convertScope(scope));
return this.storageService.remove(key, scope);
}
close(reason: ShutdownReason): Promise<void> {
const promise = this.storageService.close(reason);
this.closed = true;
return promise;
}
private convertScope(scope: StorageScope): StorageLegacyScope {
return scope === StorageScope.GLOBAL ? StorageLegacyScope.GLOBAL : StorageLegacyScope.WORKSPACE;
}
}

View file

@ -4,10 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { StorageService } from 'vs/platform/storage/common/storageService';
import { parseStorage, migrateStorageToMultiRootWorkspace } from 'vs/platform/storage/common/migration';
import { StorageLegacyScope, StorageLegacyService } from 'vs/platform/storage/common/storageLegacyService';
import { parseStorage, migrateStorageToMultiRootWorkspace } from 'vs/platform/storage/common/storageLegacyMigration';
import { URI } from 'vs/base/common/uri';
import { StorageScope } from 'vs/platform/storage/common/storage';
import { startsWith } from 'vs/base/common/strings';
suite('Storage Migration', () => {
@ -92,10 +91,10 @@ suite('Storage Migration', () => {
}
assert.equal(Object.keys(storageToTest).length, expectedKeyCount);
assert.equal(storageToTest['key1'], service.get('key1', StorageScope.WORKSPACE));
assert.equal(storageToTest['key2.something'], service.get('key2.something', StorageScope.WORKSPACE));
assert.equal(storageToTest['key3/special'], service.get('key3/special', StorageScope.WORKSPACE));
assert.equal(storageToTest['key4 space'], service.get('key4 space', StorageScope.WORKSPACE));
assert.equal(storageToTest['key1'], service.get('key1', StorageLegacyScope.WORKSPACE));
assert.equal(storageToTest['key2.something'], service.get('key2.something', StorageLegacyScope.WORKSPACE));
assert.equal(storageToTest['key3/special'], service.get('key3/special', StorageLegacyScope.WORKSPACE));
assert.equal(storageToTest['key4 space'], service.get('key4 space', StorageLegacyScope.WORKSPACE));
});
});
@ -103,38 +102,38 @@ suite('Storage Migration', () => {
const ws1 = URI.file('/some/folder/folder1').toString();
const ws2 = URI.file('/some/folder/folder1/sub1').toString();
const s1 = new StorageService(storage, storage, ws1, Date.now());
const s2 = new StorageService(storage, storage, ws2, Date.now());
const s1 = new StorageLegacyService(storage, storage, ws1, Date.now());
const s2 = new StorageLegacyService(storage, storage, ws2, Date.now());
s1.store('s1key1', 'value1', StorageScope.WORKSPACE);
s1.store('s1key2.something', JSON.stringify({ foo: 'bar' }), StorageScope.WORKSPACE);
s1.store('s1key3/special', true, StorageScope.WORKSPACE);
s1.store('s1key4 space', 4, StorageScope.WORKSPACE);
s1.store('s1key1', 'value1', StorageLegacyScope.WORKSPACE);
s1.store('s1key2.something', JSON.stringify({ foo: 'bar' }), StorageLegacyScope.WORKSPACE);
s1.store('s1key3/special', true, StorageLegacyScope.WORKSPACE);
s1.store('s1key4 space', 4, StorageLegacyScope.WORKSPACE);
s2.store('s2key1', 'value1', StorageScope.WORKSPACE);
s2.store('s2key2.something', JSON.stringify({ foo: 'bar' }), StorageScope.WORKSPACE);
s2.store('s2key3/special', true, StorageScope.WORKSPACE);
s2.store('s2key4 space', 4, StorageScope.WORKSPACE);
s2.store('s2key1', 'value1', StorageLegacyScope.WORKSPACE);
s2.store('s2key2.something', JSON.stringify({ foo: 'bar' }), StorageLegacyScope.WORKSPACE);
s2.store('s2key3/special', true, StorageLegacyScope.WORKSPACE);
s2.store('s2key4 space', 4, StorageLegacyScope.WORKSPACE);
const parsed = parseStorage(storage);
const s1Storage = parsed.folder.get(ws1);
assert.equal(Object.keys(s1Storage).length, 5);
assert.equal(s1Storage['s1key1'], s1.get('s1key1', StorageScope.WORKSPACE));
assert.equal(s1Storage['s1key2.something'], s1.get('s1key2.something', StorageScope.WORKSPACE));
assert.equal(s1Storage['s1key3/special'], s1.get('s1key3/special', StorageScope.WORKSPACE));
assert.equal(s1Storage['s1key4 space'], s1.get('s1key4 space', StorageScope.WORKSPACE));
assert.equal(s1Storage['s1key1'], s1.get('s1key1', StorageLegacyScope.WORKSPACE));
assert.equal(s1Storage['s1key2.something'], s1.get('s1key2.something', StorageLegacyScope.WORKSPACE));
assert.equal(s1Storage['s1key3/special'], s1.get('s1key3/special', StorageLegacyScope.WORKSPACE));
assert.equal(s1Storage['s1key4 space'], s1.get('s1key4 space', StorageLegacyScope.WORKSPACE));
const s2Storage = parsed.folder.get(ws2);
assert.equal(Object.keys(s2Storage).length, 5);
assert.equal(s2Storage['s2key1'], s2.get('s2key1', StorageScope.WORKSPACE));
assert.equal(s2Storage['s2key2.something'], s2.get('s2key2.something', StorageScope.WORKSPACE));
assert.equal(s2Storage['s2key3/special'], s2.get('s2key3/special', StorageScope.WORKSPACE));
assert.equal(s2Storage['s2key4 space'], s2.get('s2key4 space', StorageScope.WORKSPACE));
assert.equal(s2Storage['s2key1'], s2.get('s2key1', StorageLegacyScope.WORKSPACE));
assert.equal(s2Storage['s2key2.something'], s2.get('s2key2.something', StorageLegacyScope.WORKSPACE));
assert.equal(s2Storage['s2key3/special'], s2.get('s2key3/special', StorageLegacyScope.WORKSPACE));
assert.equal(s2Storage['s2key4 space'], s2.get('s2key4 space', StorageLegacyScope.WORKSPACE));
});
function createService(workspaceId?: string): StorageService {
const service = new StorageService(storage, storage, workspaceId, workspaceId && startsWith(workspaceId, 'file:') ? Date.now() : void 0);
function createService(workspaceId?: string): StorageLegacyService {
const service = new StorageLegacyService(storage, storage, workspaceId, workspaceId && startsWith(workspaceId, 'file:') ? Date.now() : void 0);
// Unrelated
storage.setItem('foo', 'bar');
@ -148,10 +147,10 @@ suite('Storage Migration', () => {
service.store('key4 space', 4);
// Workspace
service.store('key1', 'value1', StorageScope.WORKSPACE);
service.store('key2.something', JSON.stringify({ foo: 'bar' }), StorageScope.WORKSPACE);
service.store('key3/special', true, StorageScope.WORKSPACE);
service.store('key4 space', 4, StorageScope.WORKSPACE);
service.store('key1', 'value1', StorageLegacyScope.WORKSPACE);
service.store('key2.something', JSON.stringify({ foo: 'bar' }), StorageLegacyScope.WORKSPACE);
service.store('key3/special', true, StorageLegacyScope.WORKSPACE);
service.store('key4 space', 4, StorageLegacyScope.WORKSPACE);
return service;
}
@ -164,7 +163,7 @@ suite('Storage Migration', () => {
migrateStorageToMultiRootWorkspace(workspaceToMigrateFrom, { id: '1500007676869', configPath: null }, storage);
const s2 = new StorageService(storage, storage, workspaceToMigrateTo);
const s2 = new StorageLegacyService(storage, storage, workspaceToMigrateTo);
const parsed = parseStorage(storage);
assert.equal(parsed.empty.size, 0);
@ -173,10 +172,10 @@ suite('Storage Migration', () => {
const migratedStorage = parsed.multiRoot.get(workspaceToMigrateTo);
assert.equal(Object.keys(migratedStorage).length, 4);
assert.equal(migratedStorage['key1'], s2.get('key1', StorageScope.WORKSPACE));
assert.equal(migratedStorage['key2.something'], s2.get('key2.something', StorageScope.WORKSPACE));
assert.equal(migratedStorage['key3/special'], s2.get('key3/special', StorageScope.WORKSPACE));
assert.equal(migratedStorage['key4 space'], s2.get('key4 space', StorageScope.WORKSPACE));
assert.equal(migratedStorage['key1'], s2.get('key1', StorageLegacyScope.WORKSPACE));
assert.equal(migratedStorage['key2.something'], s2.get('key2.something', StorageLegacyScope.WORKSPACE));
assert.equal(migratedStorage['key3/special'], s2.get('key3/special', StorageLegacyScope.WORKSPACE));
assert.equal(migratedStorage['key4 space'], s2.get('key4 space', StorageLegacyScope.WORKSPACE));
});
test('Migrate Storage (folder (Windows) => multi root)', () => {
@ -187,7 +186,7 @@ suite('Storage Migration', () => {
migrateStorageToMultiRootWorkspace(workspaceToMigrateFrom, { id: '1500007676869', configPath: null }, storage);
const s2 = new StorageService(storage, storage, workspaceToMigrateTo);
const s2 = new StorageLegacyService(storage, storage, workspaceToMigrateTo);
const parsed = parseStorage(storage);
assert.equal(parsed.empty.size, 0);
@ -196,10 +195,10 @@ suite('Storage Migration', () => {
const migratedStorage = parsed.multiRoot.get(workspaceToMigrateTo);
assert.equal(Object.keys(migratedStorage).length, 4);
assert.equal(migratedStorage['key1'], s2.get('key1', StorageScope.WORKSPACE));
assert.equal(migratedStorage['key2.something'], s2.get('key2.something', StorageScope.WORKSPACE));
assert.equal(migratedStorage['key3/special'], s2.get('key3/special', StorageScope.WORKSPACE));
assert.equal(migratedStorage['key4 space'], s2.get('key4 space', StorageScope.WORKSPACE));
assert.equal(migratedStorage['key1'], s2.get('key1', StorageLegacyScope.WORKSPACE));
assert.equal(migratedStorage['key2.something'], s2.get('key2.something', StorageLegacyScope.WORKSPACE));
assert.equal(migratedStorage['key3/special'], s2.get('key3/special', StorageLegacyScope.WORKSPACE));
assert.equal(migratedStorage['key4 space'], s2.get('key4 space', StorageLegacyScope.WORKSPACE));
});
test('Migrate Storage (folder (Windows UNC) => multi root)', () => {
@ -210,7 +209,7 @@ suite('Storage Migration', () => {
migrateStorageToMultiRootWorkspace(workspaceToMigrateFrom, { id: '1500007676869', configPath: null }, storage);
const s2 = new StorageService(storage, storage, workspaceToMigrateTo);
const s2 = new StorageLegacyService(storage, storage, workspaceToMigrateTo);
const parsed = parseStorage(storage);
assert.equal(parsed.empty.size, 0);
@ -219,10 +218,10 @@ suite('Storage Migration', () => {
const migratedStorage = parsed.multiRoot.get(workspaceToMigrateTo);
assert.equal(Object.keys(migratedStorage).length, 4);
assert.equal(migratedStorage['key1'], s2.get('key1', StorageScope.WORKSPACE));
assert.equal(migratedStorage['key2.something'], s2.get('key2.something', StorageScope.WORKSPACE));
assert.equal(migratedStorage['key3/special'], s2.get('key3/special', StorageScope.WORKSPACE));
assert.equal(migratedStorage['key4 space'], s2.get('key4 space', StorageScope.WORKSPACE));
assert.equal(migratedStorage['key1'], s2.get('key1', StorageLegacyScope.WORKSPACE));
assert.equal(migratedStorage['key2.something'], s2.get('key2.something', StorageLegacyScope.WORKSPACE));
assert.equal(migratedStorage['key3/special'], s2.get('key3/special', StorageLegacyScope.WORKSPACE));
assert.equal(migratedStorage['key4 space'], s2.get('key4 space', StorageLegacyScope.WORKSPACE));
});
test('Migrate Storage (empty => multi root)', () => {
@ -233,7 +232,7 @@ suite('Storage Migration', () => {
migrateStorageToMultiRootWorkspace(workspaceToMigrateFrom, { id: '2500007676869', configPath: null }, storage);
const s2 = new StorageService(storage, storage, workspaceToMigrateTo);
const s2 = new StorageLegacyService(storage, storage, workspaceToMigrateTo);
const parsed = parseStorage(storage);
assert.equal(parsed.empty.size, 1);
@ -242,9 +241,9 @@ suite('Storage Migration', () => {
const migratedStorage = parsed.multiRoot.get(workspaceToMigrateTo);
assert.equal(Object.keys(migratedStorage).length, 4);
assert.equal(migratedStorage['key1'], s2.get('key1', StorageScope.WORKSPACE));
assert.equal(migratedStorage['key2.something'], s2.get('key2.something', StorageScope.WORKSPACE));
assert.equal(migratedStorage['key3/special'], s2.get('key3/special', StorageScope.WORKSPACE));
assert.equal(migratedStorage['key4 space'], s2.get('key4 space', StorageScope.WORKSPACE));
assert.equal(migratedStorage['key1'], s2.get('key1', StorageLegacyScope.WORKSPACE));
assert.equal(migratedStorage['key2.something'], s2.get('key2.something', StorageLegacyScope.WORKSPACE));
assert.equal(migratedStorage['key3/special'], s2.get('key3/special', StorageLegacyScope.WORKSPACE));
assert.equal(migratedStorage['key4 space'], s2.get('key4 space', StorageLegacyScope.WORKSPACE));
});
});

View file

@ -4,94 +4,51 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { StorageScope } from 'vs/platform/storage/common/storage';
import { IWorkspaceContextService, IWorkspace, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { StorageService, InMemoryLocalStorage } from 'vs/platform/storage/common/storageService';
import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
// tslint:disable-next-line:import-patterns
import { TestStorageService } from 'vs/workbench/test/workbenchTestServices';
suite('Workbench StorageSevice', () => {
let contextService: IWorkspaceContextService;
let instantiationService: TestInstantiationService;
setup(() => {
instantiationService = new TestInstantiationService();
contextService = instantiationService.stub(IWorkspaceContextService, <IWorkspaceContextService>{
getWorkbenchState: () => WorkbenchState.FOLDER,
getWorkspace: () => {
return <IWorkspace>TestWorkspace;
}
});
});
suite('StorageService', () => {
test('Remove Data', () => {
let s = new StorageService(new InMemoryLocalStorage(), null, contextService.getWorkspace().id);
s.store('Monaco.IDE.Core.Storage.Test.remove', 'foobar');
assert.strictEqual('foobar', s.get('Monaco.IDE.Core.Storage.Test.remove'));
let storage = new TestStorageService();
storage.store('Monaco.IDE.Core.Storage.Test.remove', 'foobar', StorageScope.GLOBAL);
assert.strictEqual('foobar', storage.get('Monaco.IDE.Core.Storage.Test.remove', StorageScope.GLOBAL));
s.remove('Monaco.IDE.Core.Storage.Test.remove');
assert.ok(!s.get('Monaco.IDE.Core.Storage.Test.remove'));
storage.remove('Monaco.IDE.Core.Storage.Test.remove', StorageScope.GLOBAL);
assert.ok(!storage.get('Monaco.IDE.Core.Storage.Test.remove', StorageScope.GLOBAL));
});
test('Get Data, Integer, Boolean', () => {
let s = new StorageService(new InMemoryLocalStorage(), null, contextService.getWorkspace().id);
let storage = new TestStorageService();
assert.strictEqual(s.get('Monaco.IDE.Core.Storage.Test.get', StorageScope.GLOBAL, 'foobar'), 'foobar');
assert.strictEqual(s.get('Monaco.IDE.Core.Storage.Test.get', StorageScope.GLOBAL, ''), '');
assert.strictEqual(s.get('Monaco.IDE.Core.Storage.Test.getInteger', StorageScope.GLOBAL, 5), 5);
assert.strictEqual(s.get('Monaco.IDE.Core.Storage.Test.getInteger', StorageScope.GLOBAL, 0), 0);
assert.strictEqual(s.get('Monaco.IDE.Core.Storage.Test.getBoolean', StorageScope.GLOBAL, true), true);
assert.strictEqual(s.get('Monaco.IDE.Core.Storage.Test.getBoolean', StorageScope.GLOBAL, false), false);
assert.strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', StorageScope.GLOBAL, 'foobar'), 'foobar');
assert.strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', StorageScope.GLOBAL, ''), '');
assert.strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.getInteger', StorageScope.GLOBAL, 5), 5);
assert.strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.getInteger', StorageScope.GLOBAL, 0), 0);
assert.strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.getBoolean', StorageScope.GLOBAL, true), true);
assert.strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.getBoolean', StorageScope.GLOBAL, false), false);
s.store('Monaco.IDE.Core.Storage.Test.get', 'foobar');
assert.strictEqual(s.get('Monaco.IDE.Core.Storage.Test.get'), 'foobar');
storage.store('Monaco.IDE.Core.Storage.Test.get', 'foobar', StorageScope.GLOBAL);
assert.strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', StorageScope.GLOBAL), 'foobar');
s.store('Monaco.IDE.Core.Storage.Test.get', '');
assert.strictEqual(s.get('Monaco.IDE.Core.Storage.Test.get'), '');
storage.store('Monaco.IDE.Core.Storage.Test.get', '', StorageScope.GLOBAL);
assert.strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', StorageScope.GLOBAL), '');
s.store('Monaco.IDE.Core.Storage.Test.getInteger', 5);
assert.strictEqual(s.getInteger('Monaco.IDE.Core.Storage.Test.getInteger'), 5);
storage.store('Monaco.IDE.Core.Storage.Test.getInteger', 5, StorageScope.GLOBAL);
assert.strictEqual(storage.getInteger('Monaco.IDE.Core.Storage.Test.getInteger', StorageScope.GLOBAL), 5);
s.store('Monaco.IDE.Core.Storage.Test.getInteger', 0);
assert.strictEqual(s.getInteger('Monaco.IDE.Core.Storage.Test.getInteger'), 0);
storage.store('Monaco.IDE.Core.Storage.Test.getInteger', 0, StorageScope.GLOBAL);
assert.strictEqual(storage.getInteger('Monaco.IDE.Core.Storage.Test.getInteger', StorageScope.GLOBAL), 0);
s.store('Monaco.IDE.Core.Storage.Test.getBoolean', true);
assert.strictEqual(s.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean'), true);
storage.store('Monaco.IDE.Core.Storage.Test.getBoolean', true, StorageScope.GLOBAL);
assert.strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', StorageScope.GLOBAL), true);
s.store('Monaco.IDE.Core.Storage.Test.getBoolean', false);
assert.strictEqual(s.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean'), false);
storage.store('Monaco.IDE.Core.Storage.Test.getBoolean', false, StorageScope.GLOBAL);
assert.strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', StorageScope.GLOBAL), false);
assert.strictEqual(s.get('Monaco.IDE.Core.Storage.Test.getDefault', StorageScope.GLOBAL, 'getDefault'), 'getDefault');
assert.strictEqual(s.getInteger('Monaco.IDE.Core.Storage.Test.getIntegerDefault', StorageScope.GLOBAL, 5), 5);
assert.strictEqual(s.getBoolean('Monaco.IDE.Core.Storage.Test.getBooleanDefault', StorageScope.GLOBAL, true), true);
});
test('StorageSevice cleans up when workspace changes', () => {
let storageImpl = new InMemoryLocalStorage();
let time = new Date().getTime();
let s = new StorageService(storageImpl, null, contextService.getWorkspace().id, time);
s.store('key1', 'foobar');
s.store('key2', 'something');
s.store('wkey1', 'foo', StorageScope.WORKSPACE);
s.store('wkey2', 'foo2', StorageScope.WORKSPACE);
s = new StorageService(storageImpl, null, contextService.getWorkspace().id, time);
assert.strictEqual(s.get('key1', StorageScope.GLOBAL), 'foobar');
assert.strictEqual(s.get('key1', StorageScope.WORKSPACE, null), null);
assert.strictEqual(s.get('key2', StorageScope.GLOBAL), 'something');
assert.strictEqual(s.get('wkey1', StorageScope.WORKSPACE), 'foo');
assert.strictEqual(s.get('wkey2', StorageScope.WORKSPACE), 'foo2');
s = new StorageService(storageImpl, null, contextService.getWorkspace().id, time + 100);
assert.strictEqual(s.get('key1', StorageScope.GLOBAL), 'foobar');
assert.strictEqual(s.get('key1', StorageScope.WORKSPACE, null), null);
assert.strictEqual(s.get('key2', StorageScope.GLOBAL), 'something');
assert(!s.get('wkey1', StorageScope.WORKSPACE));
assert(!s.get('wkey2', StorageScope.WORKSPACE));
assert.strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.getDefault', StorageScope.GLOBAL, 'getDefault'), 'getDefault');
assert.strictEqual(storage.getInteger('Monaco.IDE.Core.Storage.Test.getIntegerDefault', StorageScope.GLOBAL, 5), 5);
assert.strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBooleanDefault', StorageScope.GLOBAL, true), true);
});
});

View file

@ -5,8 +5,8 @@
import { TPromise } from 'vs/base/common/winjs.base';
import * as uuid from 'vs/base/common/uuid';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { resolveCommonProperties } from '../node/commonProperties';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
export function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string, version: string, machineId: string, installSourcePath: string): TPromise<{ [name: string]: string }> {
return resolveCommonProperties(commit, version, machineId, installSourcePath).then(result => {
@ -15,13 +15,11 @@ export function resolveWorkbenchCommonProperties(storageService: IStorageService
// __GDPR__COMMON__ "common.version.renderer" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
result['common.version.renderer'] = process.versions && (<any>process).versions['chrome'];
const lastSessionDate = storageService.get('telemetry.lastSessionDate');
const firstSessionDate = storageService.get('telemetry.firstSessionDate') || new Date().toUTCString();
storageService.store('telemetry.firstSessionDate', firstSessionDate);
storageService.store('telemetry.lastSessionDate', new Date().toUTCString());
const lastSessionDate = storageService.get('telemetry.lastSessionDate', StorageScope.GLOBAL);
storageService.store('telemetry.lastSessionDate', new Date().toUTCString(), StorageScope.GLOBAL);
// __GDPR__COMMON__ "common.firstSessionDate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.firstSessionDate'] = firstSessionDate;
result['common.firstSessionDate'] = getOrCreateFirstSessionDate(storageService);
// __GDPR__COMMON__ "common.lastSessionDate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.lastSessionDate'] = lastSessionDate;
// __GDPR__COMMON__ "common.isNewSession" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
@ -34,8 +32,19 @@ export function resolveWorkbenchCommonProperties(storageService: IStorageService
}
function getOrCreateInstanceId(storageService: IStorageService): string {
const result = storageService.get('telemetry.instanceId') || uuid.generateUuid();
storageService.store('telemetry.instanceId', result);
const key = 'telemetry.instanceId';
const result = storageService.get(key, StorageScope.GLOBAL, uuid.generateUuid());
storageService.store(key, result, StorageScope.GLOBAL);
return result;
}
function getOrCreateFirstSessionDate(storageService: IStorageService): string {
const key = 'telemetry.firstSessionDate';
const firstSessionDate = storageService.get(key, StorageScope.GLOBAL, new Date().toUTCString());
storageService.store(key, firstSessionDate, StorageScope.GLOBAL);
return firstSessionDate;
}

View file

@ -7,9 +7,10 @@ import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';
import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/workbenchCommonProperties';
import { StorageService, InMemoryLocalStorage } from 'vs/platform/storage/common/storageService';
import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices';
import { getRandomTestPath, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { StorageService } from 'vs/platform/storage/electron-browser/storageService';
import { NullLogService } from 'vs/platform/log/common/log';
import { del } from 'vs/base/node/extfs';
import { mkdirp } from 'vs/base/node/pfs';
import { timeout } from 'vs/base/common/async';
@ -20,10 +21,10 @@ suite('Telemetry - common properties', function () {
const commit: string = void 0;
const version: string = void 0;
let storageService: StorageService;
let nestStorage2Service: IStorageService;
setup(() => {
storageService = new StorageService(new InMemoryLocalStorage(), null, TestWorkspace.id);
nestStorage2Service = new StorageService(':memory:', new NullLogService(), TestEnvironmentService);
});
teardown(done => {
@ -33,7 +34,7 @@ suite('Telemetry - common properties', function () {
test('default', async function () {
await mkdirp(parentDir);
fs.writeFileSync(installSource, 'my.install.source');
const props = await resolveWorkbenchCommonProperties(storageService, commit, version, 'someMachineId', installSource);
const props = await resolveWorkbenchCommonProperties(nestStorage2Service, commit, version, 'someMachineId', installSource);
assert.ok('commitHash' in props);
assert.ok('sessionID' in props);
assert.ok('timestamp' in props);
@ -54,22 +55,22 @@ suite('Telemetry - common properties', function () {
assert.ok('common.instanceId' in props, 'instanceId');
assert.ok('common.machineId' in props, 'machineId');
fs.unlinkSync(installSource);
const props_1 = await resolveWorkbenchCommonProperties(storageService, commit, version, 'someMachineId', installSource);
const props_1 = await resolveWorkbenchCommonProperties(nestStorage2Service, commit, version, 'someMachineId', installSource);
assert.ok(!('common.source' in props_1));
});
test('lastSessionDate when aviablale', async function () {
storageService.store('telemetry.lastSessionDate', new Date().toUTCString());
nestStorage2Service.store('telemetry.lastSessionDate', new Date().toUTCString(), StorageScope.GLOBAL);
const props = await resolveWorkbenchCommonProperties(storageService, commit, version, 'someMachineId', installSource);
const props = await resolveWorkbenchCommonProperties(nestStorage2Service, commit, version, 'someMachineId', installSource);
assert.ok('common.lastSessionDate' in props); // conditional, see below
assert.ok('common.isNewSession' in props);
assert.equal(props['common.isNewSession'], 0);
});
test('values chance on ask', async function () {
const props = await resolveWorkbenchCommonProperties(storageService, commit, version, 'someMachineId', installSource);
const props = await resolveWorkbenchCommonProperties(nestStorage2Service, commit, version, 'someMachineId', installSource);
let value1 = props['common.sequence'];
let value2 = props['common.sequence'];
assert.ok(value1 !== value2, 'seq');

View file

@ -12,12 +12,16 @@ import { Event, Emitter } from 'vs/base/common/event';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IConstructorSignature0, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { trackFocus, Dimension } from 'vs/base/browser/dom';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Disposable } from 'vs/base/common/lifecycle';
/**
* Composites are layed out in the sidebar and panel part of the workbench. At a time only one composite
* can be open in the sidebar, and only one composite can be open in the panel.
*
* Each composite has a minimized representation that is good enough to provide some
* information about the state of the composite data.
*
* The workbench will keep a composite alive after it has been created and show/hide it based on
* user interaction. The lifecycle of a composite goes in the order create(), setVisible(true|false),
* layout(), focus(), dispose(). During use of the workbench, a composite will often receive a setVisible,
@ -33,6 +37,7 @@ export abstract class Composite extends Component implements IComposite {
if (!this._onDidFocus) {
this._registerFocusTrackEvents();
}
return this._onDidFocus.event;
}
@ -41,6 +46,7 @@ export abstract class Composite extends Component implements IComposite {
if (!this._onDidBlur) {
this._registerFocusTrackEvents();
}
return this._onDidBlur.event;
}
@ -64,9 +70,10 @@ export abstract class Composite extends Component implements IComposite {
constructor(
id: string,
private _telemetryService: ITelemetryService,
themeService: IThemeService
themeService: IThemeService,
storageService: IStorageService
) {
super(id, themeService);
super(id, themeService, storageService);
this.visible = false;
}
@ -232,9 +239,9 @@ export abstract class CompositeDescriptor<T extends Composite> {
}
}
export abstract class CompositeRegistry<T extends Composite> {
export abstract class CompositeRegistry<T extends Composite> extends Disposable {
private readonly _onDidRegister: Emitter<CompositeDescriptor<T>> = new Emitter<CompositeDescriptor<T>>();
private readonly _onDidRegister: Emitter<CompositeDescriptor<T>> = this._register(new Emitter<CompositeDescriptor<T>>());
get onDidRegister(): Event<CompositeDescriptor<T>> { return this._onDidRegister.event; }
private composites: CompositeDescriptor<T>[] = [];

View file

@ -25,6 +25,7 @@ import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart';
import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart';
import { StatusbarPart } from 'vs/workbench/browser/parts/statusbar/statusbarPart';
import { getZoomFactor } from 'vs/base/browser/browser';
import { RunOnceScheduler } from 'vs/base/common/async';
const TITLE_BAR_HEIGHT = isMacintosh ? 22 : 30;
const STATUS_BAR_HEIGHT = 22;
@ -67,6 +68,8 @@ export class WorkbenchLayout extends Disposable implements IVerticalSashLayoutPr
private _panelHeight: number;
private _panelWidth: number;
private saveStateScheduler: RunOnceScheduler;
constructor(
private parent: HTMLElement,
private workbenchContainer: HTMLElement,
@ -99,6 +102,9 @@ export class WorkbenchLayout extends Disposable implements IVerticalSashLayoutPr
this.sashXTwo = new Sash(this.workbenchContainer, this);
this.sashY = new Sash(this.workbenchContainer, this, { orientation: Orientation.HORIZONTAL });
// State scheduler
this.saveStateScheduler = new RunOnceScheduler(() => this.saveState(), 1000);
this.registerListeners();
}
@ -115,6 +121,10 @@ export class WorkbenchLayout extends Disposable implements IVerticalSashLayoutPr
private registerListeners(): void {
this._register(this.themeService.onThemeChange(_ => this.layout()));
this._register(this.parts.editor.onDidSizeConstraintsChange(() => this.onDidEditorSizeConstraintsChange()));
this._register(this.storageService.onWillSaveState(() => {
this.saveStateScheduler.dispose();
this.saveState();
}));
this.registerSashListeners();
}
@ -372,20 +382,22 @@ export class WorkbenchLayout extends Disposable implements IVerticalSashLayoutPr
}));
this._register(this.sashXOne.onDidEnd(() => {
this.storageService.store(WorkbenchLayout.sashXOneWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL);
this.saveStateScheduler.schedule();
}));
this._register(this.sashY.onDidEnd(() => {
this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL);
this.saveStateScheduler.schedule();
}));
this._register(this.sashXTwo.onDidEnd(() => {
this.storageService.store(WorkbenchLayout.sashXTwoWidthSettingsKey, this.panelWidth, StorageScope.GLOBAL);
this.saveStateScheduler.schedule();
}));
this._register(this.sashY.onDidReset(() => {
this.panelHeight = this.sidebarHeight * DEFAULT_PANEL_SIZE_COEFFICIENT;
this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL);
this.saveStateScheduler.schedule();
this.layout();
}));
@ -393,18 +405,22 @@ export class WorkbenchLayout extends Disposable implements IVerticalSashLayoutPr
let activeViewlet = this.viewletService.getActiveViewlet();
let optimalWidth = activeViewlet && activeViewlet.getOptimalWidth();
this.sidebarWidth = Math.max(optimalWidth, DEFAULT_SIDEBAR_PART_WIDTH);
this.storageService.store(WorkbenchLayout.sashXOneWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL);
this.saveStateScheduler.schedule();
this.partService.setSideBarHidden(false).then(() => this.layout());
}));
this._register(this.sashXTwo.onDidReset(() => {
this.panelWidth = (this.workbenchSize.width - this.sidebarWidth - this.activitybarWidth) * DEFAULT_PANEL_SIZE_COEFFICIENT;
this.storageService.store(WorkbenchLayout.sashXTwoWidthSettingsKey, this.panelWidth, StorageScope.GLOBAL);
this.saveStateScheduler.schedule();
this.layout();
}));
}
layout(options?: ILayoutOptions): void {
layout(options?: ILayoutOptions): void { //
this.workbenchSize = getClientArea(this.parent);
const isActivityBarHidden = !this.partService.isVisible(Parts.ACTIVITYBAR_PART);
@ -472,7 +488,7 @@ export class WorkbenchLayout extends Disposable implements IVerticalSashLayoutPr
this.panelSizeBeforeMaximized = panelWidth;
}
}
this.storageService.store(WorkbenchLayout.panelSizeBeforeMaximizedKey, this.panelSizeBeforeMaximized, StorageScope.GLOBAL);
const panelDimension = new Dimension(panelWidth, panelHeight);
// Editor
@ -527,16 +543,13 @@ export class WorkbenchLayout extends Disposable implements IVerticalSashLayoutPr
if (!isSidebarHidden) {
this.sidebarWidth = sidebarSize.width;
this.storageService.store(WorkbenchLayout.sashXOneWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL);
}
if (!isPanelHidden) {
if (panelPosition === Position.BOTTOM) {
this.panelHeight = panelDimension.height;
this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL);
} else {
this.panelWidth = panelDimension.width;
this.storageService.store(WorkbenchLayout.sashXTwoWidthSettingsKey, this.panelWidth, StorageScope.GLOBAL);
}
}
@ -642,6 +655,9 @@ export class WorkbenchLayout extends Disposable implements IVerticalSashLayoutPr
// Propagate to Context View
this.contextViewService.layout();
// Schedule save state
this.saveStateScheduler.schedule();
}
getVerticalSashTop(sash: Sash): number {
@ -743,4 +759,13 @@ export class WorkbenchLayout extends Disposable implements IVerticalSashLayoutPr
this.layout();
}
}
private saveState(): void {
this.storageService.store(WorkbenchLayout.sashXOneWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL);
this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL);
this.storageService.store(WorkbenchLayout.sashXTwoWidthSettingsKey, this.panelWidth, StorageScope.GLOBAL);
this.storageService.store(WorkbenchLayout.panelSizeBeforeMaximizedKey, this.panelSizeBeforeMaximized, StorageScope.GLOBAL);
}
}

View file

@ -7,6 +7,7 @@ import 'vs/css!./media/part';
import { Component } from 'vs/workbench/common/component';
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import { Dimension, size } from 'vs/base/browser/dom';
import { IStorageService } from 'vs/platform/storage/common/storage';
export interface IPartOptions {
hasTitle?: boolean;
@ -14,8 +15,8 @@ export interface IPartOptions {
}
/**
* Parts are layed out in the workbench and have their own layout that arranges an optional title
* and mandatory content area to show content.
* Parts are layed out in the workbench and have their own layout that
* arranges an optional title and mandatory content area to show content.
*/
export abstract class Part extends Component {
private parent: HTMLElement;
@ -26,9 +27,10 @@ export abstract class Part extends Component {
constructor(
id: string,
private options: IPartOptions,
themeService: IThemeService
themeService: IThemeService,
storageService: IStorageService
) {
super(id, themeService);
super(id, themeService, storageService);
}
protected onThemeChange(theme: ITheme): void {
@ -98,10 +100,10 @@ export abstract class Part extends Component {
}
}
const TITLE_HEIGHT = 35;
export class PartLayout {
private static readonly TITLE_HEIGHT = 35;
constructor(container: HTMLElement, private options: IPartOptions, titleArea: HTMLElement, private contentArea: HTMLElement) { }
layout(dimension: Dimension): Dimension[] {
@ -113,7 +115,7 @@ export class PartLayout {
// Title Size: Width (Fill), Height (Variable)
let titleSize: Dimension;
if (this.options && this.options.hasTitle) {
titleSize = new Dimension(width, Math.min(height, TITLE_HEIGHT));
titleSize = new Dimension(width, Math.min(height, PartLayout.TITLE_HEIGHT));
} else {
titleSize = new Dimension(0, 0);
}

View file

@ -61,7 +61,7 @@ export class ActivitybarPart extends Part {
@IStorageService private storageService: IStorageService,
@IExtensionService private extensionService: IExtensionService
) {
super(id, { hasTitle: false }, themeService);
super(id, { hasTitle: false }, themeService, storageService);
this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, {
icon: true,
@ -332,10 +332,10 @@ export class ActivitybarPart extends Part {
return sizes;
}
shutdown(): void {
protected saveState(): void {
const state = this.viewletService.getAllViewlets().map(({ id, iconUrl }) => ({ id, iconUrl }));
this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEWLETS, JSON.stringify(state), StorageScope.GLOBAL);
super.shutdown();
super.saveState();
}
}

View file

@ -85,7 +85,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
id: string,
options: IPartOptions
) {
super(id, options, themeService);
super(id, options, themeService, storageService);
this.instantiatedCompositeListeners = [];
this.mapCompositeToCompositeContainer = {};
@ -495,12 +495,6 @@ export abstract class CompositePart<T extends Composite> extends Part {
return sizes;
}
shutdown(): void {
this.instantiatedComposites.forEach(i => i.shutdown());
super.shutdown();
}
dispose(): void {
this.mapCompositeToCompositeContainer = null;
this.mapProgressServiceToComposite = null;

View file

@ -10,13 +10,12 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { LRUCache } from 'vs/base/common/map';
import { URI } from 'vs/base/common/uri';
import { once, Event } from 'vs/base/common/event';
import { isEmptyObject } from 'vs/base/common/types';
import { DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
import { Scope } from 'vs/workbench/common/memento';
/**
* The base class of editors in the workbench. Editors register themselves for specific editor inputs.
@ -50,9 +49,10 @@ export abstract class BaseEditor extends Panel implements IEditor {
constructor(
id: string,
telemetryService: ITelemetryService,
themeService: IThemeService
themeService: IThemeService,
storageService: IStorageService
) {
super(id, telemetryService, themeService);
super(id, telemetryService, themeService, storageService);
}
get input(): EditorInput {
@ -143,28 +143,28 @@ export abstract class BaseEditor extends Panel implements IEditor {
this._group = group;
}
protected getEditorMemento<T>(storageService: IStorageService, editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
protected getEditorMemento<T>(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
const mementoKey = `${this.getId()}${key}`;
let editorMemento = BaseEditor.EDITOR_MEMENTOS.get(mementoKey);
if (!editorMemento) {
editorMemento = new EditorMemento(this.getId(), key, this.getMemento(storageService, Scope.WORKSPACE), limit, editorGroupService);
editorMemento = new EditorMemento(this.getId(), key, this.getMemento(StorageScope.WORKSPACE), limit, editorGroupService);
BaseEditor.EDITOR_MEMENTOS.set(mementoKey, editorMemento);
}
return editorMemento;
}
shutdown(): void {
protected saveState(): void {
// Shutdown all editor memento for this editor type
// Save all editor memento for this editor type
BaseEditor.EDITOR_MEMENTOS.forEach(editorMemento => {
if (editorMemento.id === this.getId()) {
editorMemento.shutdown();
editorMemento.saveState();
}
});
super.shutdown();
super.saveState();
}
dispose(): void {
@ -195,9 +195,9 @@ export class EditorMemento<T> implements IEditorMemento<T> {
return this._id;
}
saveState(group: IEditorGroup, resource: URI, state: T): void;
saveState(group: IEditorGroup, editor: EditorInput, state: T): void;
saveState(group: IEditorGroup, resourceOrEditor: URI | EditorInput, state: T): void {
saveEditorState(group: IEditorGroup, resource: URI, state: T): void;
saveEditorState(group: IEditorGroup, editor: EditorInput, state: T): void;
saveEditorState(group: IEditorGroup, resourceOrEditor: URI | EditorInput, state: T): void {
const resource = this.doGetResource(resourceOrEditor);
if (!resource || !group) {
return; // we are not in a good state to save any state for a resource
@ -216,14 +216,14 @@ export class EditorMemento<T> implements IEditorMemento<T> {
// Automatically clear when editor input gets disposed if any
if (resourceOrEditor instanceof EditorInput) {
once(resourceOrEditor.onDispose)(() => {
this.clearState(resource);
this.clearEditorState(resource);
});
}
}
loadState(group: IEditorGroup, resource: URI): T;
loadState(group: IEditorGroup, editor: EditorInput): T;
loadState(group: IEditorGroup, resourceOrEditor: URI | EditorInput): T {
loadEditorState(group: IEditorGroup, resource: URI): T;
loadEditorState(group: IEditorGroup, editor: EditorInput): T;
loadEditorState(group: IEditorGroup, resourceOrEditor: URI | EditorInput): T {
const resource = this.doGetResource(resourceOrEditor);
if (!resource || !group) {
return void 0; // we are not in a good state to load any state for a resource
@ -239,9 +239,9 @@ export class EditorMemento<T> implements IEditorMemento<T> {
return void 0;
}
clearState(resource: URI, group?: IEditorGroup): void;
clearState(editor: EditorInput, group?: IEditorGroup): void;
clearState(resourceOrEditor: URI | EditorInput, group?: IEditorGroup): void {
clearEditorState(resource: URI, group?: IEditorGroup): void;
clearEditorState(editor: EditorInput, group?: IEditorGroup): void;
clearEditorState(resourceOrEditor: URI | EditorInput, group?: IEditorGroup): void {
const resource = this.doGetResource(resourceOrEditor);
if (resource) {
const cache = this.doLoad();
@ -279,7 +279,7 @@ export class EditorMemento<T> implements IEditorMemento<T> {
return this.cache;
}
shutdown(): void {
saveState(): void {
const cache = this.doLoad();
// Cleanup once during shutdown

View file

@ -10,6 +10,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor';
import { IStorageService } from 'vs/platform/storage/common/storage';
/**
* An implementation of editor for diffing binary files like images or videos.
@ -21,9 +22,10 @@ export class BinaryResourceDiffEditor extends SideBySideEditor {
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService
) {
super(telemetryService, instantiationService, themeService);
super(telemetryService, instantiationService, themeService, storageService);
}
getMetadata(): string {

View file

@ -19,6 +19,7 @@ import { Dimension, size, clearNode } from 'vs/base/browser/dom';
import { IFileService } from 'vs/platform/files/common/files';
import { CancellationToken } from 'vs/base/common/cancellation';
import { dispose } from 'vs/base/common/lifecycle';
import { IStorageService } from 'vs/platform/storage/common/storage';
export interface IOpenCallbacks {
openInternal: (input: EditorInput, options: EditorOptions) => Thenable<void>;
@ -48,8 +49,9 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
telemetryService: ITelemetryService,
themeService: IThemeService,
@IFileService private readonly _fileService: IFileService,
@IStorageService storageService: IStorageService
) {
super(id, telemetryService, themeService);
super(id, telemetryService, themeService, storageService);
this.callbacks = callbacks;
}

View file

@ -117,8 +117,6 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito
setActive(isActive: boolean): void;
setLabel(label: string): void;
relayout(): void;
shutdown(): void;
}
export function getActiveTextEditorOptions(group: IEditorGroup, expectedActiveEditor?: IEditorInput, presetOptions?: EditorOptions): EditorOptions {

View file

@ -230,12 +230,6 @@ export class EditorControl extends Disposable {
}
}
shutdown(): void {
// Forward to all editor controls
this.controls.forEach(editor => editor.shutdown());
}
dispose(): void {
this.activeControlDisposeables = dispose(this.activeControlDisposeables);

View file

@ -1366,10 +1366,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
//#endregion
shutdown(): void {
this.editorControl.shutdown();
}
dispose(): void {
this._disposed = true;

View file

@ -21,8 +21,7 @@ import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupVi
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { assign } from 'vs/base/common/objects';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Scope } from 'vs/workbench/common/memento';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup';
import { TValueCallback, TPromise } from 'vs/base/common/winjs.base';
import { always } from 'vs/base/common/async';
@ -117,7 +116,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
private dimension: Dimension;
private _preferredSize: Dimension;
private memento: object;
private workspaceMemento: object;
private globalMemento: object;
private _partOptions: IEditorPartOptions;
@ -140,15 +139,16 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IConfigurationService private configurationService: IConfigurationService,
@IStorageService private storageService: IStorageService
@IStorageService storageService: IStorageService
) {
super(id, { hasTitle: false }, themeService);
super(id, { hasTitle: false }, themeService, storageService);
this.gridWidgetView = new GridWidgetView<IEditorGroupView>();
this._partOptions = getEditorPartOptions(this.configurationService.getValue<IWorkbenchEditorConfiguration>());
this.memento = this.getMemento(this.storageService, Scope.WORKSPACE);
this.globalMemento = this.getMemento(this.storageService, Scope.GLOBAL);
this.workspaceMemento = this.getMemento(StorageScope.WORKSPACE);
this.globalMemento = this.getMemento(StorageScope.GLOBAL);
this._whenRestored = new TPromise(resolve => {
this.whenRestoredComplete = resolve;
@ -818,7 +818,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
}
private doCreateGridControlWithPreviousState(): void {
const uiState = this.memento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] as IEditorPartUIState;
const uiState = this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] as IEditorPartUIState;
if (uiState && uiState.serializedGrid) {
try {
@ -955,7 +955,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
this._onDidLayout.fire(dimension);
}
shutdown(): void {
protected saveState(): void {
// Persist grid UI state
if (this.gridWidget) {
@ -966,19 +966,16 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
};
if (this.isEmpty()) {
delete this.memento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY];
delete this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY];
} else {
this.memento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] = uiState;
this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] = uiState;
}
}
// Persist centered view state
this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY] = this.centeredLayoutWidget.state;
// Forward to all groups
this.groupViews.forEach(group => group.shutdown());
super.shutdown();
super.saveState();
}
dispose(): void {

View file

@ -17,6 +17,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService';
import { SplitView, Sizing, Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { Event, Relay, anyEvent, mapEvent, Emitter } from 'vs/base/common/event';
import { IStorageService } from 'vs/platform/storage/common/storage';
export class SideBySideEditor extends BaseEditor {
@ -59,9 +60,10 @@ export class SideBySideEditor extends BaseEditor {
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService
) {
super(SideBySideEditor.ID, telemetryService, themeService);
super(SideBySideEditor.ID, telemetryService, themeService, storageService);
}
protected createEditor(parent: HTMLElement): void {

View file

@ -58,7 +58,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, windowService);
}
protected getEditorMemento<T>(storageService: IStorageService, editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
protected getEditorMemento<T>(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
return new EditorMemento(this.getId(), key, Object.create(null), limit, editorGroupService); // do not persist in storage as diff editors are never persisted
}

View file

@ -55,9 +55,9 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
@IEditorGroupsService protected editorGroupService: IEditorGroupsService,
@IWindowService private windowService: IWindowService
) {
super(id, telemetryService, themeService);
super(id, telemetryService, themeService, storageService);
this.editorMemento = this.getEditorMemento<IEditorViewState>(storageService, editorGroupService, TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100);
this.editorMemento = this.getEditorMemento<IEditorViewState>(editorGroupService, TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100);
this._register(this.configurationService.onDidChangeConfiguration(e => this.handleConfigurationChangeEvent(this.configurationService.getValue<IEditorConfiguration>(this.getResource()))));
}
@ -233,7 +233,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
return;
}
this.editorMemento.saveState(this.group, resource, editorViewState);
this.editorMemento.saveEditorState(this.group, resource, editorViewState);
}
protected retrieveTextEditorViewState(resource: URI): IEditorViewState {
@ -260,7 +260,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
*/
protected clearTextEditorViewState(resources: URI[], group?: IEditorGroup): void {
resources.forEach(resource => {
this.editorMemento.clearState(resource, group);
this.editorMemento.clearEditorState(resource, group);
});
}
@ -268,7 +268,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
* Loads the text editor view state for the given resource and returns it.
*/
protected loadTextEditorViewState(resource: URI): IEditorViewState {
return this.editorMemento.loadState(this.group, resource);
return this.editorMemento.loadEditorState(this.group, resource);
}
private updateEditorConfiguration(configuration = this.configurationService.getValue<IEditorConfiguration>(this.getResource())): void {

View file

@ -164,15 +164,14 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
super.clearInput();
}
shutdown(): void {
protected saveState(): void {
// Save View State (only for untitled)
if (this.input instanceof UntitledEditorInput) {
this.saveTextResourceEditorViewState(this.input);
}
// Call Super
super.shutdown();
super.saveState();
}
private saveTextResourceEditorViewState(input: EditorInput): void {

View file

@ -44,6 +44,7 @@ import { getIconClass } from 'vs/workbench/browser/parts/quickinput/quickInputUt
import { AccessibilitySupport } from 'vs/base/common/platform';
import * as browser from 'vs/base/browser/browser';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IStorageService } from 'vs/platform/storage/common/storage';
const $ = dom.$;
@ -796,9 +797,10 @@ export class QuickInputService extends Component implements IQuickInputService {
@IEditorGroupsService private editorGroupService: IEditorGroupsService,
@IKeybindingService private keybindingService: IKeybindingService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService
) {
super(QuickInputService.ID, themeService);
super(QuickInputService.ID, themeService, storageService);
this.inQuickOpenContext = new RawContextKey<boolean>('inQuickOpen', false).bindTo(contextKeyService);
this._register(this.quickOpenService.onShow(() => this.inQuickOpen('quickOpen', true)));
this._register(this.quickOpenService.onHide(() => this.inQuickOpen('quickOpen', false)));

View file

@ -50,6 +50,7 @@ import { ILabelService } from 'vs/platform/label/common/label';
import { timeout } from 'vs/base/common/async';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import { IStorageService } from 'vs/platform/storage/common/storage';
const HELP_PREFIX = '?';
@ -91,9 +92,10 @@ export class QuickOpenController extends Component implements IQuickOpenService
@IInstantiationService private instantiationService: IInstantiationService,
@IPartService private partService: IPartService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService
) {
super(QuickOpenController.ID, themeService);
super(QuickOpenController.ID, themeService, storageService);
this.editorHistoryHandler = this.instantiationService.createInstance(EditorHistoryHandler);

View file

@ -27,6 +27,7 @@ import { isThemeColor } from 'vs/editor/common/editorCommon';
import { Color } from 'vs/base/common/color';
import { addClass, EventHelper, createStyleSheet, addDisposableListener } from 'vs/base/browser/dom';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IStorageService } from 'vs/platform/storage/common/storage';
export class StatusbarPart extends Part implements IStatusbarService {
@ -44,9 +45,10 @@ export class StatusbarPart extends Part implements IStatusbarService {
id: string,
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IWorkspaceContextService private contextService: IWorkspaceContextService
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IStorageService storageService: IStorageService
) {
super(id, { hasTitle: false }, themeService);
super(id, { hasTitle: false }, themeService, storageService);
this.registerListeners();
}

View file

@ -32,6 +32,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { template, getBaseLabel } from 'vs/base/common/labels';
import { ILabelService } from 'vs/platform/label/common/label';
import { Event } from 'vs/base/common/event';
import { IStorageService } from 'vs/platform/storage/common/storage';
export class TitlebarPart extends Part implements ITitleService {
@ -80,9 +81,10 @@ export class TitlebarPart extends Part implements ITitleService {
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@ILabelService private labelService: ILabelService
@ILabelService private labelService: ILabelService,
@IStorageService storageService: IStorageService
) {
super(id, { hasTitle: false }, themeService);
super(id, { hasTitle: false }, themeService, storageService);
this.properties = { isPure: true, isAdmin: false };
this.activeEditorListeners = [];

View file

@ -28,6 +28,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IView } from 'vs/workbench/common/views';
import { IStorageService } from 'vs/platform/storage/common/storage';
export interface IPanelColors extends IColorMapping {
dropBackground?: ColorIdentifier;
@ -165,7 +166,8 @@ export abstract class ViewletPanel extends Panel implements IView {
return 0;
}
shutdown(): void {
saveState(): void {
// Subclasses to implement for saving state
}
}
@ -203,9 +205,10 @@ export class PanelViewlet extends Viewlet {
@IPartService partService: IPartService,
@IContextMenuService protected contextMenuService: IContextMenuService,
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService
) {
super(id, configurationService, partService, telemetryService, themeService);
super(id, configurationService, partService, telemetryService, themeService, storageService);
}
create(parent: HTMLElement): Promise<void> {

View file

@ -431,9 +431,10 @@ export class PersistentContributableViewsModel extends ContributableViewsModel {
this._register(this.onDidAdd(viewDescriptorRefs => this.saveVisibilityStates(viewDescriptorRefs.map(r => r.viewDescriptor))));
this._register(this.onDidRemove(viewDescriptorRefs => this.saveVisibilityStates(viewDescriptorRefs.map(r => r.viewDescriptor))));
this._register(this.storageService.onWillSaveState(() => this.saveViewsStates()));
}
saveViewsStates(): void {
private saveViewsStates(): void {
const storedViewsStates: { [id: string]: { collapsed: boolean, size: number, order: number } } = {};
for (const viewDescriptor of this.viewDescriptors) {
const viewState = this.viewStates.get(viewDescriptor.id);
@ -480,11 +481,6 @@ export class PersistentContributableViewsModel extends ContributableViewsModel {
const storedVisibilityStates = <Array<string | { id: string, isHidden: boolean }>>JSON.parse(storageService.get(hiddenViewsStorageId, StorageScope.GLOBAL, '[]'));
return <{ id: string, isHidden: boolean }[]>storedVisibilityStates.map(c => typeof c === 'string' /* migration */ ? { id: c, isHidden: true } : c);
}
dispose(): void {
this.saveViewsStates();
super.dispose();
}
}
const SCM_VIEWLET_ID = 'workbench.view.scm';

View file

@ -5,7 +5,6 @@
import { TPromise } from 'vs/base/common/winjs.base';
import * as DOM from 'vs/base/browser/dom';
import { Scope } from 'vs/workbench/common/memento';
import { dispose, IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IAction } from 'vs/base/common/actions';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
@ -104,12 +103,12 @@ export abstract class TreeViewsViewletPanel extends ViewletPanel {
}
export interface IViewletViewOptions extends IViewletPanelOptions {
viewletSettings: object;
viewletState: object;
}
export abstract class ViewContainerViewlet extends PanelViewlet implements IViewsViewlet {
private readonly viewletSettings: Object;
private readonly viewletState: object;
private didLayout = false;
private dimension: DOM.Dimension;
private areExtensionsReady: boolean = false;
@ -133,11 +132,11 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
@IExtensionService protected extensionService: IExtensionService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService
) {
super(id, { showHeaderInTitleWhenSingleView, dnd: new DefaultPanelDndController() }, configurationService, partService, contextMenuService, telemetryService, themeService);
super(id, { showHeaderInTitleWhenSingleView, dnd: new DefaultPanelDndController() }, configurationService, partService, contextMenuService, telemetryService, themeService, storageService);
const container = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).get(id);
this.viewsModel = this._register(this.instantiationService.createInstance(PersistentContributableViewsModel, container, viewletStateStorageId));
this.viewletSettings = this.getMemento(storageService, Scope.WORKSPACE);
this.viewletState = this.getMemento(StorageScope.WORKSPACE);
this.visibleViewsStorageId = `${id}.numberOfVisibleViews`;
this.visibleViewsCountFromCache = this.storageService.getInteger(this.visibleViewsStorageId, this.contextService.getWorkbenchState() === WorkbenchState.EMPTY ? StorageScope.GLOBAL : StorageScope.WORKSPACE, 0);
@ -241,13 +240,6 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
return optimalWidth + additionalMargin;
}
shutdown(): void {
this.panels.forEach((view) => view.shutdown());
this.storageService.store(this.visibleViewsStorageId, this.length, this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? StorageScope.WORKSPACE : StorageScope.GLOBAL);
this.viewsModel.saveViewsStates();
super.shutdown();
}
protected isSingleView(): boolean {
if (!super.isSingleView()) {
return false;
@ -276,7 +268,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
title: viewDescriptor.name,
actionRunner: this.getActionRunner(),
expanded: !collapsed,
viewletSettings: this.viewletSettings
viewletState: this.viewletState
});
panel.render();
panel.setVisible(true);
@ -384,6 +376,13 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
}
return sizes;
}
protected saveState(): void {
this.panels.forEach((view) => view.saveState());
this.storageService.store(this.visibleViewsStorageId, this.length, this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? StorageScope.WORKSPACE : StorageScope.GLOBAL);
super.saveState();
}
}
export class FileIconThemableWorkbenchTree extends WorkbenchTree {

View file

@ -20,6 +20,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorG
import { URI } from 'vs/base/common/uri';
import { ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/toggleSidebarPosition';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStorageService } from 'vs/platform/storage/common/storage';
export abstract class Viewlet extends Composite implements IViewlet {
@ -27,9 +28,10 @@ export abstract class Viewlet extends Composite implements IViewlet {
protected configurationService: IConfigurationService,
private partService: IPartService,
telemetryService: ITelemetryService,
themeService: IThemeService
themeService: IThemeService,
storageService: IStorageService
) {
super(id, telemetryService, themeService);
super(id, telemetryService, themeService, storageService);
}
getOptimalWidth(): number {

View file

@ -3,78 +3,44 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable } from 'vs/base/common/lifecycle';
import { Scope, Memento } from 'vs/workbench/common/memento';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Memento } from 'vs/workbench/common/memento';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Themable } from 'vs/workbench/common/theme';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
/**
* Base class of any core/ui component in the workbench. Examples include services, extensions, parts, viewlets and quick open.
* Provides some convinience methods to participate in the workbench lifecycle (dispose, shutdown) and
* loading and saving settings through memento.
*/
export interface IWorkbenchComponent extends IDisposable {
/**
* The unique identifier of this component.
*/
getId(): string;
/**
* Called when the browser containing the container is closed.
*
* Use this function to store settings that you want to restore next time. Should not be used to free resources
* because dispose() is being called for this purpose and shutdown() has a chance to be vetoed by the user.
*/
shutdown(): void;
}
export class Component extends Themable implements IWorkbenchComponent {
export class Component extends Themable {
private id: string;
private componentMemento: Memento;
private memento: Memento;
constructor(
id: string,
themeService: IThemeService
themeService: IThemeService,
storageService: IStorageService
) {
super(themeService);
this.id = id;
this.componentMemento = new Memento(this.id);
this.memento = new Memento(this.id, storageService);
this._register(storageService.onWillSaveState(() => {
// Ask the component to persist state into the memento
this.saveState();
// Then save the memento into storage
this.memento.saveMemento();
}));
}
getId(): string {
return this.id;
}
/**
* Returns a JSON Object that represents the data of this memento. The optional
* parameter scope allows to specify the scope of the memento to load. If not
* provided, the scope will be global, Scope.WORKSPACE can be used to
* scope the memento to the workspace.
*
* Mementos are shared across components with the same id. This means that multiple components
* with the same id will store data into the same data structure.
*/
protected getMemento(storageService: IStorageService, scope: Scope = Scope.GLOBAL): object {
return this.componentMemento.getMemento(storageService, scope);
protected getMemento(scope: StorageScope): object {
return this.memento.getMemento(scope);
}
/**
* Saves all data of the mementos that have been loaded to the local storage. This includes
* global and workspace scope.
*
* Mementos are shared across components with the same id. This means that multiple components
* with the same id will store data into the same data structure.
*/
protected saveMemento(): void {
this.componentMemento.saveMemento();
}
shutdown(): void {
// Save Memento
this.saveMemento();
protected saveState(): void {
// Subclasses to implement for storing state
}
}

View file

@ -1005,14 +1005,14 @@ export const enum CloseDirection {
export interface IEditorMemento<T> {
saveState(group: IEditorGroup, resource: URI, state: T): void;
saveState(group: IEditorGroup, editor: EditorInput, state: T): void;
saveEditorState(group: IEditorGroup, resource: URI, state: T): void;
saveEditorState(group: IEditorGroup, editor: EditorInput, state: T): void;
loadState(group: IEditorGroup, resource: URI): T;
loadState(group: IEditorGroup, editor: EditorInput): T;
loadEditorState(group: IEditorGroup, resource: URI): T;
loadEditorState(group: IEditorGroup, editor: EditorInput): T;
clearState(resource: URI, group?: IEditorGroup): void;
clearState(editor: EditorInput, group?: IEditorGroup): void;
clearEditorState(resource: URI, group?: IEditorGroup): void;
clearEditorState(editor: EditorInput, group?: IEditorGroup): void;
}
class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry {

View file

@ -3,109 +3,74 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as types from 'vs/base/common/types';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { isEmptyObject } from 'vs/base/common/types';
/**
* Supported memento scopes.
*/
export const enum Scope {
/**
* The memento will be scoped to all workspaces of this domain.
*/
GLOBAL,
/**
* The memento will be scoped to the current workspace.
*/
WORKSPACE
}
/**
* A memento provides access to a datastructure that is persisted and restored as part of the workbench lifecycle.
*/
export class Memento {
// Mementos are static to ensure that for a given component with an id only ever one memento gets loaded
private static globalMementos: { [id: string]: ScopedMemento } = {};
private static workspaceMementos: { [id: string]: ScopedMemento } = {};
private static globalMementos: { [id: string]: ScopedMemento } = Object.create(null);
private static workspaceMementos: { [id: string]: ScopedMemento } = Object.create(null);
private static readonly COMMON_PREFIX = 'memento/';
private id: string;
constructor(id: string) {
constructor(id: string, private storageService: IStorageService) {
this.id = Memento.COMMON_PREFIX + id.toLowerCase();
}
/**
* Returns a JSON Object that represents the data of this memento. The optional
* parameter scope allows to specify the scope of the memento to load. If not
* provided, the scope will be global, Memento.Scope.WORKSPACE can be used to
* scope the memento to the workspace.
*/
getMemento(storageService: IStorageService, scope: Scope = Scope.GLOBAL): object {
getMemento(scope: StorageScope): object {
// Scope by Workspace
if (scope === Scope.WORKSPACE) {
if (scope === StorageScope.WORKSPACE) {
let workspaceMemento = Memento.workspaceMementos[this.id];
if (!workspaceMemento) {
workspaceMemento = new ScopedMemento(this.id, scope, storageService);
workspaceMemento = new ScopedMemento(this.id, scope, this.storageService);
Memento.workspaceMementos[this.id] = workspaceMemento;
}
return workspaceMemento.getMemento();
}
// Use global scope
// Scope Global
let globalMemento = Memento.globalMementos[this.id];
if (!globalMemento) {
globalMemento = new ScopedMemento(this.id, scope, storageService);
globalMemento = new ScopedMemento(this.id, scope, this.storageService);
Memento.globalMementos[this.id] = globalMemento;
}
return globalMemento.getMemento();
}
/**
* Saves all data of the mementos that have been loaded to the local storage. This includes
* global and workspace scope.
*/
saveMemento(): void {
// Global
const globalMemento = Memento.globalMementos[this.id];
if (globalMemento) {
globalMemento.save();
}
// Workspace
const workspaceMemento = Memento.workspaceMementos[this.id];
if (workspaceMemento) {
workspaceMemento.save();
}
// Global
const globalMemento = Memento.globalMementos[this.id];
if (globalMemento) {
globalMemento.save();
}
}
}
class ScopedMemento {
private id: string;
private mementoObj: object;
private scope: Scope;
constructor(id: string, scope: Scope, private storageService: IStorageService) {
this.id = id;
this.scope = scope;
this.mementoObj = this.loadMemento();
constructor(private id: string, private scope: StorageScope, private storageService: IStorageService) {
this.mementoObj = this.load();
}
getMemento(): object {
return this.mementoObj;
}
private loadMemento(): object {
let storageScope = this.scope === Scope.GLOBAL ? StorageScope.GLOBAL : StorageScope.WORKSPACE;
let memento = this.storageService.get(this.id, storageScope);
private load(): object {
const memento = this.storageService.get(this.id, this.scope);
if (memento) {
return JSON.parse(memento);
}
@ -114,12 +79,10 @@ class ScopedMemento {
}
save(): void {
let storageScope = this.scope === Scope.GLOBAL ? StorageScope.GLOBAL : StorageScope.WORKSPACE;
if (!types.isEmptyObject(this.mementoObj)) {
this.storageService.store(this.id, JSON.stringify(this.mementoObj), storageScope);
if (!isEmptyObject(this.mementoObj)) {
this.storageService.store(this.id, JSON.stringify(this.mementoObj), this.scope);
} else {
this.storageService.remove(this.id, storageScope);
this.storageService.remove(this.id, this.scope);
}
}
}

View file

@ -5,7 +5,6 @@
import * as nls from 'vs/nls';
import * as perf from 'vs/base/common/performance';
import { TPromise } from 'vs/base/common/winjs.base';
import { WorkbenchShell } from 'vs/workbench/electron-browser/shell';
import * as browser from 'vs/base/browser/browser';
import { domContentLoaded } from 'vs/base/browser/dom';
@ -23,9 +22,8 @@ import * as gracefulFs from 'graceful-fs';
import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/keybindingService';
import { IWindowConfiguration, IWindowsService } from 'vs/platform/windows/common/windows';
import { WindowsChannelClient } from 'vs/platform/windows/node/windowsIpc';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IStorageLegacyService, StorageLegacyService, inMemoryLocalStorageInstance, IStorageLegacy } from 'vs/platform/storage/common/storageLegacyService';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { StorageService, inMemoryLocalStorageInstance, IStorage } from 'vs/platform/storage/common/storageService';
import { Client as ElectronIPCClient } from 'vs/base/parts/ipc/electron-browser/ipc.electron-browser';
import { webFrame } from 'electron';
import { UpdateChannelClient } from 'vs/platform/update/node/updateIpc';
@ -37,6 +35,7 @@ import { IWorkspacesService, ISingleFolderWorkspaceIdentifier } from 'vs/platfor
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import * as fs from 'fs';
import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform/log/common/log';
import { StorageService, DelegatingStorageService } from 'vs/platform/storage/electron-browser/storageService';
import { IssueChannelClient } from 'vs/platform/issue/node/issueIpc';
import { IIssueService } from 'vs/platform/issue/common/issue';
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc';
@ -48,26 +47,24 @@ import { sanitizeFilePath } from 'vs/base/node/extfs';
gracefulFs.gracefulify(fs); // enable gracefulFs
export function startup(configuration: IWindowConfiguration): TPromise<void> {
export function startup(configuration: IWindowConfiguration): Promise<void> {
// Massage configuration file URIs
revive(configuration);
// Setup perf
perf.importEntries(configuration.perfEntries);
// Ensure others can listen to zoom level changes
browser.setZoomFactor(webFrame.getZoomFactor());
// See https://github.com/Microsoft/vscode/issues/26151
// Can be trusted because we are not setting it ourselves.
browser.setZoomLevel(webFrame.getZoomLevel(), true /* isTrusted */);
// Browser config
browser.setZoomFactor(webFrame.getZoomFactor()); // Ensure others can listen to zoom level changes
browser.setZoomLevel(webFrame.getZoomLevel(), true /* isTrusted */); // Can be trusted because we are not setting it ourselves (https://github.com/Microsoft/vscode/issues/26151)
browser.setFullscreen(!!configuration.fullscreen);
KeyboardMapperFactory.INSTANCE._onKeyboardLayoutChanged();
browser.setAccessibilitySupport(configuration.accessibilitySupport ? platform.AccessibilitySupport.Enabled : platform.AccessibilitySupport.Disabled);
// Setup Intl
// Keyboard support
KeyboardMapperFactory.INSTANCE._onKeyboardLayoutChanged();
// Setup Intl for comparers
comparer.setFileNameComparer(new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }));
// Open workbench
@ -78,6 +75,7 @@ function revive(workbench: IWindowConfiguration) {
if (workbench.folderUri) {
workbench.folderUri = uri.revive(workbench.folderUri);
}
const filesToWaitPaths = workbench.filesToWait && workbench.filesToWait.paths;
[filesToWaitPaths, workbench.filesToOpen, workbench.filesToCreate, workbench.filesToDiff].forEach(paths => {
if (Array.isArray(paths)) {
@ -90,30 +88,42 @@ function revive(workbench: IWindowConfiguration) {
});
}
function openWorkbench(configuration: IWindowConfiguration): TPromise<void> {
function openWorkbench(configuration: IWindowConfiguration): Promise<void> {
const mainProcessClient = new ElectronIPCClient(`window:${configuration.windowId}`);
const mainServices = createMainProcessServices(mainProcessClient, configuration);
const environmentService = new EnvironmentService(configuration, configuration.execPath);
const logService = createLogService(mainProcessClient, configuration, environmentService);
logService.trace('openWorkbench configuration', JSON.stringify(configuration));
// Since the configuration service is one of the core services that is used in so many places, we initialize it
// right before startup of the workbench shell to have its data ready for consumers
return createAndInitializeWorkspaceService(configuration, environmentService).then(workspaceService => {
const storageService = createStorageService(workspaceService, environmentService);
return Promise.all([
createAndInitializeWorkspaceService(configuration, environmentService),
createStorageService(environmentService, logService)
]).then(services => {
const workspaceService = services[0];
const storageLegacyService = createStorageLegacyService(workspaceService, environmentService);
const storageService = new DelegatingStorageService(services[1], storageLegacyService, logService, environmentService);
return domContentLoaded().then(() => {
// Open Shell
perf.mark('willStartWorkbench');
// Create Shell
const shell = new WorkbenchShell(document.body, {
contextService: workspaceService,
configurationService: workspaceService,
environmentService,
logService,
storageLegacyService,
storageService
}, mainServices, mainProcessClient, configuration);
// Gracefully Shutdown Storage
shell.onShutdown(event => {
event.join(storageService.close(event.reason));
});
// Open Shell
shell.open();
// Inform user about loading issues from the loader
@ -128,20 +138,19 @@ function openWorkbench(configuration: IWindowConfiguration): TPromise<void> {
});
}
function createAndInitializeWorkspaceService(configuration: IWindowConfiguration, environmentService: EnvironmentService): TPromise<WorkspaceService> {
function createAndInitializeWorkspaceService(configuration: IWindowConfiguration, environmentService: EnvironmentService): Promise<WorkspaceService> {
return validateFolderUri(configuration.folderUri, configuration.verbose).then(validatedFolderUri => {
const workspaceService = new WorkspaceService(environmentService);
return workspaceService.initialize(configuration.workspace || validatedFolderUri || configuration).then(() => workspaceService, error => workspaceService);
});
}
function validateFolderUri(folderUri: ISingleFolderWorkspaceIdentifier, verbose: boolean): TPromise<uri> {
function validateFolderUri(folderUri: ISingleFolderWorkspaceIdentifier, verbose: boolean): Promise<uri> {
// Return early if we do not have a single folder uri or if it is a non file uri
if (!folderUri || folderUri.scheme !== Schemas.file) {
return TPromise.as(folderUri);
return Promise.resolve(folderUri);
}
// Ensure absolute existing folder path
@ -156,7 +165,19 @@ function validateFolderUri(folderUri: ISingleFolderWorkspaceIdentifier, verbose:
});
}
function createStorageService(workspaceService: IWorkspaceContextService, environmentService: IEnvironmentService): IStorageService {
function createStorageService(environmentService: IEnvironmentService, logService: ILogService): Promise<StorageService> {
perf.mark('willCreateStorageService');
const storageService = new StorageService(':memory:', logService, environmentService);
return storageService.init().then(() => {
perf.mark('didCreateStorageService');
return storageService;
});
}
function createStorageLegacyService(workspaceService: IWorkspaceContextService, environmentService: IEnvironmentService): IStorageLegacyService {
let workspaceId: string;
let secondaryWorkspaceId: number;
@ -188,14 +209,14 @@ function createStorageService(workspaceService: IWorkspaceContextService, enviro
const disableStorage = !!environmentService.extensionTestsPath; // never keep any state when running extension tests!
let storage: IStorage;
let storage: IStorageLegacy;
if (disableStorage) {
storage = inMemoryLocalStorageInstance;
} else {
storage = window.localStorage;
}
return new StorageService(storage, storage, workspaceId, secondaryWorkspaceId);
return new StorageLegacyService(storage, storage, workspaceId, secondaryWorkspaceId);
}
function createLogService(mainProcessClient: ElectronIPCClient, configuration: IWindowConfiguration, environmentService: IEnvironmentService): ILogService {
@ -203,6 +224,7 @@ function createLogService(mainProcessClient: ElectronIPCClient, configuration: I
const consoleLogService = new ConsoleLogService(configuration.logLevel);
const logService = new MultiplexLogService([consoleLogService, spdlogService]);
const logLevelClient = new LogLevelSetterChannelClient(mainProcessClient.getChannel('loglevel'));
return new FollowerLogService(logLevelClient, logService);
}

View file

@ -39,12 +39,11 @@ import { IIntegrityService } from 'vs/platform/integrity/common/integrity';
import { EditorWorkerServiceImpl } from 'vs/editor/common/services/editorWorkerServiceImpl';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { ExtensionService } from 'vs/workbench/services/extensions/electron-browser/extensionService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IStorageLegacyService } from 'vs/platform/storage/common/storageLegacyService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { InstantiationService } from 'vs/platform/instantiation/node/instantiationService';
// import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { ILifecycleService, LifecyclePhase, ShutdownReason, StartupKind } from 'vs/platform/lifecycle/common/lifecycle';
import { ILifecycleService, LifecyclePhase, ShutdownReason, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
import { IMarkerService } from 'vs/platform/markers/common/markers';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ISearchService, ISearchHistoryService } from 'vs/platform/search/common/search';
@ -76,9 +75,9 @@ import { IBroadcastService, BroadcastService } from 'vs/platform/broadcast/elect
import { HashService } from 'vs/workbench/services/hash/node/hashService';
import { IHashService } from 'vs/workbench/services/hash/common/hashService';
import { ILogService } from 'vs/platform/log/common/log';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Event, Emitter } from 'vs/base/common/event';
import { WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme';
import { stat } from 'fs';
import { join } from 'path';
import { ILocalizationsChannel, LocalizationsChannelClient } from 'vs/platform/localizations/node/localizationsIpc';
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue';
@ -108,6 +107,7 @@ export interface ICoreServices {
configurationService: IConfigurationService;
environmentService: IEnvironmentService;
logService: ILogService;
storageLegacyService: IStorageLegacyService;
storageService: IStorageService;
}
@ -116,6 +116,11 @@ export interface ICoreServices {
* With the Shell being the top level element in the page, it is also responsible for driving the layouting.
*/
export class WorkbenchShell extends Disposable {
private readonly _onShutdown = this._register(new Emitter<ShutdownEvent>());
get onShutdown(): Event<ShutdownEvent> { return this._onShutdown.event; }
private storageLegacyService: IStorageLegacyService;
private storageService: IStorageService;
private environmentService: IEnvironmentService;
private logService: ILogService;
@ -146,6 +151,7 @@ export class WorkbenchShell extends Disposable {
this.configurationService = coreServices.configurationService;
this.environmentService = coreServices.environmentService;
this.logService = coreServices.logService;
this.storageLegacyService = coreServices.storageLegacyService;
this.storageService = coreServices.storageService;
this.mainProcessServices = mainProcessServices;
@ -164,6 +170,9 @@ export class WorkbenchShell extends Disposable {
// Warm up font cache information before building up too many dom elements
restoreFontInfo(this.storageService);
readFontInfo(BareFontInfo.createFromRawSettings(this.configurationService.getValue('editor'), browser.getZoomLevel()));
this._register(this.storageService.onWillSaveState(() => {
saveFontInfo(this.storageService); // Keep font info for next startup around
}));
// Workbench
this.workbench = this.createWorkbench(instantiationService, serviceCollection, this.container);
@ -214,11 +223,6 @@ export class WorkbenchShell extends Disposable {
}, 5000);
this._register(eventuallPhaseTimeoutHandle);
// localStorage metrics (TODO@Ben remove me later)
if (!this.environmentService.extensionTestsPath && this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
this.logLocalStorageMetrics();
}
}, error => handleStartupError(this.logService, error));
return workbench;
@ -272,53 +276,6 @@ export class WorkbenchShell extends Disposable {
perf.mark('didStartWorkbench');
}
private logLocalStorageMetrics(): void {
if (this.lifecycleService.startupKind === StartupKind.ReloadedWindow || this.lifecycleService.startupKind === StartupKind.ReopenedWindow) {
return; // avoid logging localStorage metrics for reload/reopen, we prefer cold startup numbers
}
perf.mark('willReadLocalStorage');
const readyToSend = this.storageService.getBoolean('localStorageMetricsReadyToSend2');
perf.mark('didReadLocalStorage');
if (!readyToSend) {
this.storageService.store('localStorageMetricsReadyToSend2', true);
return; // avoid logging localStorage metrics directly after the update, we prefer cold startup numbers
}
if (!this.storageService.getBoolean('localStorageMetricsSent2')) {
perf.mark('willWriteLocalStorage');
this.storageService.store('localStorageMetricsSent2', true);
perf.mark('didWriteLocalStorage');
perf.mark('willStatLocalStorage');
stat(join(this.environmentService.userDataPath, 'Local Storage', 'file__0.localstorage'), (error, stat) => {
perf.mark('didStatLocalStorage');
/* __GDPR__
"localStorageTimers<NUMBER>" : {
"statTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"accessTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"firstReadTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"subsequentReadTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"writeTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"keys" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"size": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
}
*/
this.telemetryService.publicLog('localStorageTimers2', {
'statTime': perf.getDuration('willStatLocalStorage', 'didStatLocalStorage'),
'accessTime': perf.getDuration('willAccessLocalStorage', 'didAccessLocalStorage'),
'firstReadTime': perf.getDuration('willReadWorkspaceIdentifier', 'didReadWorkspaceIdentifier'),
'subsequentReadTime': perf.getDuration('willReadLocalStorage', 'didReadLocalStorage'),
'writeTime': perf.getDuration('willWriteLocalStorage', 'didWriteLocalStorage'),
'keys': window.localStorage.length,
'size': stat ? stat.size : -1
});
});
}
}
private initServiceCollection(container: HTMLElement): [IInstantiationService, ServiceCollection] {
const serviceCollection = new ServiceCollection();
serviceCollection.set(IWorkspaceContextService, this.contextService);
@ -326,8 +283,9 @@ export class WorkbenchShell extends Disposable {
serviceCollection.set(IEnvironmentService, this.environmentService);
serviceCollection.set(ILabelService, new SyncDescriptor(LabelService));
serviceCollection.set(ILogService, this._register(this.logService));
serviceCollection.set(IStorageLegacyService, this.storageLegacyService);
serviceCollection.set(IStorageService, this.storageService);
this.mainProcessServices.forEach((serviceIdentifier, serviceInstance) => {
serviceCollection.set(serviceIdentifier, serviceInstance);
});
@ -353,7 +311,6 @@ export class WorkbenchShell extends Disposable {
serviceCollection.set(IHashService, new SyncDescriptor(HashService));
// Telemetry
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
const channel = getDelayedChannel<ITelemetryAppenderChannel>(sharedProcess.then(c => c.getChannel('telemetryAppender')));
const config: ITelemetryServiceConfig = {
@ -380,7 +337,11 @@ export class WorkbenchShell extends Disposable {
serviceCollection.set(IDialogService, instantiationService.createInstance(DialogService));
const lifecycleService = instantiationService.createInstance(LifecycleService);
this._register(lifecycleService.onShutdown(reason => this.dispose(reason)));
this._register(lifecycleService.onShutdown(event => {
this._onShutdown.fire(event);
this.dispose(event.reason);
}));
serviceCollection.set(ILifecycleService, lifecycleService);
this.lifecycleService = lifecycleService;
@ -505,9 +466,6 @@ export class WorkbenchShell extends Disposable {
dispose(reason = ShutdownReason.QUIT): void {
super.dispose();
// Keep font info for next startup around
saveFontInfo(this.storageService);
// Dispose Workbench
if (this.workbench) {
this.workbench.dispose(reason);
@ -517,7 +475,6 @@ export class WorkbenchShell extends Disposable {
}
}
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
// Foreground

View file

@ -7,7 +7,7 @@ import 'vs/css!./media/workbench';
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable, dispose, toDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { Event, Emitter, once } from 'vs/base/common/event';
import * as DOM from 'vs/base/browser/dom';
import { RunOnceScheduler } from 'vs/base/common/async';
@ -335,7 +335,6 @@ export class Workbench extends Disposable implements IPartService {
// Status bar
this.statusbarPart = this.instantiationService.createInstance(StatusbarPart, Identifiers.STATUSBAR_PART);
this._register(toDisposable(() => this.statusbarPart.shutdown()));
serviceCollection.set(IStatusbarService, this.statusbarPart);
// Progress 2
@ -367,7 +366,6 @@ export class Workbench extends Disposable implements IPartService {
// Sidebar part
this.sidebarPart = this.instantiationService.createInstance(SidebarPart, Identifiers.SIDEBAR_PART);
this._register(toDisposable(() => this.sidebarPart.shutdown()));
// Viewlet service
this.viewletService = this.instantiationService.createInstance(ViewletService, this.sidebarPart);
@ -375,7 +373,6 @@ export class Workbench extends Disposable implements IPartService {
// Panel service (panel part)
this.panelPart = this.instantiationService.createInstance(PanelPart, Identifiers.PANEL_PART);
this._register(toDisposable(() => this.panelPart.shutdown()));
serviceCollection.set(IPanelService, this.panelPart);
// views service
@ -384,7 +381,6 @@ export class Workbench extends Disposable implements IPartService {
// Activity service (activitybar part)
this.activitybarPart = this.instantiationService.createInstance(ActivitybarPart, Identifiers.ACTIVITYBAR_PART);
this._register(toDisposable(() => this.activitybarPart.shutdown()));
const activityService = this.instantiationService.createInstance(ActivityService, this.activitybarPart, this.panelPart);
serviceCollection.set(IActivityService, activityService);
@ -397,7 +393,6 @@ export class Workbench extends Disposable implements IPartService {
// Editor and Group services
const restorePreviousEditorState = !this.hasInitialFilesToOpen;
this.editorPart = this.instantiationService.createInstance(EditorPart, Identifiers.EDITOR_PART, restorePreviousEditorState);
this._register(toDisposable(() => this.editorPart.shutdown()));
this.editorGroupService = this.editorPart;
serviceCollection.set(IEditorGroupsService, this.editorPart);
this.editorService = this.instantiationService.createInstance(EditorService);
@ -405,7 +400,6 @@ export class Workbench extends Disposable implements IPartService {
// Title bar
this.titlebarPart = this.instantiationService.createInstance(TitlebarPart, Identifiers.TITLEBAR_PART);
this._register(toDisposable(() => this.titlebarPart.shutdown()));
serviceCollection.set(ITitleService, this.titlebarPart);
// History
@ -452,12 +446,10 @@ export class Workbench extends Disposable implements IPartService {
// Quick open service (quick open controller)
this.quickOpen = this.instantiationService.createInstance(QuickOpenController);
this._register(toDisposable(() => this.quickOpen.shutdown()));
serviceCollection.set(IQuickOpenService, this.quickOpen);
// Quick input service
this.quickInput = this.instantiationService.createInstance(QuickInputService);
this._register(toDisposable(() => this.quickInput.shutdown()));
serviceCollection.set(IQuickInputService, this.quickInput);
// PreferencesService
@ -483,6 +475,9 @@ export class Workbench extends Disposable implements IPartService {
private registerListeners(): void {
// Storage lifecycle
this._register(this.storageService.onWillSaveState(reason => this.saveState(reason)));
// Listen to visible editor changes
this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidVisibleEditorsChange()));
@ -934,6 +929,7 @@ export class Workbench extends Disposable implements IPartService {
private setPanelPositionFromStorageOrConfig() {
const defaultPanelPosition = this.configurationService.getValue<string>(Workbench.defaultPanelPositionStorageKey);
const panelPosition = this.storageService.get(Workbench.panelPositionStorageKey, StorageScope.WORKSPACE, defaultPanelPosition);
this.panelPosition = (panelPosition === 'right') ? Position.RIGHT : Position.BOTTOM;
}
@ -1132,12 +1128,11 @@ export class Workbench extends Disposable implements IPartService {
return this.instantiationService;
}
dispose(reason = ShutdownReason.QUIT): void {
super.dispose();
private saveState(reason: ShutdownReason): void {
// Restore sidebar if we are being shutdown as a matter of a reload
if (reason === ShutdownReason.RELOAD) {
this.storageService.store(Workbench.sidebarRestoreStorageKey, 'true', StorageScope.WORKSPACE);
this.storageService.store(Workbench.sidebarRestoreStorageKey, true, StorageScope.WORKSPACE);
}
// Preserve zen mode only on reload. Real quit gets out of zen mode so novice users do not get stuck in zen mode.
@ -1149,8 +1144,13 @@ export class Workbench extends Disposable implements IPartService {
if (this.zenMode.active) {
this.toggleZenMode(true);
}
this.storageService.remove(Workbench.zenModeActiveStorageKey, StorageScope.WORKSPACE);
}
}
dispose(reason = ShutdownReason.QUIT): void {
super.dispose();
this.workbenchShutdown = true;
}
@ -1335,8 +1335,7 @@ export class Workbench extends Disposable implements IPartService {
this.shouldCenterLayout = active;
let smartActive = active;
if (this.editorPart.groups.length > 1 && this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize')) {
// Respect the auto resize setting - do not go into centered layout if there is more than 1 group.
smartActive = false;
smartActive = false; // Respect the auto resize setting - do not go into centered layout if there is more than 1 group.
}
// Enter Centered Editor Layout

View file

@ -32,7 +32,7 @@ export class WorkbenchReferencesController extends ReferencesController {
notificationService,
instantiationService,
storageService,
configurationService,
configurationService
);
}
}

View file

@ -23,6 +23,7 @@ import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/
import { ICommandService } from 'vs/platform/commands/common/commands';
import { textLinkForeground, textLinkActiveForeground, focusBorder } from 'vs/platform/theme/common/colorRegistry';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IStorageService } from 'vs/platform/storage/common/storage';
export const COMMENTS_PANEL_ID = 'workbench.panel.comments';
export const COMMENTS_PANEL_TITLE = 'Comments';
@ -42,9 +43,10 @@ export class CommentsPanel extends Panel {
@ICommandService private commandService: ICommandService,
@IOpenerService private openerService: IOpenerService,
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService
) {
super(COMMENTS_PANEL_ID, telemetryService, themeService);
super(COMMENTS_PANEL_ID, telemetryService, themeService, storageService);
}
public create(parent: HTMLElement): Promise<void> {

View file

@ -46,7 +46,7 @@ export class BreakpointsView extends ViewletPanel {
private static readonly MAX_VISIBLE_FILES = 9;
private static readonly MEMENTO = 'breakopintsview.memento';
private settings: any;
private viewState: object;
private list: WorkbenchList<IEnablement>;
private needsRefresh: boolean;
@ -64,7 +64,7 @@ export class BreakpointsView extends ViewletPanel {
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService);
this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize();
this.settings = options.viewletSettings;
this.viewState = options.viewletState;
this.disposables.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange()));
}
@ -231,8 +231,10 @@ export class BreakpointsView extends ViewletPanel {
return Math.min(BreakpointsView.MAX_VISIBLE_FILES, length) * 22;
}
public shutdown(): void {
this.settings[BreakpointsView.MEMENTO] = !this.isExpanded();
public saveState(): void {
this.viewState[BreakpointsView.MEMENTO] = !this.isExpanded();
super.saveState();
}
}

View file

@ -355,7 +355,7 @@ export class LoadedScriptsView extends TreeViewsViewletPanel {
private treeContainer: HTMLElement;
private loadedScriptsItemType: IContextKey<string>;
private settings: any;
private viewState: object;
constructor(
options: IViewletViewOptions,
@ -370,7 +370,7 @@ export class LoadedScriptsView extends TreeViewsViewletPanel {
@IDebugService private debugService: IDebugService
) {
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService);
this.settings = options.viewletSettings;
this.viewState = options.viewletState;
this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService);
}
@ -464,9 +464,10 @@ export class LoadedScriptsView extends TreeViewsViewletPanel {
super.layoutBody(size);
}
public shutdown(): void {
this.settings[LoadedScriptsView.MEMENTO] = !this.isExpanded();
super.shutdown();
public saveState(): void {
this.viewState[LoadedScriptsView.MEMENTO] = !this.isExpanded();
super.saveState();
}
dispose(): void {

View file

@ -36,7 +36,7 @@ export class CallStackView extends TreeViewsViewletPanel {
private pauseMessage: HTMLSpanElement;
private pauseMessageLabel: HTMLSpanElement;
private onCallStackChangeScheduler: RunOnceScheduler;
private settings: any;
private viewState: object;
private needsRefresh: boolean;
private ignoreSelectionChangedEvent: boolean;
private treeContainer: HTMLElement;
@ -53,7 +53,7 @@ export class CallStackView extends TreeViewsViewletPanel {
@IContextKeyService contextKeyService: IContextKeyService
) {
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService);
this.settings = options.viewletSettings;
this.viewState = options.viewletState;
this.callStackItemType = CONTEXT_CALLSTACK_ITEM_TYPE.bindTo(contextKeyService);
// Create scheduler to prevent unnecessary flashing of tree when reacting to changes
@ -228,9 +228,10 @@ export class CallStackView extends TreeViewsViewletPanel {
});
}
public shutdown(): void {
this.settings[CallStackView.MEMENTO] = !this.isExpanded();
super.shutdown();
public saveState(): void {
this.viewState[CallStackView.MEMENTO] = !this.isExpanded();
super.saveState();
}
}

View file

@ -238,7 +238,7 @@ export class ConfigurationManager implements IConfigurationManager {
}
}));
this.toDispose.push(lifecycleService.onShutdown(this.store, this));
this.toDispose.push(this.storageService.onWillSaveState(this.saveState, this));
}
private initLaunches(): void {
@ -382,7 +382,7 @@ export class ConfigurationManager implements IConfigurationManager {
return this.extensionService.activateByEvent(activationEvent).then(() => this.extensionService.activateByEvent('onDebug'));
}
private store(): void {
private saveState(): void {
this.storageService.store(DEBUG_SELECTED_CONFIG_NAME_KEY, this.selectedName, StorageScope.WORKSPACE);
if (this.selectedLaunch) {
this.storageService.store(DEBUG_SELECTED_ROOT, this.selectedLaunch.uri.toString(), StorageScope.WORKSPACE);

View file

@ -134,7 +134,7 @@ export class DebugService implements IDebugService {
this.viewModel = new ViewModel(contextKeyService);
this.toDispose.push(this.fileService.onFileChanges(e => this.onFileChanges(e)));
this.lifecycleService.onShutdown(this.store, this);
this.toDispose.push(this.storageService.onWillSaveState(this.saveState, this));
this.lifecycleService.onShutdown(this.dispose, this);
this.toDispose.push(this.broadcastService.onBroadcast(broadcast => {
@ -968,7 +968,7 @@ export class DebugService implements IDebugService {
return result || [];
}
private store(): void {
private saveState(): void {
const breakpoints = this.model.getBreakpoints();
if (breakpoints.length) {
this.storageService.store(DEBUG_BREAKPOINTS_KEY, JSON.stringify(breakpoints), StorageScope.WORKSPACE);

View file

@ -101,9 +101,9 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
@IThemeService protected themeService: IThemeService,
@IModelService private modelService: IModelService,
@IContextKeyService private contextKeyService: IContextKeyService,
@ICodeEditorService codeEditorService: ICodeEditorService,
@ICodeEditorService codeEditorService: ICodeEditorService
) {
super(REPL_ID, telemetryService, themeService);
super(REPL_ID, telemetryService, themeService, storageService);
this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT;
this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50);
@ -257,15 +257,6 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
return result;
}
shutdown(): void {
const replHistory = this.history.getHistory();
if (replHistory.length) {
this.storageService.store(HISTORY_STORAGE_KEY, JSON.stringify(replHistory), StorageScope.WORKSPACE);
} else {
this.storageService.remove(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE);
}
}
// --- Cached locals
@memoize
private get characterWidth(): number {
@ -411,6 +402,17 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
this.replInput.setDecorations(DECORATION_KEY, decorations);
}
protected saveState(): void {
const replHistory = this.history.getHistory();
if (replHistory.length) {
this.storageService.store(HISTORY_STORAGE_KEY, JSON.stringify(replHistory), StorageScope.WORKSPACE);
} else {
this.storageService.remove(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE);
}
super.saveState();
}
dispose(): void {
this.replInput.dispose();
if (this.replElementsChangeListener) {

View file

@ -36,7 +36,7 @@ export class VariablesView extends TreeViewsViewletPanel {
private static readonly MEMENTO = 'variablesview.memento';
private onFocusStackFrameScheduler: RunOnceScheduler;
private settings: any;
private viewState: object;
private expandedElements: any[];
private needsRefresh: boolean;
private treeContainer: HTMLElement;
@ -51,7 +51,7 @@ export class VariablesView extends TreeViewsViewletPanel {
) {
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService);
this.settings = options.viewletSettings;
this.viewState = options.viewletState;
this.expandedElements = [];
// Use scheduler to prevent unnecessary flashing
this.onFocusStackFrameScheduler = new RunOnceScheduler(() => {
@ -150,9 +150,10 @@ export class VariablesView extends TreeViewsViewletPanel {
});
}
public shutdown(): void {
this.settings[VariablesView.MEMENTO] = !this.isExpanded();
super.shutdown();
public saveState(): void {
this.viewState[VariablesView.MEMENTO] = !this.isExpanded();
super.saveState();
}
}

View file

@ -38,7 +38,7 @@ export class WatchExpressionsView extends TreeViewsViewletPanel {
private static readonly MEMENTO = 'watchexpressionsview.memento';
private onWatchExpressionsUpdatedScheduler: RunOnceScheduler;
private treeContainer: HTMLElement;
private settings: any;
private viewState: object;
private needsRefresh: boolean;
constructor(
@ -50,7 +50,7 @@ export class WatchExpressionsView extends TreeViewsViewletPanel {
@IConfigurationService configurationService: IConfigurationService
) {
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService);
this.settings = options.viewletSettings;
this.viewState = options.viewletState;
this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => {
this.needsRefresh = false;
@ -133,9 +133,10 @@ export class WatchExpressionsView extends TreeViewsViewletPanel {
});
}
public shutdown(): void {
this.settings[WatchExpressionsView.MEMENTO] = !this.isExpanded();
super.shutdown();
public saveState(): void {
this.viewState[WatchExpressionsView.MEMENTO] = !this.isExpanded();
super.saveState();
}
}

View file

@ -207,7 +207,7 @@ export class ExperimentService extends Disposable implements IExperimentService
if (Array.isArray(allExperimentIdsFromStorage)) {
allExperimentIdsFromStorage.forEach(experiment => {
if (enabledExperiments.indexOf(experiment) === -1) {
this.storageService.remove('experiments.' + experiment);
this.storageService.remove(`experiments.${experiment}`, StorageScope.GLOBAL);
}
});
}
@ -255,7 +255,7 @@ export class ExperimentService extends Disposable implements IExperimentService
return this.shouldRunExperiment(experiment, processedExperiment).then((state: ExperimentState) => {
experimentState.state = processedExperiment.state = state;
this.storageService.store(storageKey, JSON.stringify(experimentState));
this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL);
if (state === ExperimentState.Run) {
this.fireRunExperiment(processedExperiment);
@ -280,7 +280,7 @@ export class ExperimentService extends Disposable implements IExperimentService
const runExperimentIdsFromStorage: string[] = safeParse(this.storageService.get('currentOrPreviouslyRunExperiments', StorageScope.GLOBAL), []);
if (runExperimentIdsFromStorage.indexOf(experiment.id)) {
runExperimentIdsFromStorage.push(experiment.id);
this.storageService.store('currentOrPreviouslyRunExperiments', JSON.stringify(runExperimentIdsFromStorage));
this.storageService.store('currentOrPreviouslyRunExperiments', JSON.stringify(runExperimentIdsFromStorage), StorageScope.GLOBAL);
}
}

View file

@ -6,7 +6,6 @@
import * as assert from 'assert';
import { ExperimentService, ExperimentActionType, ExperimentState } from 'vs/workbench/parts/experiments/node/experimentService';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { TestLifecycleService } from 'vs/workbench/test/workbenchTestServices';
import {
@ -15,7 +14,7 @@ import {
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementService, getLocalExtensionIdFromManifest } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { Emitter } from 'vs/base/common/event';
import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/common/extensionEnablementService.test';
import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test';
import { URLService } from 'vs/platform/url/common/urlService';
import { IURLService } from 'vs/platform/url/common/url';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@ -26,6 +25,7 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { assign } from 'vs/base/common/objects';
import { URI } from 'vs/base/common/uri';
import { IStorageService } from 'vs/platform/storage/common/storage';
let experimentData = {
experiments: []

View file

@ -9,9 +9,8 @@ import { IExperiment, ExperimentActionType, IExperimentService, ExperimentState
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { Emitter } from 'vs/base/common/event';
import { TestExperimentService } from 'vs/workbench/parts/experiments/test/node/experimentService.test';
import { TestExperimentService } from 'vs/workbench/parts/experiments/test/electron-browser/experimentService.test';
import { ExperimentalPrompts } from 'vs/workbench/parts/experiments/electron-browser/experimentalPrompt';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { INotificationService, Severity, IPromptChoice } from 'vs/platform/notification/common/notification';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { TestLifecycleService } from 'vs/workbench/test/workbenchTestServices';
@ -19,6 +18,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { TPromise } from 'vs/base/common/winjs.base';
import { IStorageService } from 'vs/platform/storage/common/storage';
suite('Experimental Prompts', () => {
let instantiationService: TestInstantiationService;

View file

@ -47,6 +47,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { ExtensionsTree, IExtensionData } from 'vs/workbench/parts/extensions/browser/extensionsViewer';
import { ShowCurrentReleaseNotesAction } from 'vs/workbench/parts/update/electron-browser/update';
import { KeybindingParser } from 'vs/base/common/keybindingParser';
import { IStorageService } from 'vs/platform/storage/common/storage';
function renderBody(body: string): string {
const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-core-resource://');
@ -187,8 +188,9 @@ export class ExtensionEditor extends BaseEditor {
@IOpenerService private readonly openerService: IOpenerService,
@IPartService private readonly partService: IPartService,
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
@IStorageService storageService: IStorageService
) {
super(ExtensionEditor.ID, telemetryService, themeService);
super(ExtensionEditor.ID, telemetryService, themeService, storageService);
this.disposables = [];
this.extensionReadme = null;
this.extensionChangelog = null;

View file

@ -38,6 +38,7 @@ import { IDebugService } from 'vs/workbench/parts/debug/common/debug';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { randomPort } from 'vs/base/node/ports';
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IStorageService } from 'vs/platform/storage/common/storage';
export const IExtensionHostProfileService = createDecorator<IExtensionHostProfileService>('extensionHostProfileService');
export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey<string>('profileSessionState', 'none');
@ -110,8 +111,9 @@ export class RuntimeExtensionsEditor extends BaseEditor {
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService,
@IStorageService storageService: IStorageService
) {
super(RuntimeExtensionsEditor.ID, telemetryService, themeService);
super(RuntimeExtensionsEditor.ID, telemetryService, themeService, storageService);
this._list = null;
this._profileInfo = this._extensionHostProfileService.lastProfile;

View file

@ -16,7 +16,7 @@ import {
import { getGalleryExtensionId, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionManagementService, getLocalExtensionIdFromGallery, getLocalExtensionIdFromManifest } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService';
import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/common/extensionEnablementService.test';
import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { IURLService } from 'vs/platform/url/common/url';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';

View file

@ -34,10 +34,9 @@ import { IPager } from 'vs/base/common/paging';
import { assign } from 'vs/base/common/objects';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/common/extensionEnablementService.test';
import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test';
import { IURLService } from 'vs/platform/url/common/url';
import product from 'vs/platform/node/product';
import { ITextModel } from 'vs/editor/common/model';
@ -46,7 +45,8 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { INotificationService, Severity, IPromptChoice, IPromptOptions } from 'vs/platform/notification/common/notification';
import { URLService } from 'vs/platform/url/common/urlService';
import { IExperimentService } from 'vs/workbench/parts/experiments/node/experimentService';
import { TestExperimentService } from 'vs/workbench/parts/experiments/test/node/experimentService.test';
import { TestExperimentService } from 'vs/workbench/parts/experiments/test/electron-browser/experimentService.test';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
const mockExtensionGallery: IGalleryExtension[] = [
aGalleryExtension('MockExtension1', {

View file

@ -17,7 +17,7 @@ import {
import { getGalleryExtensionId, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionManagementService, getLocalExtensionIdFromManifest } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService';
import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/common/extensionEnablementService.test';
import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { IURLService } from 'vs/platform/url/common/url';
import { Emitter } from 'vs/base/common/event';

View file

@ -17,7 +17,7 @@ import {
import { getGalleryExtensionId, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionManagementService, getLocalExtensionIdFromGallery, getLocalExtensionIdFromManifest } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService';
import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/common/extensionEnablementService.test';
import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { IURLService } from 'vs/platform/url/common/url';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';

View file

@ -14,6 +14,7 @@ import { URI } from 'vs/base/common/uri';
import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/parts/files/common/files';
import { IFileService } from 'vs/platform/files/common/files';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IStorageService } from 'vs/platform/storage/common/storage';
/**
* An implementation of editor for binary files like images.
@ -27,7 +28,8 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor {
@IThemeService themeService: IThemeService,
@IFileService fileService: IFileService,
@IWindowsService private windowsService: IWindowsService,
@IEditorService private editorService: IEditorService
@IEditorService private editorService: IEditorService,
@IStorageService storageService: IStorageService
) {
super(
BinaryFileEditor.ID,
@ -37,7 +39,8 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor {
},
telemetryService,
themeService,
fileService
fileService,
storageService
);
}

View file

@ -263,13 +263,12 @@ export class TextFileEditor extends BaseTextEditor {
super.clearInput();
}
shutdown(): void {
protected saveState(): void {
// Update/clear editor view State
this.doSaveOrClearTextEditorViewState(this.input);
// Call Super
super.shutdown();
super.saveState();
}
private doSaveOrClearTextEditorViewState(input: FileEditorInput): void {

View file

@ -150,7 +150,7 @@ export class ExplorerViewlet extends ViewContainerViewlet implements IExplorerVi
private static readonly EXPLORER_VIEWS_STATE = 'workbench.explorer.views.state';
private viewletState: FileViewletState;
private fileViewletState: FileViewletState;
private viewletVisibleContextKey: IContextKey<boolean>;
constructor(
@ -169,7 +169,7 @@ export class ExplorerViewlet extends ViewContainerViewlet implements IExplorerVi
) {
super(VIEWLET_ID, ExplorerViewlet.EXPLORER_VIEWS_STATE, true, configurationService, partService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
this.viewletState = new FileViewletState();
this.fileViewletState = new FileViewletState();
this.viewletVisibleContextKey = ExplorerViewletVisibleContext.bindTo(contextKeyService);
this._register(this.contextService.onDidChangeWorkspaceName(e => this.updateTitleArea()));
@ -218,7 +218,7 @@ export class ExplorerViewlet extends ViewContainerViewlet implements IExplorerVi
});
const explorerInstantiator = this.instantiationService.createChild(new ServiceCollection([IEditorService, delegatingEditorService]));
return explorerInstantiator.createInstance(ExplorerView, <IExplorerViewOptions>{ ...options, viewletState: this.viewletState });
return explorerInstantiator.createInstance(ExplorerView, <IExplorerViewOptions>{ ...options, viewletState: this.fileViewletState });
}
return super.createView(viewDescriptor, options);
}
@ -242,13 +242,13 @@ export class ExplorerViewlet extends ViewContainerViewlet implements IExplorerVi
public getActionRunner(): IActionRunner {
if (!this.actionRunner) {
this.actionRunner = new ActionRunner(this.viewletState);
this.actionRunner = new ActionRunner(this.fileViewletState);
}
return this.actionRunner;
}
public getViewletState(): FileViewletState {
return this.viewletState;
return this.fileViewletState;
}
focus(): void {

View file

@ -26,7 +26,7 @@ import { SAVE_FILE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_AS_COMMAND_ID,
import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel';
import { INotificationService, INotificationHandle, INotificationActions, Severity } from 'vs/platform/notification/common/notification';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ExecuteCommandAction } from 'vs/platform/actions/common/actions';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { once } from 'vs/base/common/event';
@ -111,7 +111,7 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I
// If the user tried to save from the opened conflict editor, show its message again
if (this.activeConflictResolutionResource && this.activeConflictResolutionResource.toString() === model.getResource().toString()) {
if (this.storageService.getBoolean(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY)) {
if (this.storageService.getBoolean(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, StorageScope.GLOBAL)) {
return; // return if this message is ignored
}
@ -211,7 +211,7 @@ class DoNotShowResolveConflictLearnMoreAction extends Action {
}
run(notification: IDisposable): Promise<any> {
this.storageService.store(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, true);
this.storageService.store(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, true, StorageScope.GLOBAL);
// Hide notification
notification.dispose();
@ -247,7 +247,7 @@ class ResolveSaveConflictAction extends Action {
options: { pinned: true }
}
).then(() => {
if (this.storageService.getBoolean(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY)) {
if (this.storageService.getBoolean(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, StorageScope.GLOBAL)) {
return; // return if this message is ignored
}

View file

@ -149,8 +149,4 @@ export class EmptyView extends ViewletPanel {
public getActionItem(action: IAction): IActionItem {
return null;
}
public shutdown(): void {
// Subclass to implement
}
}

View file

@ -74,7 +74,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView
private shouldRefresh: boolean;
private autoReveal: boolean;
private sortOrder: SortOrder;
private settings: object;
private viewState: object;
private treeContainer: HTMLElement;
private dragHandler: DelayedDragHandler;
private decorationProvider: ExplorerDecorationsProvider;
@ -98,7 +98,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView
) {
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService);
this.settings = options.viewletSettings;
this.viewState = options.viewletState;
this.viewletState = options.viewletState;
this.autoReveal = true;
@ -173,8 +173,8 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView
// Load and Fill Viewer
let targetsToExpand = [];
if (this.settings[ExplorerView.MEMENTO_EXPANDED_FOLDER_RESOURCES]) {
targetsToExpand = this.settings[ExplorerView.MEMENTO_EXPANDED_FOLDER_RESOURCES].map((e: string) => URI.parse(e));
if (this.viewState[ExplorerView.MEMENTO_EXPANDED_FOLDER_RESOURCES]) {
targetsToExpand = this.viewState[ExplorerView.MEMENTO_EXPANDED_FOLDER_RESOURCES].map((e: string) => URI.parse(e));
}
this.doRefresh(targetsToExpand).then(() => {
@ -236,7 +236,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView
if (activeFile) {
// Always remember last opened file
this.settings[ExplorerView.MEMENTO_LAST_ACTIVE_FILE_RESOURCE] = activeFile.toString();
this.viewState[ExplorerView.MEMENTO_LAST_ACTIVE_FILE_RESOURCE] = activeFile.toString();
// Select file if input is inside workspace
if (this.isVisible() && !this.isDisposed && this.contextService.isInsideWorkspace(activeFile)) {
@ -252,7 +252,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView
// Handle closed or untitled file (convince explorer to not reopen any file when getting visible)
const activeInput = this.editorService.activeEditor;
if (!activeInput || toResource(activeInput, { supportSideBySide: true, filter: Schemas.untitled })) {
this.settings[ExplorerView.MEMENTO_LAST_ACTIVE_FILE_RESOURCE] = void 0;
this.viewState[ExplorerView.MEMENTO_LAST_ACTIVE_FILE_RESOURCE] = void 0;
clearFocus = true;
}
@ -355,8 +355,8 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView
// Otherwise restore last used file: By lastActiveFileResource
let lastActiveFileResource: URI;
if (this.settings[ExplorerView.MEMENTO_LAST_ACTIVE_FILE_RESOURCE]) {
lastActiveFileResource = URI.parse(this.settings[ExplorerView.MEMENTO_LAST_ACTIVE_FILE_RESOURCE]);
if (this.viewState[ExplorerView.MEMENTO_LAST_ACTIVE_FILE_RESOURCE]) {
lastActiveFileResource = URI.parse(this.viewState[ExplorerView.MEMENTO_LAST_ACTIVE_FILE_RESOURCE]);
}
if (lastActiveFileResource && this.isCreated && this.model.findClosest(lastActiveFileResource)) {
@ -608,9 +608,9 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView
// delete events can result in UI activity that will fill the memento again when multiple
// editors are closing)
setTimeout(() => {
const lastActiveResource: string = this.settings[ExplorerView.MEMENTO_LAST_ACTIVE_FILE_RESOURCE];
const lastActiveResource: string = this.viewState[ExplorerView.MEMENTO_LAST_ACTIVE_FILE_RESOURCE];
if (lastActiveResource && e.contains(URI.parse(lastActiveResource), FileChangeType.DELETED)) {
this.settings[ExplorerView.MEMENTO_LAST_ACTIVE_FILE_RESOURCE] = null;
this.viewState[ExplorerView.MEMENTO_LAST_ACTIVE_FILE_RESOURCE] = null;
}
});
@ -1003,7 +1003,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView
return this.tree.reveal(element, relativeTop);
}
public shutdown(): void {
saveState(): void {
// Keep list of expanded folders to restore on next load
if (this.isCreated) {
@ -1012,18 +1012,18 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView
.map((e: ExplorerItem) => e.resource.toString());
if (expanded.length) {
this.settings[ExplorerView.MEMENTO_EXPANDED_FOLDER_RESOURCES] = expanded;
this.viewState[ExplorerView.MEMENTO_EXPANDED_FOLDER_RESOURCES] = expanded;
} else {
delete this.settings[ExplorerView.MEMENTO_EXPANDED_FOLDER_RESOURCES];
delete this.viewState[ExplorerView.MEMENTO_EXPANDED_FOLDER_RESOURCES];
}
}
// Clean up last focused if not set
if (!this.settings[ExplorerView.MEMENTO_LAST_ACTIVE_FILE_RESOURCE]) {
delete this.settings[ExplorerView.MEMENTO_LAST_ACTIVE_FILE_RESOURCE];
if (!this.viewState[ExplorerView.MEMENTO_LAST_ACTIVE_FILE_RESOURCE]) {
delete this.viewState[ExplorerView.MEMENTO_LAST_ACTIVE_FILE_RESOURCE];
}
super.shutdown();
super.saveState();
}
dispose(): void {

View file

@ -59,9 +59,9 @@ export class HtmlPreviewPart extends BaseWebviewEditor {
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IEditorGroupsService readonly editorGroupService: IEditorGroupsService
) {
super(HtmlPreviewPart.ID, telemetryService, themeService, contextKeyService);
super(HtmlPreviewPart.ID, telemetryService, themeService, contextKeyService, _storageService);
this.editorMemento = this.getEditorMemento<HtmlPreviewEditorViewState>(_storageService, editorGroupService, this.viewStateStorageKey);
this.editorMemento = this.getEditorMemento<HtmlPreviewEditorViewState>(editorGroupService, this.viewStateStorageKey);
}
dispose(): void {
@ -162,13 +162,14 @@ export class HtmlPreviewPart extends BaseWebviewEditor {
super.clearInput();
}
public shutdown(): void {
protected saveState(): void {
if (this.input instanceof HtmlInput) {
this.saveHTMLPreviewViewState(this.input, {
scrollYPercentage: this._scrollYPercentage
});
}
super.shutdown();
super.saveState();
}
public sendMessage(data: any): void {
@ -242,10 +243,10 @@ export class HtmlPreviewPart extends BaseWebviewEditor {
}
private saveHTMLPreviewViewState(input: HtmlInput, editorViewState: HtmlPreviewEditorViewState): void {
this.editorMemento.saveState(this.group, input, editorViewState);
this.editorMemento.saveEditorState(this.group, input, editorViewState);
}
private loadHTMLPreviewViewState(input: HtmlInput): HtmlPreviewEditorViewState {
return this.editorMemento.loadState(this.group, input);
return this.editorMemento.loadEditorState(this.group, input);
}
}

View file

@ -71,7 +71,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo
private onDidInstallExtension(e: DidInstallExtensionEvent): void {
const donotAskUpdateKey = 'langugage.update.donotask';
if (!this.storageService.getBoolean(donotAskUpdateKey) && e.local && e.operation === InstallOperation.Install && e.local.manifest.contributes && e.local.manifest.contributes.localizations && e.local.manifest.contributes.localizations.length) {
if (!this.storageService.getBoolean(donotAskUpdateKey, StorageScope.GLOBAL) && e.local && e.operation === InstallOperation.Install && e.local.manifest.contributes && e.local.manifest.contributes.localizations && e.local.manifest.contributes.localizations.length) {
const locale = e.local.manifest.contributes.localizations[0].languageId;
if (platform.language !== locale) {
const updateAndRestart = platform.locale !== locale;
@ -92,7 +92,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo
}, {
label: localize('neverAgain', "Don't Show Again"),
isSecondary: true,
run: () => this.storageService.store(donotAskUpdateKey, true)
run: () => this.storageService.store(donotAskUpdateKey, true, StorageScope.GLOBAL)
}],
{ sticky: true }
);

View file

@ -22,8 +22,7 @@ import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/ran
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IMarkersWorkbenchService } from 'vs/workbench/parts/markers/electron-browser/markers';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Scope } from 'vs/workbench/common/memento';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { localize } from 'vs/nls';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { Iterator } from 'vs/base/common/iterator';
@ -78,7 +77,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
private treeContainer: HTMLElement;
private messageBoxContainer: HTMLElement;
private ariaLabelElement: HTMLElement;
private panelSettings: any;
private panelState: object;
private panelFoucusContextKey: IContextKey<boolean>;
private filter: Filter;
@ -103,9 +102,9 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
@IMenuService private menuService: IMenuService,
@IKeybindingService private keybindingService: IKeybindingService,
) {
super(Constants.MARKERS_PANEL_ID, telemetryService, themeService);
super(Constants.MARKERS_PANEL_ID, telemetryService, themeService, storageService);
this.panelFoucusContextKey = Constants.MarkerPanelFocusContextKey.bindTo(contextKeyService);
this.panelSettings = this.getMemento(storageService, Scope.WORKSPACE);
this.panelState = this.getMemento(StorageScope.WORKSPACE);
this.setCurrentActiveEditor();
}
@ -347,7 +346,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
this.tree.focusFirst();
});
this.filterAction = this.instantiationService.createInstance(MarkersFilterAction, { filterText: this.panelSettings['filter'] || '', filterHistory: this.panelSettings['filterHistory'] || [], useFilesExclude: !!this.panelSettings['useFilesExclude'] });
this.filterAction = this.instantiationService.createInstance(MarkersFilterAction, { filterText: this.panelState['filter'] || '', filterHistory: this.panelState['filterHistory'] || [], useFilesExclude: !!this.panelState['useFilesExclude'] });
this.actions = [this.filterAction, this.collapseAllAction];
}
@ -642,17 +641,16 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
return { total, filtered };
}
public shutdown(): void {
// store memento
this.panelSettings['filter'] = this.filterAction.filterText;
this.panelSettings['filterHistory'] = this.filterAction.filterHistory;
this.panelSettings['useFilesExclude'] = this.filterAction.useFilesExclude;
protected saveState(): void {
this.panelState['filter'] = this.filterAction.filterText;
this.panelState['filterHistory'] = this.filterAction.filterHistory;
this.panelState['useFilesExclude'] = this.filterAction.useFilesExclude;
super.shutdown();
super.saveState();
}
public dispose(): void {
super.dispose();
this.tree.dispose();
}
}
}

View file

@ -461,8 +461,8 @@ export class OutputService extends Disposable implements IOutputService, ITextMo
}
this._register(registry.onDidRegisterChannel(this.onDidRegisterChannel, this));
panelService.onDidPanelOpen(this.onDidPanelOpen, this);
panelService.onDidPanelClose(this.onDidPanelClose, this);
this._register(panelService.onDidPanelOpen(this.onDidPanelOpen, this));
this._register(panelService.onDidPanelClose(this.onDidPanelClose, this));
// Set active channel to first channel if not set
if (!this.activeChannel) {
@ -470,7 +470,8 @@ export class OutputService extends Disposable implements IOutputService, ITextMo
this.activeChannel = channels && channels.length > 0 ? this.getChannel(channels[0].id) : null;
}
this.lifecycleService.onShutdown(() => this.onShutdown());
this._register(this.lifecycleService.onShutdown(() => this.dispose()));
this._register(this.storageService.onWillSaveState(() => this.saveState()));
}
provideTextContent(resource: URI): TPromise<ITextModel> {
@ -633,11 +634,10 @@ export class OutputService extends Disposable implements IOutputService, ITextMo
return this.instantiationService.createInstance(ResourceEditorInput, nls.localize('output', "{0} - Output", channel.label), nls.localize('channel', "Output channel for '{0}'", channel.label), resource);
}
onShutdown(): void {
private saveState(): void {
if (this.activeChannel) {
this.storageService.store(OUTPUT_ACTIVE_CHANNEL_KEY, this.activeChannel.id, StorageScope.WORKSPACE);
}
this.dispose();
}
}

View file

@ -44,6 +44,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { KeybindingsEditorInput } from 'vs/workbench/services/preferences/common/preferencesEditorInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
import { IStorageService } from 'vs/platform/storage/common/storage';
let $ = DOM.$;
@ -89,9 +90,10 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor
@INotificationService private notificationService: INotificationService,
@IClipboardService private clipboardService: IClipboardService,
@IInstantiationService private instantiationService: IInstantiationService,
@IEditorService private editorService: IEditorService
@IEditorService private editorService: IEditorService,
@IStorageService storageService: IStorageService
) {
super(KeybindingsEditor.ID, telemetryService, themeService);
super(KeybindingsEditor.ID, telemetryService, themeService, storageService);
this.delayedFiltering = new Delayer<void>(300);
this._register(keybindingsService.onDidUpdateKeybindings(() => this.render(false, CancellationToken.None)));

View file

@ -94,9 +94,10 @@ export class PreferencesEditor extends BaseEditor {
@IContextKeyService private contextKeyService: IContextKeyService,
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IProgressService private progressService: IProgressService
@IProgressService private progressService: IProgressService,
@IStorageService storageService: IStorageService
) {
super(PreferencesEditor.ID, telemetryService, themeService);
super(PreferencesEditor.ID, telemetryService, themeService, storageService);
this.defaultSettingsEditorContextKey = CONTEXT_SETTINGS_EDITOR.bindTo(this.contextKeyService);
this.searchFocusContextKey = CONTEXT_SETTINGS_SEARCH_FOCUS.bindTo(this.contextKeyService);
this.delayedFilterLogging = new Delayer<void>(1000);

View file

@ -137,7 +137,7 @@ export class SettingsEditor2 extends BaseEditor {
@IEditorGroupsService protected editorGroupService: IEditorGroupsService,
@IKeybindingService private keybindingService: IKeybindingService
) {
super(SettingsEditor2.ID, telemetryService, themeService);
super(SettingsEditor2.ID, telemetryService, themeService, storageService);
this.delayedFilterLogging = new Delayer<void>(1000);
this.localSearchDelayer = new Delayer(300);
this.remoteSearchThrottle = new ThrottledDelayer(200);
@ -153,7 +153,7 @@ export class SettingsEditor2 extends BaseEditor {
this.scheduledRefreshes = new Map<string, DOM.IFocusTracker>();
this.editorMemento = this.getEditorMemento<ISettingsEditor2State>(storageService, editorGroupService, SETTINGS_EDITOR_STATE_KEY);
this.editorMemento = this.getEditorMemento<ISettingsEditor2State>(editorGroupService, SETTINGS_EDITOR_STATE_KEY);
this._register(configurationService.onDidChangeConfiguration(e => {
if (e.source !== ConfigurationTarget.DEFAULT) {
@ -222,7 +222,7 @@ export class SettingsEditor2 extends BaseEditor {
}
private restoreCachedState(): void {
const cachedState = this.editorMemento.loadState(this.group, this.input);
const cachedState = this.editorMemento.loadEditorState(this.group, this.input);
if (cachedState && typeof cachedState.target === 'object') {
cachedState.target = URI.revive(cachedState.target);
}
@ -257,7 +257,7 @@ export class SettingsEditor2 extends BaseEditor {
clearInput(): void {
this.inSettingsEditorContextKey.set(false);
this.editorMemento.clearState(this.input, this.group);
this.editorMemento.clearEditorState(this.input, this.group);
super.clearInput();
}
@ -920,7 +920,7 @@ export class SettingsEditor2 extends BaseEditor {
this.settingsTreeModel.update(resolvedSettingsRoot);
// Make sure that all extensions' settings are included in search results
const cachedState = this.editorMemento.loadState(this.group, this.input);
const cachedState = this.editorMemento.loadEditorState(this.group, this.input);
if (cachedState && cachedState.searchQuery) {
this.triggerSearch(cachedState.searchQuery);
} else {
@ -1291,13 +1291,14 @@ export class SettingsEditor2 extends BaseEditor {
this.settingsTreeRenderer.updateWidth(dimension.width);
}
shutdown(): void {
protected saveState(): void {
if (this.isVisible()) {
const searchQuery = this.searchWidget.getValue().trim();
const target = this.settingsTargetsWidget.settingsTarget as SettingsTarget;
this.editorMemento.saveState(this.group, this.input, { searchQuery, target });
this.editorMemento.saveEditorState(this.group, this.input, { searchQuery, target });
}
super.shutdown();
super.saveState();
}
}

View file

@ -21,8 +21,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { registerEditorAction, EditorAction, IEditorCommandMenuOptions } from 'vs/editor/browser/editorExtensions';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { once } from 'vs/base/common/event';
import { LRUCache } from 'vs/base/common/map';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@ -67,7 +66,6 @@ class CommandsHistory {
constructor(
@IStorageService private storageService: IStorageService,
@ILifecycleService private lifecycleService: ILifecycleService,
@IConfigurationService private configurationService: IConfigurationService
) {
this.updateConfiguration();
@ -85,7 +83,7 @@ class CommandsHistory {
}
private load(): void {
const raw = this.storageService.get(CommandsHistory.PREF_KEY_CACHE);
const raw = this.storageService.get(CommandsHistory.PREF_KEY_CACHE, StorageScope.GLOBAL);
let serializedCache: ISerializedCommandHistory;
if (raw) {
try {
@ -106,20 +104,20 @@ class CommandsHistory {
entries.forEach(entry => commandHistory.set(entry.key, entry.value));
}
commandCounter = this.storageService.getInteger(CommandsHistory.PREF_KEY_COUNTER, void 0, commandCounter);
commandCounter = this.storageService.getInteger(CommandsHistory.PREF_KEY_COUNTER, StorageScope.GLOBAL, commandCounter);
}
private registerListeners(): void {
this.configurationService.onDidChangeConfiguration(e => this.updateConfiguration());
once(this.lifecycleService.onShutdown)(reason => this.save());
once(this.storageService.onWillSaveState)(() => this.saveState());
}
private save(): void {
private saveState(): void {
const serializedCache: ISerializedCommandHistory = { usesLRU: true, entries: [] };
commandHistory.forEach((value, key) => serializedCache.entries.push({ key, value }));
this.storageService.store(CommandsHistory.PREF_KEY_CACHE, JSON.stringify(serializedCache));
this.storageService.store(CommandsHistory.PREF_KEY_COUNTER, commandCounter);
this.storageService.store(CommandsHistory.PREF_KEY_CACHE, JSON.stringify(serializedCache), StorageScope.GLOBAL);
this.storageService.store(CommandsHistory.PREF_KEY_COUNTER, commandCounter, StorageScope.GLOBAL);
}
push(commandId: string): void {

Some files were not shown because too many files have changed in this diff Show more