提交 fae8b9b7 编写于 作者: B Benjamin Pasero

Merge branch 'master' into ben/hot-exit-restore

......@@ -10,6 +10,7 @@ import * as platform from 'vs/base/common/platform';
import { Promise, TPromise, ValueCallback, ErrorCallback, ProgressCallback } from 'vs/base/common/winjs.base';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Disposable } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
function isThenable<T>(obj: any): obj is Thenable<T> {
return obj && typeof (<Thenable<any>>obj).then === 'function';
......@@ -430,11 +431,17 @@ export class Limiter<T> {
private runningPromises: number;
private maxDegreeOfParalellism: number;
private outstandingPromises: ILimitedTaskFactory[];
private _onFinished: Emitter<void>;
constructor(maxDegreeOfParalellism: number) {
this.maxDegreeOfParalellism = maxDegreeOfParalellism;
this.outstandingPromises = [];
this.runningPromises = 0;
this._onFinished = new Emitter<void>();
}
public get onFinished(): Event<void> {
return this._onFinished.event;
}
queue(promiseFactory: ITask<Promise>): Promise;
......@@ -464,7 +471,16 @@ export class Limiter<T> {
private consumed(): void {
this.runningPromises--;
this.consume();
if (this.outstandingPromises.length > 0) {
this.consume();
} else {
this._onFinished.fire();
}
}
public dispose(): void {
this._onFinished.dispose();
}
}
......
......@@ -5,14 +5,13 @@
'use strict';
import uuid = require('vs/base/common/uuid');
import strings = require('vs/base/common/strings');
import platform = require('vs/base/common/platform');
import * as uuid from 'vs/base/common/uuid';
import * as strings from 'vs/base/common/strings';
import * as platform from 'vs/base/common/platform';
import * as flow from 'vs/base/node/flow';
import flow = require('vs/base/node/flow');
import fs = require('fs');
import paths = require('path');
import * as fs from 'fs';
import * as paths from 'path';
const loop = flow.loop;
......
......@@ -6,11 +6,13 @@
'use strict';
import { Promise, TPromise } from 'vs/base/common/winjs.base';
import extfs = require('vs/base/node/extfs');
import paths = require('vs/base/common/paths');
import * as extfs from 'vs/base/node/extfs';
import * as paths from 'vs/base/common/paths';
import { dirname, join } from 'path';
import { nfcall } from 'vs/base/common/async';
import fs = require('fs');
import { nfcall, Queue } from 'vs/base/common/async';
import * as fs from 'fs';
import * as platform from 'vs/base/common/platform';
import { once } from 'vs/base/common/event';
export function readdir(path: string): TPromise<string[]> {
return nfcall(extfs.readdir, path);
......@@ -113,16 +115,42 @@ export function readFile(path: string, encoding?: string): TPromise<Buffer | str
return nfcall(fs.readFile, path, encoding);
}
// According to node.js docs (https://nodejs.org/docs/v6.5.0/api/fs.html#fs_fs_writefile_file_data_options_callback)
// it is not safe to call writeFile() on the same path multiple times without waiting for the callback to return.
// Therefor we use a Queue on the path that is given to us to sequentialize calls to the same path properly.
const writeFilePathQueue: { [path: string]: Queue<void> } = Object.create(null);
export function writeFile(path: string, data: string, encoding?: string): TPromise<void>;
export function writeFile(path: string, data: NodeBuffer, encoding?: string): TPromise<void>;
export function writeFile(path: string, data: any, encoding: string = 'utf8'): TPromise<void> {
return nfcall(fs.writeFile, path, data, encoding);
let queueKey = toQueueKey(path);
return ensureWriteFileQueue(queueKey).queue(() => nfcall(extfs.writeFileAndFlush, path, data, encoding));
}
function toQueueKey(path: string): string {
let queueKey = path;
if (platform.isWindows || platform.isMacintosh) {
queueKey = queueKey.toLowerCase(); // accomodate for case insensitive file systems
}
return queueKey;
}
export function writeFileAndFlush(path: string, data: string, encoding?: string): TPromise<void>;
export function writeFileAndFlush(path: string, data: NodeBuffer, encoding?: string): TPromise<void>;
export function writeFileAndFlush(path: string, data: any, encoding: string = 'utf8'): TPromise<void> {
return nfcall(extfs.writeFileAndFlush, path, data, encoding);
function ensureWriteFileQueue(queueKey: string): Queue<void> {
let writeFileQueue = writeFilePathQueue[queueKey];
if (!writeFileQueue) {
writeFileQueue = new Queue<void>();
writeFilePathQueue[queueKey] = writeFileQueue;
const onFinish = once(writeFileQueue.onFinished);
onFinish(() => {
delete writeFilePathQueue[queueKey];
writeFileQueue.dispose();
});
}
return writeFileQueue;
}
/**
......
......@@ -561,7 +561,7 @@ export class QuickOpenWidget implements IModelProvider {
const entryToFocus = caseSensitiveMatch || caseInsensitiveMatch;
if (entryToFocus) {
this.tree.setFocus(entryToFocus);
this.tree.reveal(entryToFocus, 0).done(null, errors.onUnexpectedError);
this.tree.reveal(entryToFocus, 0.5).done(null, errors.onUnexpectedError);
return;
}
......@@ -570,7 +570,7 @@ export class QuickOpenWidget implements IModelProvider {
// Second check for auto focus of first entry
if (autoFocus.autoFocusFirstEntry) {
this.tree.focusFirst();
this.tree.reveal(this.tree.getFocus(), 0).done(null, errors.onUnexpectedError);
this.tree.reveal(this.tree.getFocus()).done(null, errors.onUnexpectedError);
}
// Third check for specific index option
......@@ -798,6 +798,10 @@ export class QuickOpenWidget implements IModelProvider {
return this.progressBar;
}
public getInputBox(): InputBox {
return this.inputBox;
}
public setExtraClass(clazz: string): void {
const previousClass = this.builder.getProperty('extra-class');
if (previousClass) {
......
......@@ -543,4 +543,30 @@ suite('Async', () => {
});
});
});
test('Queue - events', function (done) {
let queue = new Async.Queue();
let finished = false;
queue.onFinished(() => {
done();
});
let res = [];
let f1 = () => TPromise.timeout(10).then(() => res.push(2));
let f2 = () => TPromise.timeout(20).then(() => res.push(4));
let f3 = () => TPromise.timeout(0).then(() => res.push(5));
const q1 = queue.queue(f1);
const q2 = queue.queue(f2);
queue.queue(f3);
q1.then(() => {
assert.ok(!finished);
q2.then(() => {
assert.ok(!finished);
});
});
});
});
......@@ -144,7 +144,7 @@ suite('Extfs', () => {
assert.equal(fs.readFileSync(testFile), largeString);
done();
extfs.del(parentDir, os.tmpdir(), () => { }, done);
});
});
});
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import assert = require('assert');
import os = require('os');
import path = require('path');
import fs = require('fs');
import uuid = require('vs/base/common/uuid');
import extfs = require('vs/base/node/extfs');
import { onError } from 'vs/test/utils/servicesTestUtils';
import * as pfs from 'vs/base/node/pfs';
suite('PFS', () => {
test('writeFile', function (done: () => void) {
const id = uuid.generateUuid();
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
const newDir = path.join(parentDir, 'pfs', id);
const testFile = path.join(newDir, 'writefile.txt');
extfs.mkdirp(newDir, 493, (error) => {
if (error) {
return onError(error, done);
}
assert.ok(fs.existsSync(newDir));
pfs.writeFile(testFile, 'Hello World', null).done(() => {
assert.equal(fs.readFileSync(testFile), 'Hello World');
extfs.del(parentDir, os.tmpdir(), () => { }, done);
}, error => onError(error, done));
});
});
test('writeFile - parallel write on different files works', function (done: () => void) {
const id = uuid.generateUuid();
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
const newDir = path.join(parentDir, 'pfs', id);
const testFile1 = path.join(newDir, 'writefile1.txt');
const testFile2 = path.join(newDir, 'writefile2.txt');
const testFile3 = path.join(newDir, 'writefile3.txt');
const testFile4 = path.join(newDir, 'writefile4.txt');
const testFile5 = path.join(newDir, 'writefile5.txt');
extfs.mkdirp(newDir, 493, (error) => {
if (error) {
return onError(error, done);
}
assert.ok(fs.existsSync(newDir));
TPromise.join([
pfs.writeFile(testFile1, 'Hello World 1', null),
pfs.writeFile(testFile2, 'Hello World 2', null),
pfs.writeFile(testFile3, 'Hello World 3', null),
pfs.writeFile(testFile4, 'Hello World 4', null),
pfs.writeFile(testFile5, 'Hello World 5', null)
]).done(() => {
assert.equal(fs.readFileSync(testFile1), 'Hello World 1');
assert.equal(fs.readFileSync(testFile2), 'Hello World 2');
assert.equal(fs.readFileSync(testFile3), 'Hello World 3');
assert.equal(fs.readFileSync(testFile4), 'Hello World 4');
assert.equal(fs.readFileSync(testFile5), 'Hello World 5');
extfs.del(parentDir, os.tmpdir(), () => { }, done);
}, error => onError(error, done));
});
});
test('writeFile - parallel write on same files works and is sequentalized', function (done: () => void) {
const id = uuid.generateUuid();
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
const newDir = path.join(parentDir, 'pfs', id);
const testFile = path.join(newDir, 'writefile.txt');
extfs.mkdirp(newDir, 493, (error) => {
if (error) {
return onError(error, done);
}
assert.ok(fs.existsSync(newDir));
TPromise.join([
pfs.writeFile(testFile, 'Hello World 1', null),
pfs.writeFile(testFile, 'Hello World 2', null),
TPromise.timeout(10).then(() => pfs.writeFile(testFile, 'Hello World 3', null)),
pfs.writeFile(testFile, 'Hello World 4', null),
TPromise.timeout(10).then(() => pfs.writeFile(testFile, 'Hello World 5', null))
]).done(() => {
assert.equal(fs.readFileSync(testFile), 'Hello World 5');
extfs.del(parentDir, os.tmpdir(), () => { }, done);
}, error => onError(error, done));
});
});
});
\ No newline at end of file
......@@ -113,7 +113,7 @@ suite('BackupMainService', () => {
test('removeWorkspaceBackupPath should fail gracefully when removing a path that doesn\'t exist', done => {
const workspacesJson: IBackupWorkspacesFormat = { folderWorkspaces: [fooFile.fsPath] };
pfs.writeFileAndFlush(backupWorkspacesPath, JSON.stringify(workspacesJson)).then(() => {
pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)).then(() => {
service.removeWorkspaceBackupPathSync(barFile);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json = <IBackupWorkspacesFormat>JSON.parse(content);
......
......@@ -310,6 +310,14 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe
this.pickOpenWidget.layout(this.layoutDimensions);
}
// Detect cancellation while pick promise is loading
let cancelTriggered = false;
this.pickOpenWidget.setCallbacks({
onOk: () => { /* ignore, handle later */ },
onCancel: () => { cancelTriggered = true; },
onType: (value: string) => { /* ignore, handle later */ },
});
return new TPromise<IPickOpenEntry | string>((complete, error, progress) => {
// hide widget when being cancelled
......@@ -323,8 +331,8 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe
// Resolve picks
picksPromise.then(picks => {
if (this.currentPickerToken !== currentPickerToken) {
return; // Return if another request came after
if (this.currentPickerToken !== currentPickerToken || cancelTriggered) {
return complete(void 0); // Return as canceled if another request came after or user canceled
}
picksPromiseDone = true;
......@@ -342,7 +350,7 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe
model.setEntries(entries);
// Handlers
this.pickOpenWidget.setCallbacks({
const callbacks = {
onOk: () => {
if (picks.length === 0) {
return complete(null);
......@@ -410,7 +418,8 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe
},
onShow: () => this.handleOnShow(true),
onHide: (reason) => this.handleOnHide(true, reason)
});
};
this.pickOpenWidget.setCallbacks(callbacks);
// Set input
if (!this.pickOpenWidget.isVisible()) {
......@@ -418,6 +427,13 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe
} else {
this.pickOpenWidget.setInput(model, autoFocus);
}
// The user might have typed something (or options.value was set) so we need to play back
// the input box value through our callbacks to filter the result accordingly.
const inputValue = this.pickOpenWidget.getInputBox().value;
if (inputValue) {
callbacks.onType(inputValue);
}
}, (err) => {
this.pickOpenWidget.hide();
......
......@@ -7,7 +7,6 @@
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import timer = require('vs/base/common/timer');
import { Action } from 'vs/base/common/actions';
import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
......@@ -331,7 +330,6 @@ export class ShowStartupPerformance extends Action {
}
public run(): TPromise<boolean> {
const perfTable = this.environmentService.performance ? this.getPerformanceTable() : this.getFingerprintTable();
// Show dev tools
this.windowService.openDevTools();
......@@ -346,81 +344,55 @@ export class ShowStartupPerformance extends Action {
console.log(`Initial Startup: ${fingerprint.initialStartup}`);
console.log(`Screen Reader Active: ${fingerprint.hasAccessibilitySupport}`);
console.log(`Empty Workspace: ${fingerprint.emptyWorkbench}`);
(<any>console).table(perfTable);
let nodeModuleLoadTime: number;
let nodeModuleLoadDetails: any[];
if (this.environmentService.performance) {
const nodeModuleTimes = this.analyzeNodeModulesLoadTimes();
nodeModuleLoadTime = nodeModuleTimes.duration;
nodeModuleLoadDetails = nodeModuleTimes.table;
}
(<any>console).table(this.getFingerprintTable(nodeModuleLoadTime));
if (nodeModuleLoadDetails) {
(<any>console).groupCollapsed('node_modules Load Details');
(<any>console).table(nodeModuleLoadDetails);
(<any>console).groupEnd();
}
(<any>console).groupEnd();
}, 1000);
return TPromise.as(true);
}
private getFingerprintTable(): any[] {
private getFingerprintTable(nodeModuleLoadTime?: number): any[] {
const table: any[] = [];
const fingerprint: IStartupFingerprint = timers.fingerprint;
if (fingerprint.initialStartup) {
table.push({ Topic: '[main] initial start => begin to require(workbench.main.js)', 'Took (ms)': fingerprint.timers.ellapsedMain });
table.push({ Topic: '[main] start => window.loadUrl()', 'Took (ms)': fingerprint.timers.ellapsedWindowLoad });
}
table.push({ Topic: '[renderer] window.loadUrl() => begin to require(workbench.main.js)', 'Took (ms)': fingerprint.timers.ellapsedWindowLoadToRequire });
table.push({ Topic: '[renderer] require(workbench.main.js)', 'Took (ms)': fingerprint.timers.ellapsedRequire });
if (nodeModuleLoadTime) {
table.push({ Topic: '[renderer] -> of which require() node_modules', 'Took (ms)': nodeModuleLoadTime });
}
table.push({ Topic: '[renderer] create extension host => extensions onReady()', 'Took (ms)': fingerprint.timers.ellapsedExtensions });
table.push({ Topic: '[renderer] restore viewlet', 'Took (ms)': fingerprint.timers.ellapsedViewletRestore });
table.push({ Topic: '[renderer] restoring editor view state', 'Took (ms)': fingerprint.timers.ellapsedEditorRestore });
table.push({ Topic: '[renderer] restore editor view state', 'Took (ms)': fingerprint.timers.ellapsedEditorRestore });
table.push({ Topic: '[renderer] overall workbench load', 'Took (ms)': fingerprint.timers.ellapsedWorkbench });
table.push({ Topic: '------------------------------------------------------' });
if (fingerprint.initialStartup) {
table.push({ Topic: '[main] load window at', 'Start (ms)': fingerprint.timers.windowLoad });
}
table.push({ Topic: '[main, renderer] start => extensions ready', 'Took (ms)': fingerprint.timers.extensionsReady });
table.push({ Topic: '[main, renderer] start => extensions ready', 'Took (ms)': fingerprint.timers.ellapsedExtensionsReady });
table.push({ Topic: '[main, renderer] start => workbench ready', 'Took (ms)': fingerprint.ellapsed });
return table;
}
private getPerformanceTable(): any[] {
const table: any[] = [];
table.push(...this.analyzeLoaderTimes());
const start = Math.round(timers.isInitialStartup ? timers.perfStartTime : timers.perfWindowLoadTime);
let lastEvent: timer.ITimerEvent;
const events = timer.getTimeKeeper().getCollectedEvents();
events.forEach((e) => {
if (e.topic === 'Startup') {
lastEvent = e;
const entry: any = {};
entry['Event'] = e.name;
entry['Took (ms)'] = e.stopTime.getTime() - e.startTime.getTime();
entry['Start (ms)'] = Math.max(e.startTime.getTime() - start, 0);
entry['End (ms)'] = e.stopTime.getTime() - start;
table.push(entry);
}
});
table.push({ Event: '------------------------------------------------------' });
if (timers.isInitialStartup) {
const loadWindow = Math.round(timers.perfWindowLoadTime);
const windowLoadEvent: any = {};
windowLoadEvent['Event'] = '[main] load window at';
windowLoadEvent['Start (ms)'] = loadWindow - start;
table.push(windowLoadEvent);
}
const totalExtensions: any = {};
totalExtensions['Event'] = '[main, renderer] start => extensions ready';
totalExtensions['Took (ms)'] = timers.perfAfterExtensionLoad - start;
table.push(totalExtensions);
const totalWorkbench: any = {};
totalWorkbench['Event'] = '[main, renderer] start => workbench ready';
totalWorkbench['Took (ms)'] = timers.workbenchStarted - start;
table.push(totalWorkbench);
return table;
}
private analyzeLoaderTimes(): any[] {
private analyzeNodeModulesLoadTimes(): { table: any[], duration: number } {
const stats = <ILoaderEvent[]>(<any>require).getStats();
const result = [];
......@@ -430,28 +402,29 @@ export class ShowStartupPerformance extends Action {
if (stats[i].type === LoaderEventType.NodeEndNativeRequire) {
if (stats[i - 1].type === LoaderEventType.NodeBeginNativeRequire && stats[i - 1].detail === stats[i].detail) {
const entry: any = {};
const dur = (stats[i].timestamp - stats[i - 1].timestamp);
entry['Event'] = 'nodeRequire ' + stats[i].detail;
entry['Took (ms)'] = (stats[i].timestamp - stats[i - 1].timestamp);
total += (stats[i].timestamp - stats[i - 1].timestamp);
entry['Start (ms)'] = '**' + stats[i - 1].timestamp;
entry['End (ms)'] = '**' + stats[i - 1].timestamp;
entry['Took (ms)'] = dur.toFixed(2);
total += dur;
entry['Start (ms)'] = '**' + stats[i - 1].timestamp.toFixed(2);
entry['End (ms)'] = '**' + stats[i - 1].timestamp.toFixed(2);
result.push(entry);
}
}
}
if (total > 0) {
result.push({ Event: '------------------------------------------------------' });
const entry: any = {};
entry['Event'] = '[renderer] total require() node modules';
entry['Took (ms)'] = total;
entry['Event'] = '[renderer] total require() node_modules';
entry['Took (ms)'] = total.toFixed(2);
entry['Start (ms)'] = '**';
entry['End (ms)'] = '**';
result.push(entry);
result.push({ Event: '------------------------------------------------------' });
}
return result;
return { table: result, duration: Math.round(total) };
}
}
......
......@@ -24,10 +24,10 @@ export interface IWindowConfiguration {
export interface IStartupFingerprint {
ellapsed: number;
timers: {
ellapsedMain?: number;
windowLoad?: number;
ellapsedWindowLoad?: number;
ellapsedWindowLoadToRequire: number;
ellapsedExtensions: number;
extensionsReady: number;
ellapsedExtensionsReady: number;
ellapsedRequire: number;
ellapsedViewletRestore: number;
ellapsedEditorRestore: number;
......
......@@ -238,11 +238,12 @@ export class WorkbenchShell {
ellapsed: Math.round(workbenchStarted - start),
timers: {
ellapsedExtensions: Math.round(timers.perfAfterExtensionLoad - timers.perfBeforeExtensionLoad),
extensionsReady: Math.round(timers.perfAfterExtensionLoad - start),
ellapsedExtensionsReady: Math.round(timers.perfAfterExtensionLoad - start),
ellapsedRequire: Math.round(timers.perfAfterLoadWorkbenchMain - timers.perfBeforeLoadWorkbenchMain),
ellapsedViewletRestore: Math.round(restoreViewletDuration),
ellapsedEditorRestore: Math.round(restoreEditorsDuration),
ellapsedWorkbench: Math.round(workbenchStarted - timers.perfBeforeWorkbenchOpen)
ellapsedWorkbench: Math.round(workbenchStarted - timers.perfBeforeWorkbenchOpen),
ellapsedWindowLoadToRequire: Math.round(timers.perfBeforeLoadWorkbenchMain - timers.perfWindowLoadTime)
},
platform,
release,
......@@ -255,8 +256,7 @@ export class WorkbenchShell {
};
if (initialStartup) {
startupTimeEvent.timers.ellapsedMain = Math.round(timers.perfBeforeLoadWorkbenchMain - timers.perfStartTime);
startupTimeEvent.timers.windowLoad = Math.round(timers.perfWindowLoadTime - timers.perfStartTime);
startupTimeEvent.timers.ellapsedWindowLoad = Math.round(timers.perfWindowLoadTime - timers.perfStartTime);
}
this.telemetryService.publicLog('startupTime', startupTimeEvent);
......
......@@ -123,7 +123,7 @@ class FilesViewerActionContributor extends ActionBarContributor {
if (context && context.element instanceof FileStat) {
// Any other item with keybinding
const keybinding = keybindingForAction(action.id);
const keybinding = keybindingForAction(action.id, this.keybindingService);
if (keybinding) {
return new ActionItem(context, action, { label: true, keybinding: this.keybindingService.getLabelFor(keybinding) });
}
......
......@@ -1859,7 +1859,7 @@ export class RefreshExplorerView extends Action {
}
}
export function keybindingForAction(id: string, keybindingService?: IKeybindingService): Keybinding {
export function keybindingForAction(id: string, keybindingService: IKeybindingService): Keybinding {
switch (id) {
case GlobalNewUntitledFileAction.ID:
return new Keybinding(KeyMod.CtrlCmd | KeyCode.KEY_N);
......
......@@ -34,6 +34,7 @@ import { ClickBehavior, DefaultController } from 'vs/base/parts/tree/browser/tre
import { ActionsRenderer } from 'vs/base/parts/tree/browser/actionsRenderer';
import { FileStat, NewStatPlaceholder } from 'vs/workbench/parts/files/common/explorerViewModel';
import { DragMouseEvent, IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { IWorkspace, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
......@@ -389,7 +390,8 @@ export class FileController extends DefaultController {
@ITelemetryService private telemetryService: ITelemetryService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IMenuService menuService: IMenuService,
@IContextKeyService contextKeyService: IContextKeyService
@IContextKeyService contextKeyService: IContextKeyService,
@IKeybindingService private keybindingService: IKeybindingService
) {
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */ });
......@@ -510,7 +512,7 @@ export class FileController extends DefaultController {
});
},
getActionItem: this.state.actionProvider.getActionItem.bind(this.state.actionProvider, tree, stat),
getKeyBinding: (a): Keybinding => keybindingForAction(a.id),
getKeyBinding: (a): Keybinding => keybindingForAction(a.id, this.keybindingService),
getActionsContext: (event) => {
return {
viewletState: this.state,
......
......@@ -42,7 +42,7 @@ export class CloneAction extends Action {
const result = dialog.showOpenDialog(remote.getCurrentWindow(), {
title: localize('directory', "Destination clone directory"),
properties: ['openDirectory']
properties: ['openDirectory', 'createDirectory']
});
if (!result || result.length === 0) {
......
......@@ -13,6 +13,7 @@ import platform = require('vs/platform/platform');
import workbenchActionRegistry = require('vs/workbench/common/actionRegistry');
import workbenchContributions = require('vs/workbench/common/contributions');
import snippetsTracker = require('./snippetsTracker');
import * as pfs from 'vs/base/node/pfs';
import errors = require('vs/base/common/errors');
import { IQuickOpenService, IPickOpenEntry } from 'vs/workbench/services/quickopen/common/quickOpenService';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
......@@ -81,7 +82,7 @@ class OpenSnippetsAction extends actions.Action {
'*/',
'}'
].join('\n');
return createFile(snippetPath, defaultContent).then(() => {
return pfs.writeFile(snippetPath, defaultContent).then(() => {
return this.openFile(snippetPath);
}, (err) => {
errors.onUnexpectedError(nls.localize('openSnippet.errorOnCreate', 'Unable to create {0}', snippetPath));
......@@ -109,17 +110,6 @@ function fileExists(path: string): winjs.TPromise<boolean> {
});
}
function createFile(path: string, content: string): winjs.Promise {
return new winjs.Promise((c, e, p) => {
fs.writeFile(path, content, function (err) {
if (err) {
e(err);
}
c(true);
});
});
}
var preferencesCategory = nls.localize('preferences', "Preferences");
var workbenchActionsRegistry = <workbenchActionRegistry.IWorkbenchActionRegistry>platform.Registry.as(workbenchActionRegistry.Extensions.WorkbenchActions);
......
......@@ -50,7 +50,7 @@ suite('BackupFileService', () => {
// Delete any existing backups completely and then re-create it.
extfs.del(backupHome, os.tmpdir(), () => {
pfs.mkdirp(backupHome).then(() => {
pfs.writeFileAndFlush(workspacesJsonPath, '').then(() => {
pfs.writeFile(workspacesJsonPath, '').then(() => {
done();
});
});
......
......@@ -275,13 +275,13 @@ export class FileService implements IFileService {
// Write fast if we do UTF 8 without BOM
if (!addBom && encodingToWrite === encoding.UTF8) {
writeFilePromise = pfs.writeFileAndFlush(absolutePath, value, encoding.UTF8);
writeFilePromise = pfs.writeFile(absolutePath, value, encoding.UTF8);
}
// Otherwise use encoding lib
else {
const encoded = encoding.encode(value, encodingToWrite, { addBOM: addBom });
writeFilePromise = pfs.writeFileAndFlush(absolutePath, encoded);
writeFilePromise = pfs.writeFile(absolutePath, encoded);
}
// 4.) set contents
......
......@@ -255,6 +255,7 @@ export abstract class BaseHistoryService {
interface IStackEntry {
input: IEditorInput | IResourceInput;
options?: ITextEditorOptions;
timestamp: number;
}
interface IRecentlyClosedFile {
......@@ -270,6 +271,7 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
private static MAX_HISTORY_ITEMS = 200;
private static MAX_STACK_ITEMS = 20;
private static MAX_RECENTLY_CLOSED_EDITORS = 20;
private static MERGE_CURSOR_CHANGES_THRESHOLD = 100;
private stack: IStackEntry[];
private index: number;
......@@ -406,12 +408,12 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
}
protected handleEditorSelectionChangeEvent(editor?: IBaseEditor): void {
this.handleEditorEventInStack(editor, true);
this.handleEditorEventInStack(editor);
}
protected handleActiveEditorChange(editor?: IBaseEditor): void {
this.handleEditorEventInHistory(editor);
this.handleEditorEventInStack(editor, false);
this.handleEditorEventInStack(editor);
}
private handleEditorEventInHistory(editor?: IBaseEditor): void {
......@@ -460,13 +462,13 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
this.history = this.history.filter(e => !this.matches(arg1, e));
}
private handleEditorEventInStack(editor: IBaseEditor, storeSelection: boolean): void {
private handleEditorEventInStack(editor: IBaseEditor): void {
if (this.blockStackChanges) {
return; // while we open an editor due to a navigation, we do not want to update our stack
}
if (editor instanceof BaseTextEditor && editor.input) {
this.handleTextEditorEvent(<BaseTextEditor>editor, storeSelection);
this.handleTextEditorEvent(<BaseTextEditor>editor);
return;
}
......@@ -478,14 +480,15 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
}
}
private handleTextEditorEvent(editor: BaseTextEditor, storeSelection: boolean): void {
private handleTextEditorEvent(editor: BaseTextEditor): void {
const stateCandidate = new EditorState(editor.input, editor.getSelection());
if (!this.currentFileEditorState || this.currentFileEditorState.justifiesNewPushState(stateCandidate)) {
this.currentFileEditorState = stateCandidate;
let options: ITextEditorOptions;
if (storeSelection) {
const selection = editor.getSelection();
const selection = editor.getSelection();
if (selection) {
options = {
selection: { startLineNumber: selection.startLineNumber, startColumn: selection.startColumn }
};
......@@ -514,17 +517,21 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
// Overwrite an entry in the stack if we have a matching input that comes
// with editor options to indicate that this entry is more specific. Also
// prevent entries that have the exact same options.
// prevent entries that have the exact same options. Finally, Overwrite
// entries if we detect that the change came in very fast which indicates
// that it was not coming in from a user change but rather rapid programmatic
// changes. We just take the last of the changes to not cause too many
// entries on the stack.
let replace = false;
if (this.stack[this.index]) {
const currentEntry = this.stack[this.index];
if (this.matches(input, currentEntry.input) && this.sameOptions(currentEntry.options, options)) {
if (this.matches(input, currentEntry.input) && (this.sameOptions(currentEntry.options, options) || Date.now() - currentEntry.timestamp < HistoryService.MERGE_CURSOR_CHANGES_THRESHOLD)) {
replace = true;
}
}
const stackInput = this.preferResourceInput(input);
const entry = { input: stackInput, options };
const entry = { input: stackInput, options, timestamp: Date.now() };
// If we are not at the end of history, we remove anything after
if (this.stack.length > this.index + 1) {
......
......@@ -108,9 +108,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
this.toDispose.push(this.textFileService.onFilesAssociationChange(e => this.onFilesAssociationChange()));
this.toDispose.push(this.onDidStateChange(e => {
if (e === StateChange.REVERTED) {
// Refire reverted events as content change events, cancelling any content change
// promises that are in flight.
// Cancel any content change event promises as they are no longer valid.
this.contentChangeEventScheduler.cancel();
// Refire state change reverted events as content change events
this._onDidContentChange.fire(StateChange.REVERTED);
}
}));
......@@ -549,6 +550,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// Updated resolved stat with updated stat, and keep old for event
this.updateVersionOnDiskStat(stat);
// Cancel any content change event promises as they are no longer valid
this.contentChangeEventScheduler.cancel();
// Emit File Saved Event
this._onDidStateChange.fire(StateChange.SAVED);
}, (error) => {
......@@ -770,6 +774,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
this.createTextEditorModelPromise = null;
this.cancelAutoSavePromises();
this.contentChangeEventScheduler.cancel();
super.dispose();
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册