diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 33cae599a0a..93145a6cb12 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -40,7 +40,7 @@ const nodeModules = ['electron', 'original-fs'] const builtInExtensions = [ { name: 'ms-vscode.node-debug', version: '1.7.8' }, - { name: 'ms-vscode.node-debug2', version: '1.7.2' } + { name: 'ms-vscode.node-debug2', version: '1.8.0' } ]; const vscodeEntryPoints = _.flatten([ diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index ffde9dd6d2d..c81c6c74a41 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -42,6 +42,7 @@ export interface IJSONSchema { defaultSnippets?: { label?: string; description?: string; body: any; }[]; // VSCode extension errorMessage?: string; // VSCode extension + deprecatedMessage?: string; // VSCode extension } export interface IJSONSchemaMap { diff --git a/src/vs/base/common/watchDog.ts b/src/vs/base/common/watchDog.ts new file mode 100644 index 00000000000..e7dd61b054e --- /dev/null +++ b/src/vs/base/common/watchDog.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * 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 Event, { Emitter } from 'vs/base/common/event'; + +export class WatchDog { + + private _timeout: number; + private _threshold: number; + private _onAlert = new Emitter(); + + private _handle: number; + private _missed: number; + private _lastSignal: number; + + constructor(timeout: number, threshold: number) { + this._timeout = timeout; + this._threshold = threshold; + } + + dispose(): void { + this.stop(); + } + + get onAlert(): Event { + return this._onAlert.event; + } + + start(): void { + this.reset(); + this._handle = setInterval(this._check.bind(this), this._timeout * 1.5); + } + + stop(): void { + clearInterval(this._handle); + } + + reset(): void { + this._lastSignal = Date.now(); + this._missed = 0; + } + + private _check(): void { + if ((Date.now() - this._lastSignal) > this._timeout) { + this._missed += 1; + if (this._missed > this._threshold) { + this._onAlert.fire(this); + this._missed = 0; + } + } + } +} diff --git a/src/vs/base/parts/ipc/common/ipc.electron.ts b/src/vs/base/parts/ipc/common/ipc.electron.ts index d5b03d61449..f9f773a330c 100644 --- a/src/vs/base/parts/ipc/common/ipc.electron.ts +++ b/src/vs/base/parts/ipc/common/ipc.electron.ts @@ -25,7 +25,11 @@ export class Protocol implements IMessagePassingProtocol { } send(message: any): void { - this.sender.send('ipc:message', message); + try { + this.sender.send('ipc:message', message); + } catch (e) { + // systems are going down + } } dispose(): void { diff --git a/src/vs/base/test/common/watchDog.test.ts b/src/vs/base/test/common/watchDog.test.ts new file mode 100644 index 00000000000..9a25439c013 --- /dev/null +++ b/src/vs/base/test/common/watchDog.test.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as assert from 'assert'; +import { WatchDog } from 'vs/base/common/watchDog'; + +suite('WatchDog', function () { + + test('start/stop', function (done) { + const dog = new WatchDog(10, 1); + dog.onAlert(e => { + dog.stop(); + assert.ok(e === dog); + done(); + }); + dog.start(); + }); +}); diff --git a/src/vs/code/electron-main/launch.ts b/src/vs/code/electron-main/launch.ts index ac17f0d6e66..2c2b01eff49 100644 --- a/src/vs/code/electron-main/launch.ts +++ b/src/vs/code/electron-main/launch.ts @@ -5,7 +5,7 @@ 'use strict'; -import { IWindowsService } from 'vs/code/electron-main/windows'; +import { IWindowsMainService } from 'vs/code/electron-main/windows'; import { VSCodeWindow } from 'vs/code/electron-main/window'; import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; @@ -63,7 +63,7 @@ export class LaunchService implements ILaunchService { constructor( @ILogService private logService: ILogService, - @IWindowsService private windowsService: IWindowsService, + @IWindowsMainService private windowsService: IWindowsMainService, @IURLService private urlService: IURLService ) { } diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 9477e7045f8..46946be1f33 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -12,7 +12,10 @@ import * as platform from 'vs/base/common/platform'; import { parseMainProcessArgv, ParsedArgs } from 'vs/platform/environment/node/argv'; import { mkdirp } from 'vs/base/node/pfs'; import { validatePaths } from 'vs/code/electron-main/paths'; -import { IWindowsService, WindowsManager } from 'vs/code/electron-main/windows'; +import { IWindowsMainService, WindowsManager } from 'vs/code/electron-main/windows'; +import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { WindowsChannel } from 'vs/platform/windows/common/windowsIpc'; +import { WindowsService } from 'vs/platform/windows/electron-main/windowsService'; import { WindowEventChannel } from 'vs/code/common/windowsIpc'; import { ILifecycleService, LifecycleService } from 'vs/code/electron-main/lifecycle'; import { VSCodeMenu } from 'vs/code/electron-main/menus'; @@ -73,11 +76,11 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo const instantiationService = accessor.get(IInstantiationService); const logService = accessor.get(ILogService); const environmentService = accessor.get(IEnvironmentService); - const windowsService = accessor.get(IWindowsService); + const windowsMainService = accessor.get(IWindowsMainService); const lifecycleService = accessor.get(ILifecycleService); const updateService = accessor.get(IUpdateService); const configurationService = accessor.get(IConfigurationService) as ConfigurationService; - const windowEventChannel = new WindowEventChannel(windowsService); + const windowEventChannel = new WindowEventChannel(windowsMainService); // We handle uncaught exceptions here to prevent electron from opening a dialog to the user process.on('uncaughtException', (err: any) => { @@ -90,7 +93,7 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo }; // handle on client side - windowsService.sendToFocused('vscode:reportError', JSON.stringify(friendlyError)); + windowsMainService.sendToFocused('vscode:reportError', JSON.stringify(friendlyError)); } console.error('[uncaught exception in main]: ' + err); @@ -131,6 +134,10 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo const urlChannel = instantiationService.createInstance(URLChannel, urlService); electronIpcServer.registerChannel('url', urlChannel); + const windowsService = accessor.get(IWindowsService); + const windowsChannel = new WindowsChannel(windowsService); + electronIpcServer.registerChannel('windows', windowsChannel); + // Spawn shared process const initData = { args: environmentService.args }; const options = { @@ -191,7 +198,7 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo lifecycleService.ready(); // Propagate to clients - windowsService.ready(userEnv); + windowsMainService.ready(userEnv); // Install Menu const menu = instantiationService.createInstance(VSCodeMenu); @@ -218,12 +225,12 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo }); // Recent Folders - const folders = windowsService.getRecentPathsList().folders; + const folders = windowsMainService.getRecentPathsList().folders; if (folders.length > 0) { jumpList.push({ type: 'custom', name: 'Recent Folders', - items: windowsService.getRecentPathsList().folders.slice(0, 7 /* limit number of entries here */).map(folder => { + items: windowsMainService.getRecentPathsList().folders.slice(0, 7 /* limit number of entries here */).map(folder => { return { type: 'task', title: getPathLabel(folder), @@ -254,11 +261,11 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo // Open our first window if (environmentService.args['new-window'] && environmentService.args._.length === 0) { - windowsService.open({ cli: environmentService.args, forceNewWindow: true, forceEmpty: true, restoreBackups: true }); // new window if "-n" was used without paths + windowsMainService.open({ cli: environmentService.args, forceNewWindow: true, forceEmpty: true, restoreBackups: true }); // new window if "-n" was used without paths } else if (global.macOpenFiles && global.macOpenFiles.length && (!environmentService.args._ || !environmentService.args._.length)) { - windowsService.open({ cli: environmentService.args, pathsToOpen: global.macOpenFiles, restoreBackups: true }); // mac: open-file event received on startup + windowsMainService.open({ cli: environmentService.args, pathsToOpen: global.macOpenFiles, restoreBackups: true }); // mac: open-file event received on startup } else { - windowsService.open({ cli: environmentService.args, forceNewWindow: environmentService.args['new-window'], diffMode: environmentService.args.diff, restoreBackups: true }); // default: read paths from cli + windowsMainService.open({ cli: environmentService.args, forceNewWindow: environmentService.args['new-window'], diffMode: environmentService.args.diff, restoreBackups: true }); // default: read paths from cli } } @@ -424,7 +431,7 @@ function getShellEnvironment(): TPromise { } function createPaths(environmentService: IEnvironmentService): TPromise { - const paths = [environmentService.appSettingsHome, environmentService.userHome, environmentService.extensionsPath]; + const paths = [environmentService.appSettingsHome, environmentService.userProductHome, environmentService.extensionsPath]; return TPromise.join(paths.map(p => mkdirp(p))) as TPromise; } @@ -446,7 +453,8 @@ function start(): void { services.set(IEnvironmentService, new SyncDescriptor(EnvironmentService, args, process.execPath)); services.set(ILogService, new SyncDescriptor(MainLogService)); - services.set(IWindowsService, new SyncDescriptor(WindowsManager)); + services.set(IWindowsMainService, new SyncDescriptor(WindowsManager)); + services.set(IWindowsService, new SyncDescriptor(WindowsService)); services.set(ILifecycleService, new SyncDescriptor(LifecycleService)); services.set(IStorageService, new SyncDescriptor(StorageService)); services.set(IConfigurationService, new SyncDescriptor(ConfigurationService)); diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index f130651e774..7d97bed307a 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -10,7 +10,7 @@ import * as platform from 'vs/base/common/platform'; import * as arrays from 'vs/base/common/arrays'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ipcMain as ipc, app, shell, dialog, Menu, MenuItem } from 'electron'; -import { IWindowsService } from 'vs/code/electron-main/windows'; +import { IWindowsMainService } from 'vs/code/electron-main/windows'; import { IPath, VSCodeWindow } from 'vs/code/electron-main/window'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService } from 'vs/code/electron-main/storage'; @@ -57,7 +57,7 @@ export class VSCodeMenu { @IStorageService private storageService: IStorageService, @IUpdateService private updateService: IUpdateService, @IConfigurationService private configurationService: IConfigurationService, - @IWindowsService private windowsService: IWindowsService, + @IWindowsMainService private windowsService: IWindowsMainService, @IEnvironmentService private environmentService: IEnvironmentService ) { this.actionIdKeybindingRequests = []; diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index a5a3d4837b5..75d533fd20c 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -89,9 +89,9 @@ const ReopenFoldersSetting = { NONE: 'none' }; -export const IWindowsService = createDecorator('windowsService'); +export const IWindowsMainService = createDecorator('windowsMainService'); -export interface IWindowsService { +export interface IWindowsMainService { _serviceBrand: any; // TODO make proper events @@ -108,7 +108,7 @@ export interface IWindowsService { open(openConfig: IOpenConfiguration): VSCodeWindow[]; openPluginDevelopmentHostWindow(openConfig: IOpenConfiguration): void; openFileFolderPicker(forceNewWindow?: boolean): void; - openFilePicker(forceNewWindow?: boolean): void; + openFilePicker(forceNewWindow?: boolean, path?: string): void; openFolderPicker(forceNewWindow?: boolean): void; openAccessibilityOptions(): void; focusLastActive(cli: ParsedArgs): VSCodeWindow; @@ -126,7 +126,7 @@ export interface IWindowsService { clearRecentPathsList(): void; } -export class WindowsManager implements IWindowsService, IWindowEventService { +export class WindowsManager implements IWindowsMainService, IWindowEventService { _serviceBrand: any; @@ -243,24 +243,6 @@ export class WindowsManager implements IWindowsService, IWindowEventService { } }); - ipc.on('vscode:openFilePicker', (event, forceNewWindow?: boolean, path?: string) => { - this.logService.log('IPC#vscode-openFilePicker'); - - this.openFilePicker(forceNewWindow, path); - }); - - ipc.on('vscode:openFolderPicker', (event, forceNewWindow?: boolean) => { - this.logService.log('IPC#vscode-openFolderPicker'); - - this.openFolderPicker(forceNewWindow); - }); - - ipc.on('vscode:openFileFolderPicker', (event, forceNewWindow?: boolean) => { - this.logService.log('IPC#vscode-openFileFolderPicker'); - - this.openFileFolderPicker(forceNewWindow); - }); - ipc.on('vscode:closeFolder', (event, windowId: number) => { this.logService.log('IPC#vscode-closeFolder'); @@ -276,15 +258,6 @@ export class WindowsManager implements IWindowsService, IWindowEventService { this.openNewWindow(); }); - ipc.on('vscode:reloadWindow', (event, windowId: number) => { - this.logService.log('IPC#vscode:reloadWindow'); - - const vscodeWindow = this.getWindowById(windowId); - if (vscodeWindow) { - this.reload(vscodeWindow); - } - }); - ipc.on('vscode:toggleFullScreen', (event, windowId: number) => { this.logService.log('IPC#vscode:toggleFullScreen'); diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index dc4b47b0976..46b12590af1 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -160,7 +160,7 @@ export function main(argv: ParsedArgs): TPromise { return instantiationService.invokeFunction(accessor => { const envService = accessor.get(IEnvironmentService); - return TPromise.join([envService.appSettingsHome, envService.userHome, envService.extensionsPath].map(p => mkdirp(p))).then(() => { + return TPromise.join([envService.appSettingsHome, envService.userProductHome, envService.extensionsPath].map(p => mkdirp(p))).then(() => { const { appRoot, extensionsPath, extensionDevelopmentPath, isBuilt } = envService; const services = new ServiceCollection(); diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index c480ae8bf5e..569a9a272d3 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -1052,7 +1052,7 @@ export class Cursor extends EventEmitter { if (sorted) { cursors = cursors.sort((a, b) => { - return Range.compareRangesUsingStarts(a.getSelection(), b.getSelection()); + return Range.compareRangesUsingStarts(a.modelState.selection, b.modelState.selection); }); } @@ -1112,7 +1112,7 @@ export class Cursor extends EventEmitter { private _getColumnSelectToLineNumber(): number { if (!this._columnSelectToLineNumber) { let primaryCursor = this.cursors.getAll()[0]; - let primaryPos = primaryCursor.getViewPosition(); + let primaryPos = primaryCursor.viewState.position; return primaryPos.lineNumber; } return this._columnSelectToLineNumber; @@ -1122,7 +1122,7 @@ export class Cursor extends EventEmitter { private _getColumnSelectToVisualColumn(): number { if (!this._columnSelectToVisualColumn) { let primaryCursor = this.cursors.getAll()[0]; - let primaryPos = primaryCursor.getViewPosition(); + let primaryPos = primaryCursor.viewState.position; return primaryCursor.getViewVisibleColumnFromColumn(primaryPos.lineNumber, primaryPos.column); } return this._columnSelectToVisualColumn; diff --git a/src/vs/editor/common/controller/cursorCollection.ts b/src/vs/editor/common/controller/cursorCollection.ts index a1345899f9d..71870abb0e6 100644 --- a/src/vs/editor/common/controller/cursorCollection.ts +++ b/src/vs/editor/common/controller/cursorCollection.ts @@ -82,78 +82,71 @@ export class CursorCollection { public getPosition(index: number): Position { if (index === 0) { - return this.primaryCursor.getPosition(); + return this.primaryCursor.modelState.position; } else { - return this.secondaryCursors[index - 1].getPosition(); + return this.secondaryCursors[index - 1].modelState.position; } } public getViewPosition(index: number): Position { if (index === 0) { - return this.primaryCursor.getViewPosition(); + return this.primaryCursor.viewState.position; } else { - return this.secondaryCursors[index - 1].getViewPosition(); + return this.secondaryCursors[index - 1].viewState.position; } } public getPositions(): Position[] { var result: Position[] = []; - result.push(this.primaryCursor.getPosition()); + result.push(this.primaryCursor.modelState.position); for (var i = 0, len = this.secondaryCursors.length; i < len; i++) { - result.push(this.secondaryCursors[i].getPosition()); + result.push(this.secondaryCursors[i].modelState.position); } return result; } public getViewPositions(): Position[] { var result: Position[] = []; - result.push(this.primaryCursor.getViewPosition()); + result.push(this.primaryCursor.viewState.position); for (var i = 0, len = this.secondaryCursors.length; i < len; i++) { - result.push(this.secondaryCursors[i].getViewPosition()); + result.push(this.secondaryCursors[i].viewState.position); } return result; } public getSelection(index: number): Selection { if (index === 0) { - return this.primaryCursor.getSelection(); + return this.primaryCursor.modelState.selection; } else { - return this.secondaryCursors[index - 1].getSelection(); + return this.secondaryCursors[index - 1].modelState.selection; } } public getSelections(): Selection[] { var result: Selection[] = []; - result.push(this.primaryCursor.getSelection()); + result.push(this.primaryCursor.modelState.selection); for (var i = 0, len = this.secondaryCursors.length; i < len; i++) { - result.push(this.secondaryCursors[i].getSelection()); + result.push(this.secondaryCursors[i].modelState.selection); } return result; } public getViewSelections(): Selection[] { var result: Selection[] = []; - result.push(this.primaryCursor.getViewSelection()); + result.push(this.primaryCursor.viewState.selection); for (var i = 0, len = this.secondaryCursors.length; i < len; i++) { - result.push(this.secondaryCursors[i].getViewSelection()); + result.push(this.secondaryCursors[i].viewState.selection); } return result; } public setSelections(selections: ISelection[], viewSelections?: ISelection[]): void { - this.primaryCursor.setSelection(selections[0]); - this._setSecondarySelections(selections.slice(1)); - - if (viewSelections) { - this.primaryCursor.setViewSelection(viewSelections[0]); - for (let i = 0; i < this.secondaryCursors.length; i++) { - this.secondaryCursors[i].setViewSelection(viewSelections[i + 1]); - } - } + this.primaryCursor.setSelection(selections[0], viewSelections ? viewSelections[0] : null); + this._setSecondarySelections(selections.slice(1), viewSelections ? viewSelections.slice(1) : null); } public killSecondaryCursors(): boolean { - return (this._setSecondarySelections([]) > 0); + return (this._setSecondarySelections([], []) > 0); } public normalize(): void { @@ -200,7 +193,7 @@ export class CursorCollection { * - a negative number indicates the number of secondary cursors removed * - 0 indicates that no changes have been done to the secondary cursors list */ - private _setSecondarySelections(secondarySelections: ISelection[]): number { + private _setSecondarySelections(secondarySelections: ISelection[], viewSelections: ISelection[]): number { var secondaryCursorsLength = this.secondaryCursors.length; var secondarySelectionsLength = secondarySelections.length; var returnValue = secondarySelectionsLength - secondaryCursorsLength; @@ -219,7 +212,7 @@ export class CursorCollection { for (var i = 0; i < secondarySelectionsLength; i++) { if (secondarySelections[i]) { - this.secondaryCursors[i].setSelection(secondarySelections[i]); + this.secondaryCursors[i].setSelection(secondarySelections[i], viewSelections ? viewSelections[i] : null); } } @@ -247,8 +240,8 @@ export class CursorCollection { for (var i = 0; i < cursors.length; i++) { sortedCursors.push({ index: i, - selection: cursors[i].getSelection(), - viewSelection: cursors[i].getViewSelection() + selection: cursors[i].modelState.selection, + viewSelection: cursors[i].viewState.selection }); } diff --git a/src/vs/editor/common/controller/cursorMoveHelper.ts b/src/vs/editor/common/controller/cursorMoveHelper.ts index 779a345b759..d0b99bc11f9 100644 --- a/src/vs/editor/common/controller/cursorMoveHelper.ts +++ b/src/vs/editor/common/controller/cursorMoveHelper.ts @@ -4,16 +4,238 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { IPosition } from 'vs/editor/common/editorCommon'; import { Selection } from 'vs/editor/common/core/selection'; +import { CharCode } from 'vs/base/common/charCode'; import * as strings from 'vs/base/common/strings'; -export interface IMoveResult { - lineNumber: number; - column: number; - leftoverVisibleColumns: number; +export class CursorMoveConfiguration { + _cursorMoveConfigurationBrand: void; + + public readonly tabSize: number; + public readonly pageSize: number; + + constructor( + tabSize: number, + pageSize: number + ) { + this.tabSize = tabSize; + this.pageSize = pageSize; + } } +export interface ICursorMoveHelperModel { + getLineCount(): number; + getLineContent(lineNumber: number): string; + getLineMinColumn(lineNumber: number): number; + getLineMaxColumn(lineNumber: number): number; + getLineFirstNonWhitespaceColumn(lineNumber: number): number; + getLineLastNonWhitespaceColumn(lineNumber: number): number; +} + +export class CursorMoveResult { + _cursorMoveResultBrand: void; + + public readonly lineNumber: number; + public readonly column: number; + public readonly leftoverVisibleColumns: number; + + constructor(lineNumber: number, column: number, leftoverVisibleColumns: number) { + this.lineNumber = lineNumber; + this.column = column; + this.leftoverVisibleColumns = leftoverVisibleColumns; + } +} + +/** + * Common operations that work and make sense both on the model and on the view model. + */ +export class CursorMove { + + private static _isLowSurrogate(model: ICursorMoveHelperModel, lineNumber: number, charOffset: number): boolean { + let lineContent = model.getLineContent(lineNumber); + if (charOffset < 0 || charOffset >= lineContent.length) { + return false; + } + return strings.isLowSurrogate(lineContent.charCodeAt(charOffset)); + } + + private static _isHighSurrogate(model: ICursorMoveHelperModel, lineNumber: number, charOffset: number): boolean { + let lineContent = model.getLineContent(lineNumber); + if (charOffset < 0 || charOffset >= lineContent.length) { + return false; + } + return strings.isHighSurrogate(lineContent.charCodeAt(charOffset)); + } + + private static _isInsideSurrogatePair(model: ICursorMoveHelperModel, lineNumber: number, column: number): boolean { + return this._isHighSurrogate(model, lineNumber, column - 2); + } + + public static left(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, lineNumber: number, column: number): CursorMoveResult { + + if (column > model.getLineMinColumn(lineNumber)) { + if (this._isLowSurrogate(model, lineNumber, column - 2)) { + // character before column is a low surrogate + column = column - 2; + } else { + column = column - 1; + } + } else if (lineNumber > 1) { + lineNumber = lineNumber - 1; + column = model.getLineMaxColumn(lineNumber); + } + + return new CursorMoveResult(lineNumber, column, 0); + } + + public static right(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, lineNumber: number, column: number): CursorMoveResult { + + if (column < model.getLineMaxColumn(lineNumber)) { + if (this._isHighSurrogate(model, lineNumber, column - 1)) { + // character after column is a high surrogate + column = column + 2; + } else { + column = column + 1; + } + } else if (lineNumber < model.getLineCount()) { + lineNumber = lineNumber + 1; + column = model.getLineMinColumn(lineNumber); + } + + return new CursorMoveResult(lineNumber, column, 0); + } + + public static up(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnFirstLine: boolean): CursorMoveResult { + const currentVisibleColumn = this.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize) + leftoverVisibleColumns; + + lineNumber = lineNumber - count; + if (lineNumber < 1) { + lineNumber = 1; + if (allowMoveOnFirstLine) { + column = model.getLineMinColumn(lineNumber); + } else { + column = Math.min(model.getLineMaxColumn(lineNumber), column); + if (this._isInsideSurrogatePair(model, lineNumber, column)) { + column = column - 1; + } + } + } else { + column = this.columnFromVisibleColumn(config, model, lineNumber, currentVisibleColumn); + if (this._isInsideSurrogatePair(model, lineNumber, column)) { + column = column - 1; + } + } + + leftoverVisibleColumns = currentVisibleColumn - this.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize); + + return new CursorMoveResult(lineNumber, column, leftoverVisibleColumns); + } + + public static down(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnLastLine: boolean): CursorMoveResult { + const currentVisibleColumn = this.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize) + leftoverVisibleColumns; + + lineNumber = lineNumber + count; + var lineCount = model.getLineCount(); + if (lineNumber > lineCount) { + lineNumber = lineCount; + if (allowMoveOnLastLine) { + column = model.getLineMaxColumn(lineNumber); + } else { + column = Math.min(model.getLineMaxColumn(lineNumber), column); + if (this._isInsideSurrogatePair(model, lineNumber, column)) { + column = column - 1; + } + } + } else { + column = this.columnFromVisibleColumn(config, model, lineNumber, currentVisibleColumn); + if (this._isInsideSurrogatePair(model, lineNumber, column)) { + column = column - 1; + } + } + + leftoverVisibleColumns = currentVisibleColumn - this.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize); + + return new CursorMoveResult(lineNumber, column, leftoverVisibleColumns); + } + + public static visibleColumnFromColumn(lineContent: string, column: number, tabSize: number): number { + let endOffset = lineContent.length; + if (endOffset > column - 1) { + endOffset = column - 1; + } + + let result = 0; + for (let i = 0; i < endOffset; i++) { + let charCode = lineContent.charCodeAt(i); + if (charCode === CharCode.Tab) { + result = this.nextTabStop(result, tabSize); + } else { + result = result + 1; + } + } + return result; + } + + private static _columnFromVisibleColumn(lineContent: string, visibleColumn: number, tabSize: number): number { + if (visibleColumn <= 0) { + return 1; + } + + const lineLength = lineContent.length; + + let beforeVisibleColumn = 0; + for (let i = 0; i < lineLength; i++) { + let charCode = lineContent.charCodeAt(i); + + let afterVisibleColumn: number; + if (charCode === CharCode.Tab) { + afterVisibleColumn = this.nextTabStop(beforeVisibleColumn, tabSize); + } else { + afterVisibleColumn = beforeVisibleColumn + 1; + } + + if (afterVisibleColumn >= visibleColumn) { + let prevDelta = visibleColumn - beforeVisibleColumn; + let afterDelta = afterVisibleColumn - visibleColumn; + if (afterDelta < prevDelta) { + return i + 2; + } else { + return i + 1; + } + } + + beforeVisibleColumn = afterVisibleColumn; + } + + // walked the entire string + return lineLength + 1; + } + + public static columnFromVisibleColumn(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, lineNumber: number, visibleColumn: number): number { + let result = this._columnFromVisibleColumn(model.getLineContent(lineNumber), visibleColumn, config.tabSize); + + let minColumn = model.getLineMinColumn(lineNumber); + if (result < minColumn) { + return minColumn; + } + + let maxColumn = model.getLineMaxColumn(lineNumber); + if (result > maxColumn) { + return maxColumn; + } + + return result; + } + + /** + * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns) + */ + public static nextTabStop(visibleColumn: number, tabSize: number): number { + return visibleColumn + tabSize - visibleColumn % tabSize; + } +} + + export interface IViewColumnSelectResult { viewSelections: Selection[]; reversed: boolean; @@ -24,125 +246,13 @@ export interface IColumnSelectResult extends IViewColumnSelectResult { toVisualColumn: number; } -export interface ICursorMoveHelperModel { - getLineCount(): number; - getLineFirstNonWhitespaceColumn(lineNumber: number): number; - getLineMinColumn(lineNumber: number): number; - getLineMaxColumn(lineNumber: number): number; - getLineLastNonWhitespaceColumn(lineNumber: number): number; - getLineContent(lineNumber: number): string; -} - -/** - * Internal indentation options (computed) for the editor. - */ -export interface IInternalIndentationOptions { - /** - * Tab size in spaces. This is used for rendering and for editing. - */ - tabSize: number; - /** - * Insert spaces instead of tabs when indenting or when auto-indenting. - */ - insertSpaces: boolean; -} - -export interface IConfiguration { - getIndentationOptions(): IInternalIndentationOptions; -} - -function isHighSurrogate(model: ICursorMoveHelperModel, lineNumber: number, column: number) { - return strings.isHighSurrogate(model.getLineContent(lineNumber).charCodeAt(column - 1)); -} - -function isLowSurrogate(model: ICursorMoveHelperModel, lineNumber: number, column: number) { - return strings.isLowSurrogate(model.getLineContent(lineNumber).charCodeAt(column - 1)); -} export class CursorMoveHelper { - private configuration: IConfiguration; + private readonly _config: CursorMoveConfiguration; - constructor(configuration: IConfiguration) { - this.configuration = configuration; - } - - public getLeftOfPosition(model: ICursorMoveHelperModel, lineNumber: number, column: number): IPosition { - - if (column > model.getLineMinColumn(lineNumber)) { - column = column - (isLowSurrogate(model, lineNumber, column - 1) ? 2 : 1); - } else if (lineNumber > 1) { - lineNumber = lineNumber - 1; - column = model.getLineMaxColumn(lineNumber); - } - - return { - lineNumber: lineNumber, - column: column - }; - } - - public getRightOfPosition(model: ICursorMoveHelperModel, lineNumber: number, column: number): IPosition { - - if (column < model.getLineMaxColumn(lineNumber)) { - column = column + (isHighSurrogate(model, lineNumber, column) ? 2 : 1); - } else if (lineNumber < model.getLineCount()) { - lineNumber = lineNumber + 1; - column = model.getLineMinColumn(lineNumber); - } - - return { - lineNumber: lineNumber, - column: column - }; - } - - public getPositionUp(model: ICursorMoveHelperModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnFirstLine: boolean): IMoveResult { - var currentVisibleColumn = this.visibleColumnFromColumn(model, lineNumber, column) + leftoverVisibleColumns; - - lineNumber = lineNumber - count; - if (lineNumber < 1) { - lineNumber = 1; - if (allowMoveOnFirstLine) { - column = model.getLineMinColumn(lineNumber); - } else { - column = Math.min(model.getLineMaxColumn(lineNumber), column); - } - } else { - column = this.columnFromVisibleColumn(model, lineNumber, currentVisibleColumn); - } - leftoverVisibleColumns = currentVisibleColumn - this.visibleColumnFromColumn(model, lineNumber, column); - - - return { - lineNumber: lineNumber, - column: column, - leftoverVisibleColumns: leftoverVisibleColumns - }; - } - - public getPositionDown(model: ICursorMoveHelperModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnLastLine: boolean): IMoveResult { - var currentVisibleColumn = this.visibleColumnFromColumn(model, lineNumber, column) + leftoverVisibleColumns; - - lineNumber = lineNumber + count; - var lineCount = model.getLineCount(); - if (lineNumber > lineCount) { - lineNumber = lineCount; - if (allowMoveOnLastLine) { - column = model.getLineMaxColumn(lineNumber); - } else { - column = Math.min(model.getLineMaxColumn(lineNumber), column); - } - } else { - column = this.columnFromVisibleColumn(model, lineNumber, currentVisibleColumn); - } - leftoverVisibleColumns = currentVisibleColumn - this.visibleColumnFromColumn(model, lineNumber, column); - - return { - lineNumber: lineNumber, - column: column, - leftoverVisibleColumns: leftoverVisibleColumns - }; + constructor(config: CursorMoveConfiguration) { + this._config = config; } public columnSelect(model: ICursorMoveHelperModel, fromLineNumber: number, fromVisibleColumn: number, toLineNumber: number, toVisibleColumn: number): IViewColumnSelectResult { @@ -219,7 +329,7 @@ export class CursorMoveHelper { } public visibleColumnFromColumn(model: ICursorMoveHelperModel, lineNumber: number, column: number): number { - return CursorMoveHelper.visibleColumnFromColumn(model, lineNumber, column, this.configuration.getIndentationOptions().tabSize); + return CursorMoveHelper.visibleColumnFromColumn(model, lineNumber, column, this._config.tabSize); } public static visibleColumnFromColumn(model: ICursorMoveHelperModel, lineNumber: number, column: number, tabSize: number): number { @@ -227,47 +337,18 @@ export class CursorMoveHelper { } public static visibleColumnFromColumn2(line: string, column: number, tabSize: number): number { - var result = 0; - for (var i = 0; i < column - 1; i++) { - result = (line.charAt(i) === '\t') ? CursorMoveHelper.nextTabColumn(result, tabSize) : result + 1; - } - return result; + return CursorMove.visibleColumnFromColumn(line, column, tabSize); } public columnFromVisibleColumn(model: ICursorMoveHelperModel, lineNumber: number, visibleColumn: number): number { - var line = model.getLineContent(lineNumber); - - var lastVisibleColumn = -1; - var thisVisibleColumn = 0; - - for (var i = 0; i < line.length && thisVisibleColumn <= visibleColumn; i++) { - lastVisibleColumn = thisVisibleColumn; - thisVisibleColumn = (line.charAt(i) === '\t') ? CursorMoveHelper.nextTabColumn(thisVisibleColumn, this.configuration.getIndentationOptions().tabSize) : thisVisibleColumn + 1; - } - - // Choose the closest - thisVisibleColumn = Math.abs(visibleColumn - thisVisibleColumn); - lastVisibleColumn = Math.abs(visibleColumn - lastVisibleColumn); - - var result: number; - if (thisVisibleColumn < lastVisibleColumn) { - result = i + 1; - } else { - result = i; - } - - var minColumn = model.getLineMinColumn(lineNumber); - if (result < minColumn) { - result = minColumn; - } - return result; + return CursorMove.columnFromVisibleColumn(this._config, model, lineNumber, visibleColumn); } /** * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns) */ public static nextTabColumn(column: number, tabSize: number): number { - return column + tabSize - column % tabSize; + return CursorMove.nextTabStop(column, tabSize); } /** diff --git a/src/vs/editor/common/controller/oneCursor.ts b/src/vs/editor/common/controller/oneCursor.ts index 2645cacb168..d2ad9252588 100644 --- a/src/vs/editor/common/controller/oneCursor.ts +++ b/src/vs/editor/common/controller/oneCursor.ts @@ -9,7 +9,7 @@ import * as strings from 'vs/base/common/strings'; import { ReplaceCommand, ReplaceCommandWithOffsetCursorState, ReplaceCommandWithoutChangingPosition } from 'vs/editor/common/commands/replaceCommand'; import { ShiftCommand } from 'vs/editor/common/commands/shiftCommand'; import { SurroundSelectionCommand } from 'vs/editor/common/commands/surroundSelectionCommand'; -import { CursorMoveHelper, ICursorMoveHelperModel, IMoveResult, IColumnSelectResult, IViewColumnSelectResult } from 'vs/editor/common/controller/cursorMoveHelper'; +import { CursorMoveHelper, CursorMove, CursorMoveConfiguration, ICursorMoveHelperModel, IColumnSelectResult, IViewColumnSelectResult } from 'vs/editor/common/controller/cursorMoveHelper'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection, SelectionDirection } from 'vs/editor/common/core/selection'; @@ -19,6 +19,7 @@ import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageCo import { CharCode } from 'vs/base/common/charCode'; import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier'; import { IElectricAction } from 'vs/editor/common/modes/supports/electricCharacter'; +import { IDisposable } from 'vs/base/common/lifecycle'; export interface IPostOperationRunnable { (ctx: IOneCursorOperationContext): void; @@ -119,167 +120,80 @@ export const enum WordNavigationType { WordEnd = 1 } -export class OneCursor { - - // --- contextual state - private editorId: number; - public model: editorCommon.IModel; - public configuration: editorCommon.IConfiguration; - public modeConfiguration: IModeConfiguration; - private helper: CursorHelper; - private viewModelHelper: IViewModelHelper; +/** + * Represents the cursor state on either the model or on the view model. + */ +export class CursorModelState { + _cursorModelStateBrand: void; // --- selection can start as a range (think double click and drag) - private selectionStart: Range; - private viewSelectionStart: Range; - private selectionStartLeftoverVisibleColumns: number; - - // --- position - private position: Position; - private viewPosition: Position; - private leftoverVisibleColumns: number; - - // --- bracket match decorations - private bracketDecorations: string[]; - - // --- computed properties - private _cachedSelection: Selection; - private _cachedViewSelection: Selection; - private _selStartMarker: string; - private _selEndMarker: string; - private _selDirection: SelectionDirection; + public readonly selectionStart: Range; + public readonly selectionStartLeftoverVisibleColumns: number; + public readonly position: Position; + public readonly leftoverVisibleColumns: number; + public readonly selection: Selection; constructor( - editorId: number, - model: editorCommon.IModel, - configuration: editorCommon.IConfiguration, - modeConfiguration: IModeConfiguration, - viewModelHelper: IViewModelHelper + selectionStart: Range, + selectionStartLeftoverVisibleColumns: number, + position: Position, + leftoverVisibleColumns: number, ) { - this.editorId = editorId; - this.model = model; - this.configuration = configuration; - this.modeConfiguration = modeConfiguration; - this.viewModelHelper = viewModelHelper; - this.helper = new CursorHelper(this.model, this.configuration); - - this.bracketDecorations = []; - - this._set( - new Range(1, 1, 1, 1), 0, - new Position(1, 1), 0, - new Range(1, 1, 1, 1), new Position(1, 1) - ); - } - - private _set( - selectionStart: Range, selectionStartLeftoverVisibleColumns: number, - position: Position, leftoverVisibleColumns: number, - viewSelectionStart: Range, viewPosition: Position - ): void { this.selectionStart = selectionStart; this.selectionStartLeftoverVisibleColumns = selectionStartLeftoverVisibleColumns; - this.position = position; this.leftoverVisibleColumns = leftoverVisibleColumns; - - this.viewSelectionStart = viewSelectionStart; - this.viewPosition = viewPosition; - - this._cachedSelection = OneCursor.computeSelection(this.selectionStart, this.position); - this._cachedViewSelection = OneCursor.computeSelection(this.viewSelectionStart, this.viewPosition); - - this._selStartMarker = this._ensureMarker(this._selStartMarker, this._cachedSelection.startLineNumber, this._cachedSelection.startColumn, true); - this._selEndMarker = this._ensureMarker(this._selEndMarker, this._cachedSelection.endLineNumber, this._cachedSelection.endColumn, false); - this._selDirection = this._cachedSelection.getDirection(); + this.selection = CursorModelState._computeSelection(this.selectionStart, this.position); } - private _ensureMarker(markerId: string, lineNumber: number, column: number, stickToPreviousCharacter: boolean): string { - if (!markerId) { - return this.model._addMarker(lineNumber, column, stickToPreviousCharacter); - } else { - this.model._changeMarker(markerId, lineNumber, column); - this.model._changeMarkerStickiness(markerId, stickToPreviousCharacter); - return markerId; - } - } - - public saveState(): IOneCursorState { - return { - selectionStart: this.selectionStart, - viewSelectionStart: this.viewSelectionStart, - position: this.position, - viewPosition: this.viewPosition, - leftoverVisibleColumns: this.leftoverVisibleColumns, - selectionStartLeftoverVisibleColumns: this.selectionStartLeftoverVisibleColumns - }; - } - - public restoreState(state: IOneCursorState): void { - let position = this.model.validatePosition(state.position); - let selectionStart: Range; - if (state.selectionStart) { - selectionStart = this.model.validateRange(state.selectionStart); - } else { - selectionStart = new Range(position.lineNumber, position.column, position.lineNumber, position.column); - } - - let viewPosition = this.viewModelHelper.validateViewPosition(state.viewPosition.lineNumber, state.viewPosition.column, position); - let viewSelectionStart: Range; - if (state.viewSelectionStart) { - viewSelectionStart = this.viewModelHelper.validateViewRange(state.viewSelectionStart.startLineNumber, state.viewSelectionStart.startColumn, state.viewSelectionStart.endLineNumber, state.viewSelectionStart.endColumn, selectionStart); - } else { - viewSelectionStart = this.viewModelHelper.convertModelRangeToViewRange(selectionStart); - } - - this._set( - selectionStart, state.selectionStartLeftoverVisibleColumns, - position, state.leftoverVisibleColumns, - viewSelectionStart, viewPosition + public withSelectionStartLeftoverVisibleColumns(selectionStartLeftoverVisibleColumns: number): CursorModelState { + return new CursorModelState( + this.selectionStart, + selectionStartLeftoverVisibleColumns, + this.position, + this.leftoverVisibleColumns ); } - public updateModeConfiguration(modeConfiguration: IModeConfiguration): void { - this.modeConfiguration = modeConfiguration; - } - - public duplicate(): OneCursor { - let result = new OneCursor(this.editorId, this.model, this.configuration, this.modeConfiguration, this.viewModelHelper); - result._set( - this.selectionStart, this.selectionStartLeftoverVisibleColumns, - this.position, this.leftoverVisibleColumns, - this.viewSelectionStart, this.viewPosition + public withSelectionStart(selectionStart: Range): CursorModelState { + return new CursorModelState( + selectionStart, + 0, + this.position, + this.leftoverVisibleColumns ); - return result; } - public dispose(): void { - this.model._removeMarker(this._selStartMarker); - this.model._removeMarker(this._selEndMarker); - this.bracketDecorations = this.model.deltaDecorations(this.bracketDecorations, [], this.editorId); + public collapse(): CursorModelState { + return new CursorModelState( + new Range(this.position.lineNumber, this.position.column, this.position.lineNumber, this.position.column), + 0, + this.position, + 0 + ); } - public adjustBracketDecorations(): void { - let bracketMatch: [Range, Range] = null; - let selection = this.getSelection(); - if (selection.isEmpty()) { - bracketMatch = this.model.matchBracket(this.position); + public move(inSelectionMode: boolean, position: Position, leftoverVisibleColumns: number): CursorModelState { + if (inSelectionMode) { + // move just position + return new CursorModelState( + this.selectionStart, + this.selectionStartLeftoverVisibleColumns, + position, + leftoverVisibleColumns + ); + } else { + // move everything + return new CursorModelState( + new Range(position.lineNumber, position.column, position.lineNumber, position.column), + leftoverVisibleColumns, + position, + leftoverVisibleColumns + ); } - - let newDecorations: editorCommon.IModelDeltaDecoration[] = []; - if (bracketMatch) { - let options: editorCommon.IModelDecorationOptions = { - stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - className: 'bracket-match' - }; - newDecorations.push({ range: bracketMatch[0], options: options }); - newDecorations.push({ range: bracketMatch[1], options: options }); - } - - this.bracketDecorations = this.model.deltaDecorations(this.bracketDecorations, newDecorations, this.editorId); } - private static computeSelection(selectionStart: Range, position: Position): Selection { + private static _computeSelection(selectionStart: Range, position: Position): Selection { let startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number; if (selectionStart.isEmpty()) { startLineNumber = selectionStart.startLineNumber; @@ -306,56 +220,216 @@ export class OneCursor { endColumn ); } +} - public setSelection(desiredSelection: editorCommon.ISelection): void { - let position = this.model.validatePosition({ - lineNumber: desiredSelection.positionLineNumber, - column: desiredSelection.positionColumn +export interface IOneCursor { + readonly modelState: CursorModelState; + readonly viewState: CursorModelState; + readonly config: CursorMoveConfiguration; +} + +export class OneCursor implements IOneCursor { + + // --- contextual state + private readonly editorId: number; + public readonly model: editorCommon.IModel; + public readonly viewModel: ICursorMoveHelperModel; + public readonly configuration: editorCommon.IConfiguration; + private readonly viewModelHelper: IViewModelHelper; + + private readonly _modelOptionsListener: IDisposable; + private readonly _configChangeListener: IDisposable; + + public modeConfiguration: IModeConfiguration; + private helper: CursorHelper; + public config: CursorMoveConfiguration; + + public modelState: CursorModelState; + public viewState: CursorModelState; + + // --- bracket match decorations + private bracketDecorations: string[]; + + // --- computed properties + private _selStartMarker: string; + private _selEndMarker: string; + + constructor( + editorId: number, + model: editorCommon.IModel, + configuration: editorCommon.IConfiguration, + modeConfiguration: IModeConfiguration, + viewModelHelper: IViewModelHelper + ) { + this.editorId = editorId; + this.model = model; + this.configuration = configuration; + this.modeConfiguration = modeConfiguration; + this.viewModelHelper = viewModelHelper; + this.viewModel = viewModelHelper.viewModel; + + this._recreateCursorHelper(); + + this._modelOptionsListener = model.onDidChangeOptions(() => this._recreateCursorHelper()); + + this._configChangeListener = this.configuration.onDidChange((e) => { + if (e.layoutInfo) { + // due to pageSize + this._recreateCursorHelper(); + } }); - let selectionStartPosition = this.model.validatePosition({ - lineNumber: desiredSelection.selectionStartLineNumber, - column: desiredSelection.selectionStartColumn - }); - let selectionStart = new Range(selectionStartPosition.lineNumber, selectionStartPosition.column, selectionStartPosition.lineNumber, selectionStartPosition.column); - let viewPosition = this.viewModelHelper.convertModelPositionToViewPosition(position.lineNumber, position.column); - let viewSelectionStart = this.viewModelHelper.convertModelRangeToViewRange(selectionStart); + this.bracketDecorations = []; - this._set( - selectionStart, 0, - position, 0, - viewSelectionStart, viewPosition + this._setState( + new CursorModelState(new Range(1, 1, 1, 1), 0, new Position(1, 1), 0), + new CursorModelState(new Range(1, 1, 1, 1), 0, new Position(1, 1), 0) ); } - public setViewSelection(desiredViewSel: editorCommon.ISelection): void { - let viewSelectionStart = this.viewModelHelper.validateViewRange(desiredViewSel.selectionStartLineNumber, desiredViewSel.selectionStartColumn, desiredViewSel.selectionStartLineNumber, desiredViewSel.selectionStartColumn, this.selectionStart); - let viewPosition = this.viewModelHelper.validateViewPosition(desiredViewSel.positionLineNumber, desiredViewSel.positionColumn, this.position); + private _recreateCursorHelper(): void { + this.config = new CursorMoveConfiguration( + this.model.getOptions().tabSize, + this.getPageSize() + ); + this.helper = new CursorHelper(this.model, this.configuration, this.config); + } - this._set( - this.selectionStart, 0, - this.position, 0, - viewSelectionStart, viewPosition + private _setState(modelState: CursorModelState, viewState: CursorModelState): void { + this.modelState = modelState; + this.viewState = viewState; + + this._selStartMarker = this._ensureMarker(this._selStartMarker, this.modelState.selection.startLineNumber, this.modelState.selection.startColumn, true); + this._selEndMarker = this._ensureMarker(this._selEndMarker, this.modelState.selection.endLineNumber, this.modelState.selection.endColumn, false); + } + + private _ensureMarker(markerId: string, lineNumber: number, column: number, stickToPreviousCharacter: boolean): string { + if (!markerId) { + return this.model._addMarker(lineNumber, column, stickToPreviousCharacter); + } else { + this.model._changeMarker(markerId, lineNumber, column); + this.model._changeMarkerStickiness(markerId, stickToPreviousCharacter); + return markerId; + } + } + + public saveState(): IOneCursorState { + return { + selectionStart: this.modelState.selectionStart, + viewSelectionStart: this.viewState.selectionStart, + position: this.modelState.position, + viewPosition: this.viewState.position, + leftoverVisibleColumns: this.modelState.leftoverVisibleColumns, + selectionStartLeftoverVisibleColumns: this.modelState.selectionStartLeftoverVisibleColumns + }; + } + + public restoreState(state: IOneCursorState): void { + let position = this.model.validatePosition(state.position); + let selectionStart: Range; + if (state.selectionStart) { + selectionStart = this.model.validateRange(state.selectionStart); + } else { + selectionStart = new Range(position.lineNumber, position.column, position.lineNumber, position.column); + } + + let viewPosition = this.viewModelHelper.validateViewPosition(state.viewPosition.lineNumber, state.viewPosition.column, position); + let viewSelectionStart: Range; + if (state.viewSelectionStart) { + viewSelectionStart = this.viewModelHelper.validateViewRange(state.viewSelectionStart.startLineNumber, state.viewSelectionStart.startColumn, state.viewSelectionStart.endLineNumber, state.viewSelectionStart.endColumn, selectionStart); + } else { + viewSelectionStart = this.viewModelHelper.convertModelRangeToViewRange(selectionStart); + } + + this._setState( + new CursorModelState(selectionStart, state.selectionStartLeftoverVisibleColumns, position, state.leftoverVisibleColumns), + new CursorModelState(viewSelectionStart, state.selectionStartLeftoverVisibleColumns, viewPosition, state.leftoverVisibleColumns) + ); + } + + public updateModeConfiguration(modeConfiguration: IModeConfiguration): void { + this.modeConfiguration = modeConfiguration; + } + + public duplicate(): OneCursor { + let result = new OneCursor(this.editorId, this.model, this.configuration, this.modeConfiguration, this.viewModelHelper); + result._setState( + this.modelState, + this.viewState + ); + return result; + } + + public dispose(): void { + this._modelOptionsListener.dispose(); + this._configChangeListener.dispose(); + this.model._removeMarker(this._selStartMarker); + this.model._removeMarker(this._selEndMarker); + this.bracketDecorations = this.model.deltaDecorations(this.bracketDecorations, [], this.editorId); + } + + public adjustBracketDecorations(): void { + let bracketMatch: [Range, Range] = null; + let selection = this.modelState.selection; + if (selection.isEmpty()) { + bracketMatch = this.model.matchBracket(this.modelState.position); + } + + let newDecorations: editorCommon.IModelDeltaDecoration[] = []; + if (bracketMatch) { + let options: editorCommon.IModelDecorationOptions = { + stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + className: 'bracket-match' + }; + newDecorations.push({ range: bracketMatch[0], options: options }); + newDecorations.push({ range: bracketMatch[1], options: options }); + } + + this.bracketDecorations = this.model.deltaDecorations(this.bracketDecorations, newDecorations, this.editorId); + } + + + + public setSelection(selection: editorCommon.ISelection, viewSelection: editorCommon.ISelection = null): void { + let position = this.model.validatePosition({ + lineNumber: selection.positionLineNumber, + column: selection.positionColumn + }); + let selectionStart = this.model.validatePosition({ + lineNumber: selection.selectionStartLineNumber, + column: selection.selectionStartColumn + }); + + let viewPosition: Position; + let viewSelectionStart: Position; + + if (viewSelection) { + viewPosition = this.viewModelHelper.validateViewPosition(viewSelection.positionLineNumber, viewSelection.positionColumn, position); + viewSelectionStart = this.viewModelHelper.validateViewPosition(viewSelection.selectionStartLineNumber, viewSelection.selectionStartColumn, selectionStart); + } else { + viewPosition = this.viewModelHelper.convertModelPositionToViewPosition(position.lineNumber, position.column); + viewSelectionStart = this.viewModelHelper.convertModelPositionToViewPosition(selectionStart.lineNumber, selectionStart.column); + } + + this._setState( + new CursorModelState(new Range(selectionStart.lineNumber, selectionStart.column, selectionStart.lineNumber, selectionStart.column), 0, position, 0), + new CursorModelState(new Range(viewSelectionStart.lineNumber, viewSelectionStart.column, viewSelectionStart.lineNumber, viewSelectionStart.column), 0, viewPosition, 0) ); } // -------------------- START modifications - public setSelectionStart(rng: Range, viewRng: Range): void { - this._set( - rng, this.selectionStartLeftoverVisibleColumns, - this.position, this.leftoverVisibleColumns, - viewRng, this.viewPosition + public setSelectionStart(range: Range): void { + this._setState( + this.modelState.withSelectionStart(range), + this.viewState.withSelectionStart(this.viewModelHelper.convertModelRangeToViewRange(range)) ); } public collapseSelection(): void { - let selectionStart = new Range(this.position.lineNumber, this.position.column, this.position.lineNumber, this.position.column); - let viewSelectionStart = new Range(this.viewPosition.lineNumber, this.viewPosition.column, this.viewPosition.lineNumber, this.viewPosition.column); - this._set( - selectionStart, 0, - this.position, this.leftoverVisibleColumns, - viewSelectionStart, this.viewPosition + this._setState( + this.modelState.collapse(), + this.viewState.collapse() ); } @@ -391,34 +465,17 @@ export class OneCursor { } } - this._actualMove(inSelectionMode, new Position(lineNumber, column), new Position(viewLineNumber, viewColumn), leftoverVisibleColumns); - } - - private _actualMove(inSelectionMode: boolean, position: Position, viewPosition: Position, leftoverVisibleColumns: number): void { - if (inSelectionMode) { - // move just position - this._set( - this.selectionStart, this.selectionStartLeftoverVisibleColumns, - position, leftoverVisibleColumns, - this.viewSelectionStart, viewPosition - ); - } else { - // move everything - let selectionStart = new Range(position.lineNumber, position.column, position.lineNumber, position.column); - let viewSelectionStart = new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column); - this._set( - selectionStart, leftoverVisibleColumns, - position, leftoverVisibleColumns, - viewSelectionStart, viewPosition - ); - } + this._setState( + this.modelState.move(inSelectionMode, new Position(lineNumber, column), leftoverVisibleColumns), + this.viewState.move(inSelectionMode, new Position(viewLineNumber, viewColumn), leftoverVisibleColumns) + ); } private _recoverSelectionFromMarkers(): Selection { let start = this.model._getMarker(this._selStartMarker); let end = this.model._getMarker(this._selEndMarker); - if (this._selDirection === SelectionDirection.LTR) { + if (this.modelState.selection.getDirection() === SelectionDirection.LTR) { return new Selection(start.lineNumber, start.column, end.lineNumber, end.column); } @@ -440,10 +497,9 @@ export class OneCursor { let viewSelectionStart = this.viewModelHelper.convertModelRangeToViewRange(selectionStart); let viewPosition = this.viewModelHelper.convertViewToModelPosition(position.lineNumber, position.column); - this._set( - selectionStart, 0, - position, 0, - viewSelectionStart, viewPosition + this._setState( + new CursorModelState(selectionStart, 0, position, 0), + new CursorModelState(viewSelectionStart, 0, viewPosition, 0) ); return true; @@ -458,40 +514,21 @@ export class OneCursor { return Math.floor(c.layoutInfo.height / c.fontInfo.lineHeight) - 2; } - public getSelectionStart(): Range { - return this.selectionStart; - } - public getPosition(): Position { - return this.position; - } - public getSelection(): Selection { - return this._cachedSelection; - } - - public getViewPosition(): Position { - return this.viewPosition; - } - public getViewSelection(): Selection { - return this._cachedViewSelection; - } public getValidViewPosition(): Position { - return this.viewModelHelper.validateViewPosition(this.viewPosition.lineNumber, this.viewPosition.column, this.position); + return this.viewModelHelper.validateViewPosition(this.viewState.position.lineNumber, this.viewState.position.column, this.modelState.position); } public hasSelection(): boolean { - return (!this.getSelection().isEmpty() || !this.selectionStart.isEmpty()); + return (!this.modelState.selection.isEmpty() || !this.modelState.selectionStart.isEmpty()); } public getBracketsDecorations(): string[] { return this.bracketDecorations; } - public getLeftoverVisibleColumns(): number { - return this.leftoverVisibleColumns; - } - public getSelectionStartLeftoverVisibleColumns(): number { - return this.selectionStartLeftoverVisibleColumns; - } public setSelectionStartLeftoverVisibleColumns(value: number): void { - this.selectionStartLeftoverVisibleColumns = value; + this._setState( + this.modelState.withSelectionStartLeftoverVisibleColumns(value), + this.viewState.withSelectionStartLeftoverVisibleColumns(value) + ); } // -- utils @@ -559,36 +596,18 @@ export class OneCursor { let visibleLineNumber = visibleRange.endLineNumber - (lineFromBottom - 1); return visibleLineNumber > visibleRange.startLineNumber ? visibleLineNumber : this.getLineFromViewPortTop(); } - public getLineContent(lineNumber: number): string { - return this.model.getLineContent(lineNumber); - } public findPreviousWordOnLine(position: Position): IFindWordResult { return this.helper.findPreviousWordOnLine(position); } public findNextWordOnLine(position: Position): IFindWordResult { return this.helper.findNextWordOnLine(position); } - public getLeftOfPosition(lineNumber: number, column: number): editorCommon.IPosition { - return this.helper.getLeftOfPosition(this.model, lineNumber, column); - } - public getRightOfPosition(lineNumber: number, column: number): editorCommon.IPosition { - return this.helper.getRightOfPosition(this.model, lineNumber, column); - } - public getPositionUp(lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnFirstLine: boolean): IMoveResult { - return this.helper.getPositionUp(this.model, lineNumber, column, leftoverVisibleColumns, count, allowMoveOnFirstLine); - } - public getPositionDown(lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnLastLine: boolean): IMoveResult { - return this.helper.getPositionDown(this.model, lineNumber, column, leftoverVisibleColumns, count, allowMoveOnLastLine); - } public getColumnAtEndOfLine(lineNumber: number, column: number): number { return this.helper.getColumnAtEndOfLine(this.model, lineNumber, column); } public getVisibleColumnFromColumn(lineNumber: number, column: number): number { return this.helper.visibleColumnFromColumn(this.model, lineNumber, column); } - public getColumnFromVisibleColumn(lineNumber: number, column: number): number { - return this.helper.columnFromVisibleColumn(this.model, lineNumber, column); - } public getViewVisibleColumnFromColumn(viewLineNumber: number, viewColumn: number): number { return this.helper.visibleColumnFromColumn(this.viewModelHelper.viewModel, viewLineNumber, viewColumn); } @@ -636,18 +655,6 @@ export class OneCursor { public getViewLineLastNonWhiteSpaceColumn(lineNumber: number): number { return this.viewModelHelper.viewModel.getLineLastNonWhitespaceColumn(lineNumber); } - public getLeftOfViewPosition(lineNumber: number, column: number): editorCommon.IPosition { - return this.helper.getLeftOfPosition(this.viewModelHelper.viewModel, lineNumber, column); - } - public getRightOfViewPosition(lineNumber: number, column: number): editorCommon.IPosition { - return this.helper.getRightOfPosition(this.viewModelHelper.viewModel, lineNumber, column); - } - public getViewPositionUp(lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnFirstLine: boolean): IMoveResult { - return this.helper.getPositionUp(this.viewModelHelper.viewModel, lineNumber, column, leftoverVisibleColumns, count, allowMoveOnFirstLine); - } - public getViewPositionDown(lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnLastLine: boolean): IMoveResult { - return this.helper.getPositionDown(this.viewModelHelper.viewModel, lineNumber, column, leftoverVisibleColumns, count, allowMoveOnLastLine); - } public getColumnAtBeginningOfViewLine(lineNumber: number, column: number): number { return this.helper.getColumnAtBeginningOfLine(this.viewModelHelper.viewModel, lineNumber, column); } @@ -665,7 +672,7 @@ export class OneCursor { }; } public getNearestRevealViewPositionInViewport(): Position { - const position = this.getViewPosition(); + const position = this.viewState.position; const revealRange = this.getRevealViewLinesRangeInViewport(); if (position.lineNumber < revealRange.startLineNumber) { @@ -694,7 +701,7 @@ export class OneCursorOp { let firstBracket = cursor.model.getDecorationRange(bracketDecorations[0]); let secondBracket = cursor.model.getDecorationRange(bracketDecorations[1]); - let position = cursor.getPosition(); + let position = cursor.modelState.position; if (Utils.isPositionAtRangeEdges(position, firstBracket) || Utils.isPositionInsideRange(position, firstBracket)) { cursor.moveModelPosition(false, secondBracket.endLineNumber, secondBracket.endColumn, 0, false); @@ -788,7 +795,7 @@ export class OneCursorOp { } private static _columnSelectOp(cursor: OneCursor, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult { - let viewStartSelection = cursor.getViewSelection(); + let viewStartSelection = cursor.viewState.selection; let fromVisibleColumn = cursor.getViewVisibleColumnFromColumn(viewStartSelection.selectionStartLineNumber, viewStartSelection.selectionStartColumn); return cursor.columnSelect(viewStartSelection.selectionStartLineNumber, fromVisibleColumn, toViewLineNumber, toViewVisualColumn); @@ -817,8 +824,8 @@ export class OneCursorOp { public static columnSelectRight(cursor: OneCursor, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult { let maxVisualViewColumn = 0; - let minViewLineNumber = Math.min(cursor.getViewPosition().lineNumber, toViewLineNumber); - let maxViewLineNumber = Math.max(cursor.getViewPosition().lineNumber, toViewLineNumber); + let minViewLineNumber = Math.min(cursor.viewState.position.lineNumber, toViewLineNumber); + let maxViewLineNumber = Math.max(cursor.viewState.position.lineNumber, toViewLineNumber); for (let lineNumber = minViewLineNumber; lineNumber <= maxViewLineNumber; lineNumber++) { let lineMaxViewColumn = cursor.getViewLineMaxColumn(lineNumber); let lineMaxVisualViewColumn = cursor.getViewVisibleColumnFromColumn(lineNumber, lineMaxViewColumn); @@ -860,13 +867,13 @@ export class OneCursorOp { if (cursor.hasSelection() && !inSelectionMode) { // If we are in selection mode, move left without selection cancels selection and puts cursor at the beginning of the selection - let viewSelection = cursor.getViewSelection(); - let viewSelectionStart = cursor.validateViewPosition(viewSelection.startLineNumber, viewSelection.startColumn, cursor.getSelection().getStartPosition()); + let viewSelection = cursor.viewState.selection; + let viewSelectionStart = cursor.validateViewPosition(viewSelection.startLineNumber, viewSelection.startColumn, cursor.modelState.selection.getStartPosition()); viewLineNumber = viewSelectionStart.lineNumber; viewColumn = viewSelectionStart.column; } else { let validatedViewPosition = cursor.getValidViewPosition(); - let r = cursor.getLeftOfViewPosition(validatedViewPosition.lineNumber, validatedViewPosition.column - (noOfColumns - 1)); + let r = CursorMove.left(cursor.config, cursor.viewModel, validatedViewPosition.lineNumber, validatedViewPosition.column - (noOfColumns - 1)); viewLineNumber = r.lineNumber; viewColumn = r.column; } @@ -877,7 +884,7 @@ export class OneCursorOp { } public static moveWordLeft(cursor: OneCursor, inSelectionMode: boolean, wordNavigationType: WordNavigationType, ctx: IOneCursorOperationContext): boolean { - let position = cursor.getPosition(); + let position = cursor.modelState.position; let lineNumber = position.lineNumber; let column = position.column; @@ -918,13 +925,13 @@ export class OneCursorOp { if (cursor.hasSelection() && !inSelectionMode) { // If we are in selection mode, move right without selection cancels selection and puts cursor at the end of the selection - let viewSelection = cursor.getViewSelection(); - let viewSelectionEnd = cursor.validateViewPosition(viewSelection.endLineNumber, viewSelection.endColumn, cursor.getSelection().getEndPosition()); + let viewSelection = cursor.viewState.selection; + let viewSelectionEnd = cursor.validateViewPosition(viewSelection.endLineNumber, viewSelection.endColumn, cursor.modelState.selection.getEndPosition()); viewLineNumber = viewSelectionEnd.lineNumber; viewColumn = viewSelectionEnd.column; } else { let validatedViewPosition = cursor.getValidViewPosition(); - let r = cursor.getRightOfViewPosition(validatedViewPosition.lineNumber, validatedViewPosition.column + (noOfColumns - 1)); + let r = CursorMove.right(cursor.config, cursor.viewModel, validatedViewPosition.lineNumber, validatedViewPosition.column + (noOfColumns - 1)); viewLineNumber = r.lineNumber; viewColumn = r.column; } @@ -935,7 +942,7 @@ export class OneCursorOp { } public static moveWordRight(cursor: OneCursor, inSelectionMode: boolean, wordNavigationType: WordNavigationType, ctx: IOneCursorOperationContext): boolean { - let position = cursor.getPosition(); + let position = cursor.modelState.position; let lineNumber = position.lineNumber; let column = position.column; @@ -984,8 +991,8 @@ export class OneCursorOp { if (cursor.hasSelection() && !inSelectionMode) { // If we are in selection mode, move down acts relative to the end of selection - let viewSelection = cursor.getViewSelection(); - let viewSelectionEnd = cursor.validateViewPosition(viewSelection.endLineNumber, viewSelection.endColumn, cursor.getSelection().getEndPosition()); + let viewSelection = cursor.viewState.selection; + let viewSelectionEnd = cursor.validateViewPosition(viewSelection.endLineNumber, viewSelection.endColumn, cursor.modelState.selection.getEndPosition()); viewLineNumber = viewSelectionEnd.lineNumber; viewColumn = viewSelectionEnd.column; } else { @@ -994,7 +1001,7 @@ export class OneCursorOp { viewColumn = validatedViewPosition.column; } - let r = cursor.getViewPositionDown(viewLineNumber, viewColumn, cursor.getLeftoverVisibleColumns(), linesCount, true); + let r = CursorMove.down(cursor.config, cursor.viewModel, viewLineNumber, viewColumn, cursor.viewState.leftoverVisibleColumns, linesCount, true); ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit; cursor.moveViewPosition(inSelectionMode, r.lineNumber, r.column, r.leftoverVisibleColumns, true); return true; @@ -1006,16 +1013,16 @@ export class OneCursorOp { if (cursor.hasSelection() && !inSelectionMode) { // If we are in selection mode, move down acts relative to the end of selection - let selection = cursor.getSelection(); + let selection = cursor.modelState.selection; lineNumber = selection.endLineNumber; column = selection.endColumn; } else { - let position = cursor.getPosition(); + let position = cursor.modelState.position; lineNumber = position.lineNumber; column = position.column; } - let r = cursor.getPositionDown(lineNumber, column, cursor.getLeftoverVisibleColumns(), linesCount, true); + let r = CursorMove.down(cursor.config, cursor.model, lineNumber, column, cursor.modelState.leftoverVisibleColumns, linesCount, true); ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit; cursor.moveModelPosition(inSelectionMode, r.lineNumber, r.column, r.leftoverVisibleColumns, true); return true; @@ -1023,13 +1030,13 @@ export class OneCursorOp { public static translateDown(cursor: OneCursor, ctx: IOneCursorOperationContext): boolean { - let selection = cursor.getViewSelection(); + let selection = cursor.viewState.selection; - let selectionStart = cursor.getViewPositionDown(selection.selectionStartLineNumber, selection.selectionStartColumn, cursor.getSelectionStartLeftoverVisibleColumns(), 1, false); + let selectionStart = CursorMove.down(cursor.config, cursor.viewModel, selection.selectionStartLineNumber, selection.selectionStartColumn, cursor.viewState.selectionStartLeftoverVisibleColumns, 1, false); ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit; - cursor.moveViewPosition(false, selectionStart.lineNumber, selectionStart.column, cursor.getLeftoverVisibleColumns(), true); + cursor.moveViewPosition(false, selectionStart.lineNumber, selectionStart.column, cursor.viewState.leftoverVisibleColumns, true); - let position = cursor.getViewPositionDown(selection.positionLineNumber, selection.positionColumn, cursor.getLeftoverVisibleColumns(), 1, false); + let position = CursorMove.down(cursor.config, cursor.viewModel, selection.positionLineNumber, selection.positionColumn, cursor.viewState.leftoverVisibleColumns, 1, false); ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit; cursor.moveViewPosition(true, position.lineNumber, position.column, position.leftoverVisibleColumns, true); @@ -1053,8 +1060,8 @@ export class OneCursorOp { if (cursor.hasSelection() && !inSelectionMode) { // If we are in selection mode, move up acts relative to the beginning of selection - let viewSelection = cursor.getViewSelection(); - let viewSelectionStart = cursor.validateViewPosition(viewSelection.startLineNumber, viewSelection.startColumn, cursor.getSelection().getStartPosition()); + let viewSelection = cursor.viewState.selection; + let viewSelectionStart = cursor.validateViewPosition(viewSelection.startLineNumber, viewSelection.startColumn, cursor.modelState.selection.getStartPosition()); viewLineNumber = viewSelectionStart.lineNumber; viewColumn = viewSelectionStart.column; } else { @@ -1063,7 +1070,7 @@ export class OneCursorOp { viewColumn = validatedViewPosition.column; } - let r = cursor.getViewPositionUp(viewLineNumber, viewColumn, cursor.getLeftoverVisibleColumns(), linesCount, true); + let r = CursorMove.up(cursor.config, cursor.viewModel, viewLineNumber, viewColumn, cursor.viewState.leftoverVisibleColumns, linesCount, true); ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit; cursor.moveViewPosition(inSelectionMode, r.lineNumber, r.column, r.leftoverVisibleColumns, true); @@ -1076,16 +1083,16 @@ export class OneCursorOp { if (cursor.hasSelection() && !inSelectionMode) { // If we are in selection mode, move up acts relative to the beginning of selection - let selection = cursor.getSelection(); + let selection = cursor.modelState.selection; lineNumber = selection.startLineNumber; column = selection.startColumn; } else { - let position = cursor.getPosition(); + let position = cursor.modelState.position; lineNumber = position.lineNumber; column = position.column; } - let r = cursor.getPositionUp(lineNumber, column, cursor.getLeftoverVisibleColumns(), linesCount, true); + let r = CursorMove.up(cursor.config, cursor.model, lineNumber, column, cursor.modelState.leftoverVisibleColumns, linesCount, true); ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit; cursor.moveModelPosition(inSelectionMode, r.lineNumber, r.column, r.leftoverVisibleColumns, true); @@ -1094,13 +1101,13 @@ export class OneCursorOp { public static translateUp(cursor: OneCursor, ctx: IOneCursorOperationContext): boolean { - let selection = cursor.getViewSelection(); + let selection = cursor.viewState.selection; - let selectionStart = cursor.getViewPositionUp(selection.selectionStartLineNumber, selection.selectionStartColumn, cursor.getSelectionStartLeftoverVisibleColumns(), 1, false); + let selectionStart = CursorMove.up(cursor.config, cursor.viewModel, selection.selectionStartLineNumber, selection.selectionStartColumn, cursor.viewState.selectionStartLeftoverVisibleColumns, 1, false); ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit; - cursor.moveViewPosition(false, selectionStart.lineNumber, selectionStart.column, cursor.getLeftoverVisibleColumns(), true); + cursor.moveViewPosition(false, selectionStart.lineNumber, selectionStart.column, cursor.viewState.leftoverVisibleColumns, true); - let position = cursor.getViewPositionUp(selection.positionLineNumber, selection.positionColumn, cursor.getLeftoverVisibleColumns(), 1, false); + let position = CursorMove.up(cursor.config, cursor.viewModel, selection.positionLineNumber, selection.positionColumn, cursor.viewState.leftoverVisibleColumns, 1, false); ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit; cursor.moveViewPosition(true, position.lineNumber, position.column, position.leftoverVisibleColumns, true); @@ -1133,7 +1140,7 @@ export class OneCursorOp { public static expandLineSelection(cursor: OneCursor, ctx: IOneCursorOperationContext): boolean { ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit; - let viewSel = cursor.getViewSelection(); + let viewSel = cursor.viewState.selection; let viewStartLineNumber = viewSel.startLineNumber; let viewStartColumn = viewSel.startColumn; @@ -1146,7 +1153,7 @@ export class OneCursorOp { viewEndColumn = viewEndMaxColumn; } else { // Expand selection with one more line down - let moveResult = cursor.getViewPositionDown(viewEndLineNumber, viewEndColumn, 0, 1, true); + let moveResult = CursorMove.down(cursor.config, cursor.viewModel, viewEndLineNumber, viewEndColumn, 0, 1, true); viewEndLineNumber = moveResult.lineNumber; viewEndColumn = cursor.getViewLineMaxColumn(viewEndLineNumber); } @@ -1183,7 +1190,7 @@ export class OneCursorOp { // Toggle between selecting editable range and selecting the entire buffer let editableRange = cursor.model.getEditableRange(); - let selection = cursor.getSelection(); + let selection = cursor.modelState.selection; if (!selection.equalsRange(editableRange)) { // Selection is not editable range => select editable range @@ -1234,15 +1241,13 @@ export class OneCursorOp { } let selectionStartRange = new Range(position.lineNumber, 1, selectToLineNumber, selectToColumn); - let r1 = cursor.convertModelPositionToViewPosition(position.lineNumber, 1); - let r2 = cursor.convertModelPositionToViewPosition(selectToLineNumber, selectToColumn); - cursor.setSelectionStart(selectionStartRange, new Range(r1.lineNumber, r1.column, r2.lineNumber, r2.column)); + cursor.setSelectionStart(selectionStartRange); cursor.moveModelPosition(cursor.hasSelection(), selectionStartRange.endLineNumber, selectionStartRange.endColumn, 0, false); return true; } else { // Continuing line selection - let enteringLineNumber = cursor.getSelectionStart().getStartPosition().lineNumber; + let enteringLineNumber = cursor.modelState.selectionStart.getStartPosition().lineNumber; if (position.lineNumber < enteringLineNumber) { @@ -1260,7 +1265,7 @@ export class OneCursorOp { } else { - let endPositionOfSelectionStart = cursor.getSelectionStart().getEndPosition(); + let endPositionOfSelectionStart = cursor.modelState.selectionStart.getEndPosition(); cursor.moveModelPosition(cursor.hasSelection(), endPositionOfSelectionStart.lineNumber, endPositionOfSelectionStart.column, 0, false); } @@ -1307,9 +1312,7 @@ export class OneCursorOp { } let selectionStartRange = new Range(validatedPosition.lineNumber, startColumn, validatedPosition.lineNumber, endColumn); - let r1 = cursor.convertModelPositionToViewPosition(validatedPosition.lineNumber, startColumn); - let r2 = cursor.convertModelPositionToViewPosition(validatedPosition.lineNumber, endColumn); - cursor.setSelectionStart(selectionStartRange, new Range(r1.lineNumber, r1.column, r2.lineNumber, r2.column)); + cursor.setSelectionStart(selectionStartRange); lineNumber = selectionStartRange.endLineNumber; column = selectionStartRange.endColumn; } else { @@ -1329,17 +1332,17 @@ export class OneCursorOp { } lineNumber = validatedPosition.lineNumber; - if (validatedPosition.isBeforeOrEqual(cursor.getSelectionStart().getStartPosition())) { + if (validatedPosition.isBeforeOrEqual(cursor.modelState.selectionStart.getStartPosition())) { column = startColumn; let possiblePosition = new Position(lineNumber, column); - if (cursor.getSelectionStart().containsPosition(possiblePosition)) { - column = cursor.getSelectionStart().endColumn; + if (cursor.modelState.selectionStart.containsPosition(possiblePosition)) { + column = cursor.modelState.selectionStart.endColumn; } } else { column = endColumn; let possiblePosition = new Position(lineNumber, column); - if (cursor.getSelectionStart().containsPosition(possiblePosition)) { - column = cursor.getSelectionStart().startColumn; + if (cursor.modelState.selectionStart.containsPosition(possiblePosition)) { + column = cursor.modelState.selectionStart.startColumn; } } } @@ -1369,11 +1372,11 @@ export class OneCursorOp { return false; } - return this._enter(cursor, false, ctx, cursor.getPosition(), cursor.getSelection()); + return this._enter(cursor, false, ctx, cursor.modelState.position, cursor.modelState.selection); } public static lineInsertBefore(cursor: OneCursor, ctx: IOneCursorOperationContext): boolean { - let lineNumber = cursor.getPosition().lineNumber; + let lineNumber = cursor.modelState.position.lineNumber; if (lineNumber === 1) { ctx.executeCommand = new ReplaceCommandWithoutChangingPosition(new Range(1, 1, 1, 1), '\n'); @@ -1387,13 +1390,13 @@ export class OneCursorOp { } public static lineInsertAfter(cursor: OneCursor, ctx: IOneCursorOperationContext): boolean { - let position = cursor.getPosition(); + let position = cursor.modelState.position; let column = cursor.model.getLineMaxColumn(position.lineNumber); return this._enter(cursor, false, ctx, new Position(position.lineNumber, column), new Range(position.lineNumber, column, position.lineNumber, column)); } public static lineBreakInsert(cursor: OneCursor, ctx: IOneCursorOperationContext): boolean { - return this._enter(cursor, true, ctx, cursor.getPosition(), cursor.getSelection()); + return this._enter(cursor, true, ctx, cursor.modelState.position, cursor.modelState.selection); } private static _enter(cursor: OneCursor, keepPosition: boolean, ctx: IOneCursorOperationContext, position: Position, range: Range): boolean { @@ -1441,13 +1444,13 @@ export class OneCursorOp { return false; } - let selection = cursor.getSelection(); + let selection = cursor.modelState.selection; if (!selection.isEmpty() || !cursor.modeConfiguration.autoClosingPairsClose.hasOwnProperty(ch)) { return false; } - let position = cursor.getPosition(); + let position = cursor.modelState.position; let lineText = cursor.model.getLineContent(position.lineNumber); let beforeCharacter = lineText[position.column - 1]; @@ -1466,13 +1469,13 @@ export class OneCursorOp { return false; } - let selection = cursor.getSelection(); + let selection = cursor.modelState.selection; if (!selection.isEmpty() || !cursor.modeConfiguration.autoClosingPairsOpen.hasOwnProperty(ch)) { return false; } - let position = cursor.getPosition(); + let position = cursor.modelState.position; let lineText = cursor.model.getLineContent(position.lineNumber); let beforeCharacter = lineText[position.column - 1]; @@ -1514,7 +1517,7 @@ export class OneCursorOp { return false; } - let selection = cursor.getSelection(); + let selection = cursor.modelState.selection; if (selection.isEmpty() || !cursor.modeConfiguration.surroundingPairs.hasOwnProperty(ch)) { return false; @@ -1564,7 +1567,7 @@ export class OneCursorOp { private static _typeInterceptorElectricCharRunnable(cursor: OneCursor, ctx: IOneCursorOperationContext): void { - let position = cursor.getPosition(); + let position = cursor.modelState.position; let lineText = cursor.model.getLineContent(position.lineNumber); let lineTokens = cursor.model.getLineTokens(position.lineNumber, false); @@ -1607,14 +1610,14 @@ export class OneCursorOp { columnDeltaOffset += electricAction.advanceCount; } ctx.shouldPushStackElementAfter = true; - ctx.executeCommand = new ReplaceCommandWithOffsetCursorState(cursor.getSelection(), appendText, 0, columnDeltaOffset); + ctx.executeCommand = new ReplaceCommandWithOffsetCursorState(cursor.modelState.selection, appendText, 0, columnDeltaOffset); } } } public static actualType(cursor: OneCursor, text: string, keepPosition: boolean, ctx: IOneCursorOperationContext, range?: Range): boolean { if (typeof range === 'undefined') { - range = cursor.getSelection(); + range = cursor.modelState.selection; } if (keepPosition) { ctx.executeCommand = new ReplaceCommandWithoutChangingPosition(range, text); @@ -1650,7 +1653,7 @@ export class OneCursorOp { } public static replacePreviousChar(cursor: OneCursor, txt: string, replaceCharCnt: number, ctx: IOneCursorOperationContext): boolean { - let pos = cursor.getPosition(); + let pos = cursor.modelState.position; let range: Range; let startColumn = Math.max(1, pos.column - replaceCharCnt); range = new Range(pos.lineNumber, startColumn, pos.lineNumber, pos.column); @@ -1717,7 +1720,7 @@ export class OneCursorOp { } public static tab(cursor: OneCursor, ctx: IOneCursorOperationContext): boolean { - let selection = cursor.getSelection(); + let selection = cursor.modelState.selection; if (selection.isEmpty()) { @@ -1750,7 +1753,7 @@ export class OneCursorOp { } public static indent(cursor: OneCursor, ctx: IOneCursorOperationContext): boolean { - let selection = cursor.getSelection(); + let selection = cursor.modelState.selection; ctx.shouldPushStackElementBefore = true; ctx.shouldPushStackElementAfter = true; @@ -1765,7 +1768,7 @@ export class OneCursorOp { } public static outdent(cursor: OneCursor, ctx: IOneCursorOperationContext): boolean { - let selection = cursor.getSelection(); + let selection = cursor.modelState.selection; ctx.shouldPushStackElementBefore = true; ctx.shouldPushStackElementAfter = true; @@ -1780,8 +1783,8 @@ export class OneCursorOp { } public static paste(cursor: OneCursor, text: string, pasteOnNewLine: boolean, ctx: IOneCursorOperationContext): boolean { - let position = cursor.getPosition(); - let selection = cursor.getSelection(); + let position = cursor.modelState.position; + let selection = cursor.modelState.selection; ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Paste; @@ -1817,11 +1820,11 @@ export class OneCursorOp { return false; } - if (!cursor.getSelection().isEmpty()) { + if (!cursor.modelState.selection.isEmpty()) { return false; } - let position = cursor.getPosition(); + let position = cursor.modelState.position; let lineText = cursor.model.getLineContent(position.lineNumber); let character = lineText[position.column - 2]; @@ -1854,13 +1857,13 @@ export class OneCursorOp { return true; } - let deleteSelection: Range = cursor.getSelection(); + let deleteSelection: Range = cursor.modelState.selection; if (deleteSelection.isEmpty()) { - let position = cursor.getPosition(); + let position = cursor.modelState.position; if (cursor.configuration.editor.useTabStops && position.column > 1) { - let lineContent = cursor.getLineContent(position.lineNumber); + let lineContent = cursor.model.getLineContent(position.lineNumber); let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent); let lastIndentationColumn = ( @@ -1872,13 +1875,13 @@ export class OneCursorOp { if (position.column <= lastIndentationColumn) { let fromVisibleColumn = cursor.getVisibleColumnFromColumn(position.lineNumber, position.column); let toVisibleColumn = CursorMoveHelper.prevTabColumn(fromVisibleColumn, cursor.model.getOptions().tabSize); - let toColumn = cursor.getColumnFromVisibleColumn(position.lineNumber, toVisibleColumn); + let toColumn = CursorMove.columnFromVisibleColumn(cursor.config, cursor.model, position.lineNumber, toVisibleColumn); deleteSelection = new Range(position.lineNumber, toColumn, position.lineNumber, position.column); } else { deleteSelection = new Range(position.lineNumber, position.column - 1, position.lineNumber, position.column); } } else { - let leftOfPosition = cursor.getLeftOfPosition(position.lineNumber, position.column); + let leftOfPosition = CursorMove.left(cursor.config, cursor.model, position.lineNumber, position.column); deleteSelection = new Range( leftOfPosition.lineNumber, leftOfPosition.column, @@ -1902,8 +1905,8 @@ export class OneCursorOp { } private static deleteWordLeftWhitespace(cursor: OneCursor, ctx: IOneCursorOperationContext): boolean { - let position = cursor.getPosition(); - let lineContent = cursor.getLineContent(position.lineNumber); + let position = cursor.modelState.position; + let lineContent = cursor.model.getLineContent(position.lineNumber); let startIndex = position.column - 2; let lastNonWhitespace = strings.lastNonWhitespaceIndex(lineContent, startIndex); if (lastNonWhitespace + 1 < startIndex) { @@ -1920,10 +1923,10 @@ export class OneCursorOp { return true; } - let selection = cursor.getSelection(); + let selection = cursor.modelState.selection; if (selection.isEmpty()) { - let position = cursor.getPosition(); + let position = cursor.modelState.position; let lineNumber = position.lineNumber; let column = position.column; @@ -1968,11 +1971,11 @@ export class OneCursorOp { public static deleteRight(cursor: OneCursor, ctx: IOneCursorOperationContext): boolean { - let deleteSelection: Range = cursor.getSelection(); + let deleteSelection: Range = cursor.modelState.selection; if (deleteSelection.isEmpty()) { - let position = cursor.getPosition(); - let rightOfPosition = cursor.getRightOfPosition(position.lineNumber, position.column); + let position = cursor.modelState.position; + let rightOfPosition = CursorMove.right(cursor.config, cursor.model, position.lineNumber, position.column); deleteSelection = new Range( rightOfPosition.lineNumber, rightOfPosition.column, @@ -2006,8 +2009,8 @@ export class OneCursorOp { } private static deleteWordRightWhitespace(cursor: OneCursor, ctx: IOneCursorOperationContext): boolean { - let position = cursor.getPosition(); - let lineContent = cursor.getLineContent(position.lineNumber); + let position = cursor.modelState.position; + let lineContent = cursor.model.getLineContent(position.lineNumber); let startIndex = position.column - 1; let firstNonWhitespace = this._findFirstNonWhitespaceChar(lineContent, startIndex); if (startIndex + 1 < firstNonWhitespace) { @@ -2020,10 +2023,10 @@ export class OneCursorOp { public static deleteWordRight(cursor: OneCursor, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType, ctx: IOneCursorOperationContext): boolean { - let selection = cursor.getSelection(); + let selection = cursor.modelState.selection; if (selection.isEmpty()) { - let position = cursor.getPosition(); + let position = cursor.modelState.position; let lineNumber = position.lineNumber; let column = position.column; @@ -2094,10 +2097,10 @@ export class OneCursorOp { return true; } - let selection = cursor.getSelection(); + let selection = cursor.modelState.selection; if (selection.isEmpty()) { - let position = cursor.getPosition(); + let position = cursor.modelState.position; let lineNumber = position.lineNumber; let column = position.column; @@ -2117,10 +2120,10 @@ export class OneCursorOp { } public static deleteAllRight(cursor: OneCursor, ctx: IOneCursorOperationContext): boolean { - let selection = cursor.getSelection(); + let selection = cursor.modelState.selection; if (selection.isEmpty()) { - let position = cursor.getPosition(); + let position = cursor.modelState.position; let lineNumber = position.lineNumber; let column = position.column; let maxColumn = cursor.model.getLineMaxColumn(lineNumber); @@ -2141,13 +2144,13 @@ export class OneCursorOp { } public static cut(cursor: OneCursor, enableEmptySelectionClipboard: boolean, ctx: IOneCursorOperationContext): boolean { - let selection = cursor.getSelection(); + let selection = cursor.modelState.selection; if (selection.isEmpty()) { if (enableEmptySelectionClipboard) { // This is a full line cut - let position = cursor.getPosition(); + let position = cursor.modelState.position; let startLineNumber: number, startColumn: number, @@ -2203,30 +2206,10 @@ class CursorHelper { private configuration: editorCommon.IConfiguration; private moveHelper: CursorMoveHelper; - constructor(model: editorCommon.IModel, configuration: editorCommon.IConfiguration) { + constructor(model: editorCommon.IModel, configuration: editorCommon.IConfiguration, config: CursorMoveConfiguration) { this.model = model; this.configuration = configuration; - this.moveHelper = new CursorMoveHelper({ - getIndentationOptions: () => { - return this.model.getOptions(); - } - }); - } - - public getLeftOfPosition(model: ICursorMoveHelperModel, lineNumber: number, column: number): editorCommon.IPosition { - return this.moveHelper.getLeftOfPosition(model, lineNumber, column); - } - - public getRightOfPosition(model: ICursorMoveHelperModel, lineNumber: number, column: number): editorCommon.IPosition { - return this.moveHelper.getRightOfPosition(model, lineNumber, column); - } - - public getPositionUp(model: ICursorMoveHelperModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnFirstLine: boolean): IMoveResult { - return this.moveHelper.getPositionUp(model, lineNumber, column, leftoverVisibleColumns, count, allowMoveOnFirstLine); - } - - public getPositionDown(model: ICursorMoveHelperModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnLastLine: boolean): IMoveResult { - return this.moveHelper.getPositionDown(model, lineNumber, column, leftoverVisibleColumns, count, allowMoveOnLastLine); + this.moveHelper = new CursorMoveHelper(config); } public getColumnAtBeginningOfLine(model: ICursorMoveHelperModel, lineNumber: number, column: number): number { @@ -2245,10 +2228,6 @@ class CursorHelper { return this.moveHelper.visibleColumnFromColumn(model, lineNumber, column); } - public columnFromVisibleColumn(model: ICursorMoveHelperModel, lineNumber: number, column: number): number { - return this.moveHelper.columnFromVisibleColumn(model, lineNumber, column); - } - private static _createWord(lineContent: string, wordType: WordType, start: number, end: number): IFindWordResult { // console.log('WORD ==> ' + start + ' => ' + end + ':::: <<<' + lineContent.substring(start, end) + '>>>'); return { start: start, end: end, wordType: wordType }; diff --git a/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts b/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts new file mode 100644 index 00000000000..8ff1e854911 --- /dev/null +++ b/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts @@ -0,0 +1,224 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as assert from 'assert'; +import * as strings from 'vs/base/common/strings'; +import { CursorMoveHelper, ICursorMoveHelperModel, CursorMoveConfiguration } from 'vs/editor/common/controller/cursorMoveHelper'; + +suite('CursorMove', () => { + + test('nextTabStop', () => { + assert.equal(CursorMoveHelper.nextTabColumn(0, 4), 4); + assert.equal(CursorMoveHelper.nextTabColumn(1, 4), 4); + assert.equal(CursorMoveHelper.nextTabColumn(2, 4), 4); + assert.equal(CursorMoveHelper.nextTabColumn(3, 4), 4); + assert.equal(CursorMoveHelper.nextTabColumn(4, 4), 8); + assert.equal(CursorMoveHelper.nextTabColumn(5, 4), 8); + assert.equal(CursorMoveHelper.nextTabColumn(6, 4), 8); + assert.equal(CursorMoveHelper.nextTabColumn(7, 4), 8); + assert.equal(CursorMoveHelper.nextTabColumn(8, 4), 12); + + assert.equal(CursorMoveHelper.nextTabColumn(0, 2), 2); + assert.equal(CursorMoveHelper.nextTabColumn(1, 2), 2); + assert.equal(CursorMoveHelper.nextTabColumn(2, 2), 4); + assert.equal(CursorMoveHelper.nextTabColumn(3, 2), 4); + assert.equal(CursorMoveHelper.nextTabColumn(4, 2), 6); + assert.equal(CursorMoveHelper.nextTabColumn(5, 2), 6); + assert.equal(CursorMoveHelper.nextTabColumn(6, 2), 8); + assert.equal(CursorMoveHelper.nextTabColumn(7, 2), 8); + assert.equal(CursorMoveHelper.nextTabColumn(8, 2), 10); + + assert.equal(CursorMoveHelper.nextTabColumn(0, 1), 1); + assert.equal(CursorMoveHelper.nextTabColumn(1, 1), 2); + assert.equal(CursorMoveHelper.nextTabColumn(2, 1), 3); + assert.equal(CursorMoveHelper.nextTabColumn(3, 1), 4); + assert.equal(CursorMoveHelper.nextTabColumn(4, 1), 5); + assert.equal(CursorMoveHelper.nextTabColumn(5, 1), 6); + assert.equal(CursorMoveHelper.nextTabColumn(6, 1), 7); + assert.equal(CursorMoveHelper.nextTabColumn(7, 1), 8); + assert.equal(CursorMoveHelper.nextTabColumn(8, 1), 9); + }); + + class OneLineModel implements ICursorMoveHelperModel { + private _line: string; + + constructor(line: string) { + this._line = line; + } + + getLineCount(): number { + return 1; + } + + getLineContent(lineNumber: number): string { + return this._line; + } + + getLineMinColumn(lineNumber: number): number { + return 1; + } + + getLineMaxColumn(lineNumber: number): number { + return this._line.length + 1; + } + + getLineFirstNonWhitespaceColumn(lineNumber: number): number { + let result = strings.firstNonWhitespaceIndex(this._line); + if (result === -1) { + return 0; + } + return result + 1; + } + + getLineLastNonWhitespaceColumn(lineNumber: number): number { + let result = strings.lastNonWhitespaceIndex(this._line); + if (result === -1) { + return 0; + } + return result + 2; + } + + } + + test('visibleColumnFromColumn', () => { + + function testVisibleColumnFromColumn(text:string, tabSize:number, column:number, expected:number): void { + let helper = new CursorMoveHelper(new CursorMoveConfiguration(tabSize, 13)); + let model = new OneLineModel(text); + assert.equal(helper.visibleColumnFromColumn(model, 1, column), expected); + } + + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 1, 0); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 2, 4); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 3, 8); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 4, 9); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 5, 10); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 6, 11); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 7, 12); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 8, 13); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 9, 14); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 10, 15); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 11, 16); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 12, 17); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 13, 18); + + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 1, 0); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 2, 4); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 3, 5); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 4, 8); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 5, 9); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 6, 10); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 7, 11); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 8, 12); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 9, 13); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 10, 14); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 11, 15); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 12, 16); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 13, 17); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 14, 18); + + testVisibleColumnFromColumn('\t \tx\t', 4, -1, 0); + testVisibleColumnFromColumn('\t \tx\t', 4, 0, 0); + testVisibleColumnFromColumn('\t \tx\t', 4, 1, 0); + testVisibleColumnFromColumn('\t \tx\t', 4, 2, 4); + testVisibleColumnFromColumn('\t \tx\t', 4, 3, 5); + testVisibleColumnFromColumn('\t \tx\t', 4, 4, 6); + testVisibleColumnFromColumn('\t \tx\t', 4, 5, 8); + testVisibleColumnFromColumn('\t \tx\t', 4, 6, 9); + testVisibleColumnFromColumn('\t \tx\t', 4, 7, 12); + testVisibleColumnFromColumn('\t \tx\t', 4, 8, 12); + testVisibleColumnFromColumn('\t \tx\t', 4, 9, 12); + + testVisibleColumnFromColumn('baz', 4, 1, 0); + testVisibleColumnFromColumn('baz', 4, 2, 1); + testVisibleColumnFromColumn('baz', 4, 3, 2); + testVisibleColumnFromColumn('baz', 4, 4, 3); + + testVisibleColumnFromColumn('📚az', 4, 1, 0); + testVisibleColumnFromColumn('📚az', 4, 2, 1); + testVisibleColumnFromColumn('📚az', 4, 3, 2); + testVisibleColumnFromColumn('📚az', 4, 4, 3); + testVisibleColumnFromColumn('📚az', 4, 5, 4); + }); + + test('columnFromVisibleColumn', () => { + + function testColumnFromVisibleColumn(text:string, tabSize:number, visibleColumn:number, expected:number): void { + let helper = new CursorMoveHelper(new CursorMoveConfiguration(tabSize, 13)); + let model = new OneLineModel(text); + assert.equal(helper.columnFromVisibleColumn(model, 1, visibleColumn), expected); + } + + // testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 0, 1); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 1, 1); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 2, 1); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 3, 2); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 4, 2); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 5, 2); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 6, 2); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 7, 3); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 8, 3); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 9, 4); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 10, 5); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 11, 6); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 12, 7); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 13, 8); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 14, 9); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 15, 10); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 16, 11); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 17, 12); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 18, 13); + + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 0, 1); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 1, 1); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 2, 1); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 3, 2); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 4, 2); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 5, 3); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 6, 3); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 7, 4); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 8, 4); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 9, 5); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 10, 6); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 11, 7); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 12, 8); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 13, 9); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 14, 10); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 15, 11); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 16, 12); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 17, 13); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 18, 14); + + testColumnFromVisibleColumn('\t \tx\t', 4, -2, 1); + testColumnFromVisibleColumn('\t \tx\t', 4, -1, 1); + testColumnFromVisibleColumn('\t \tx\t', 4, 0, 1); + testColumnFromVisibleColumn('\t \tx\t', 4, 1, 1); + testColumnFromVisibleColumn('\t \tx\t', 4, 2, 1); + testColumnFromVisibleColumn('\t \tx\t', 4, 3, 2); + testColumnFromVisibleColumn('\t \tx\t', 4, 4, 2); + testColumnFromVisibleColumn('\t \tx\t', 4, 5, 3); + testColumnFromVisibleColumn('\t \tx\t', 4, 6, 4); + testColumnFromVisibleColumn('\t \tx\t', 4, 7, 4); + testColumnFromVisibleColumn('\t \tx\t', 4, 8, 5); + testColumnFromVisibleColumn('\t \tx\t', 4, 9, 6); + testColumnFromVisibleColumn('\t \tx\t', 4, 10, 6); + testColumnFromVisibleColumn('\t \tx\t', 4, 11, 7); + testColumnFromVisibleColumn('\t \tx\t', 4, 12, 7); + testColumnFromVisibleColumn('\t \tx\t', 4, 13, 7); + testColumnFromVisibleColumn('\t \tx\t', 4, 14, 7); + + testColumnFromVisibleColumn('baz', 4, 0, 1); + testColumnFromVisibleColumn('baz', 4, 1, 2); + testColumnFromVisibleColumn('baz', 4, 2, 3); + testColumnFromVisibleColumn('baz', 4, 3, 4); + + testColumnFromVisibleColumn('📚az', 4, 0, 1); + testColumnFromVisibleColumn('📚az', 4, 1, 2); + testColumnFromVisibleColumn('📚az', 4, 2, 3); + testColumnFromVisibleColumn('📚az', 4, 3, 4); + testColumnFromVisibleColumn('📚az', 4, 4, 5); + }); +}); \ No newline at end of file diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 7cc3288d9e0..bfdc3265e30 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -17,6 +17,7 @@ export interface IEnvironmentService { appRoot: string; userHome: string; + userProductHome: string; userDataPath: string; appSettingsHome: string; diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index f67f0bd8f56..11e625d0fcc 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -63,7 +63,10 @@ export class EnvironmentService implements IEnvironmentService { get execPath(): string { return this._execPath; } @memoize - get userHome(): string { return path.join(os.homedir(), product.dataFolderName); } + get userHome(): string { return os.homedir(); } + + @memoize + get userProductHome(): string { return path.join(this.userHome, product.dataFolderName); } @memoize get userDataPath(): string { return parseUserDataDir(this._args, process); } @@ -84,7 +87,7 @@ export class EnvironmentService implements IEnvironmentService { get backupWorkspacesPath(): string { return path.join(this.backupHome, 'workspaces.json'); } @memoize - get extensionsPath(): string { return path.normalize(this._args['extensions-dir'] || path.join(this.userHome, 'extensions')); } + get extensionsPath(): string { return path.normalize(this._args['extensions-dir'] || path.join(this.userProductHome, 'extensions')); } @memoize get extensionDevelopmentPath(): string { return this._args.extensionDevelopmentPath ? path.normalize(this._args.extensionDevelopmentPath) : this._args.extensionDevelopmentPath; } diff --git a/src/vs/workbench/services/extensions/common/extensionRuntimeService.ts b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts similarity index 89% rename from src/vs/workbench/services/extensions/common/extensionRuntimeService.ts rename to src/vs/platform/extensionManagement/common/extensionEnablementService.ts index 3712951dfdb..c5969b330cb 100644 --- a/src/vs/workbench/services/extensions/common/extensionRuntimeService.ts +++ b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts @@ -6,23 +6,26 @@ import { localize } from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import { distinct } from 'vs/base/common/arrays'; +import Event, { Emitter } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IExtensionManagementService, DidUninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace'; -import { IExtensionRuntimeService } from 'vs/platform/extensions/common/extensions'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IMessageService } from 'vs/platform/message/common/message'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensions/disabled'; -export class ExtensionRuntimeService implements IExtensionRuntimeService { +export class ExtensionEnablementService implements IExtensionEnablementService { _serviceBrand: any; private workspace: IWorkspace; private disposables: IDisposable[] = []; + private _onEnablementChanged = new Emitter(); + public onEnablementChanged: Event = this._onEnablementChanged.event; + constructor( @IStorageService private storageService: IStorageService, @IWorkspaceContextService contextService: IWorkspaceContextService, @@ -99,7 +102,7 @@ export class ExtensionRuntimeService implements IExtensionRuntimeService { private disableExtension(identifier: string, scope: StorageScope): TPromise { let disabledExtensions = this._getDisabledExtensions(scope); disabledExtensions.push(identifier); - this._setDisabledExtensions(disabledExtensions, scope); + this._setDisabledExtensions(disabledExtensions, scope, identifier); return TPromise.wrap(true); } @@ -108,7 +111,7 @@ export class ExtensionRuntimeService implements IExtensionRuntimeService { const index = disabledExtensions.indexOf(identifier); if (index !== -1) { disabledExtensions.splice(index, 1); - this._setDisabledExtensions(disabledExtensions, scope); + this._setDisabledExtensions(disabledExtensions, scope, identifier); } return TPromise.wrap(true); } @@ -118,12 +121,13 @@ export class ExtensionRuntimeService implements IExtensionRuntimeService { return value ? distinct(value.split(',')) : []; } - private _setDisabledExtensions(disabledExtensions: string[], scope: StorageScope): void { + private _setDisabledExtensions(disabledExtensions: string[], scope: StorageScope, extension: string): void { if (disabledExtensions.length) { this.storageService.store(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions.join(','), scope); } else { this.storageService.remove(DISABLED_EXTENSIONS_STORAGE_PATH, scope); } + this._onEnablementChanged.fire(extension); } private onDidUninstallExtension({id, error}: DidUninstallExtensionEvent): void { diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 97fcbac9e69..411d199be38 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -233,6 +233,46 @@ export interface IExtensionManagementService { getInstalled(type?: LocalExtensionType): TPromise; } +export const IExtensionEnablementService = createDecorator('extensionEnablementService'); + +// TODO: @sandy: Merge this into IExtensionManagementService when we have a storage service available in Shared process +export interface IExtensionEnablementService { + _serviceBrand: any; + + /** + * Event to listen on for extension enablement changes + */ + onEnablementChanged: Event; + + /** + * Returns all globally disabled extension identifiers. + * Returns an empty array if none exist. + */ + getGloballyDisabledExtensions(): string[]; + + /** + * Returns all workspace disabled extension identifiers. + * Returns an empty array if none exist or workspace does not exist. + */ + getWorkspaceDisabledExtensions(): string[]; + + /** + * Returns `true` if given extension can be enabled by calling `setEnablement`, otherwise false`. + */ + canEnable(identifier: string): boolean; + + /** + * Enable or disable the given extension. + * if `workspace` is `true` then enablement is done for workspace, otherwise globally. + * + * Returns a promise that resolves to boolean value. + * if resolves to `true` then requires restart for the change to take effect. + * + * Throws error if enablement is requested for workspace and there is no workspace + */ + setEnablement(identifier: string, enable: boolean, workspace?: boolean): TPromise; +} + export const IExtensionTipsService = createDecorator('extensionTipsService'); export interface IExtensionTipsService { diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 71e00c84e5e..119cc706626 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -77,38 +77,4 @@ export interface IExtensionService { * Get information about extensions status. */ getExtensionsStatus(): { [id: string]: IExtensionsStatus }; -} - -export const IExtensionRuntimeService = createDecorator('extensionRuntimeService'); - -export interface IExtensionRuntimeService { - _serviceBrand: any; - - /** - * Returns all globally disabled extension identifiers. - * Returns an empty array if none exist. - */ - getGloballyDisabledExtensions(): string[]; - - /** - * Returns all workspace disabled extension identifiers. - * Returns an empty array if none exist or workspace does not exist. - */ - getWorkspaceDisabledExtensions(): string[]; - - /** - * Returns `true` if given extension can be enabled by calling `setEnablement`, otherwise false`. - */ - canEnable(identifier: string): boolean; - - /** - * Enable or disable the given extension. - * if `workspace` is `true` then enablement is done for workspace, otherwise globally. - * - * Returns a promise that resolves to boolean value. - * if resolves to `true` then requires restart for the change to take effect. - * - * Throws error if enablement is requested for workspace and there is no workspace - */ - setEnablement(identifier: string, enable: boolean, workspace?: boolean): TPromise; -} +} \ No newline at end of file diff --git a/src/vs/platform/keybinding/browser/keybindingServiceImpl.ts b/src/vs/platform/keybinding/browser/keybindingServiceImpl.ts index da145c71d04..bc68d738e71 100644 --- a/src/vs/platform/keybinding/browser/keybindingServiceImpl.ts +++ b/src/vs/platform/keybinding/browser/keybindingServiceImpl.ts @@ -182,7 +182,7 @@ export abstract class KeybindingService implements IKeybindingService { e.preventDefault(); } let commandId = resolveResult.commandId.replace(/^\^/, ''); - this._commandService.executeCommand(commandId, {}).done(undefined, err => { + this._commandService.executeCommand(commandId, resolveResult.commandArgs || {}).done(undefined, err => { this._messageService.show(Severity.Warning, err); }); } diff --git a/src/vs/platform/keybinding/common/keybinding.ts b/src/vs/platform/keybinding/common/keybinding.ts index b80dfedc9b7..5b133882eb9 100644 --- a/src/vs/platform/keybinding/common/keybinding.ts +++ b/src/vs/platform/keybinding/common/keybinding.ts @@ -13,6 +13,7 @@ import Event from 'vs/base/common/event'; export interface IUserFriendlyKeybinding { key: string; command: string; + args?: any; when?: string; } @@ -36,6 +37,7 @@ export interface IKeybindings { export interface IKeybindingItem { keybinding: number; command: string; + commandArgs?: any; when: ContextKeyExpr; weight1: number; weight2: number; diff --git a/src/vs/platform/keybinding/common/keybindingResolver.ts b/src/vs/platform/keybinding/common/keybindingResolver.ts index 1f63e8e33d3..bea2bd18c42 100644 --- a/src/vs/platform/keybinding/common/keybindingResolver.ts +++ b/src/vs/platform/keybinding/common/keybindingResolver.ts @@ -13,6 +13,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; export interface IResolveResult { enterChord: number; commandId: string; + commandArgs: any; } export interface IBoundCommands { @@ -31,11 +32,13 @@ interface ICommandEntry { when: ContextKeyExpr; keybinding: number; commandId: string; + commandArgs: any; } export class NormalizedKeybindingItem { keybinding: number; command: string; + commandArgs: any; when: ContextKeyExpr; isDefault: boolean; actualCommand: string; @@ -45,12 +48,13 @@ export class NormalizedKeybindingItem { if (source.when) { when = source.when.normalize(); } - return new NormalizedKeybindingItem(source.keybinding, source.command, when, isDefault); + return new NormalizedKeybindingItem(source.keybinding, source.command, source.commandArgs, when, isDefault); } - constructor(keybinding: number, command: string, when: ContextKeyExpr, isDefault: boolean) { + constructor(keybinding: number, command: string, commandArgs: any, when: ContextKeyExpr, isDefault: boolean) { this.keybinding = keybinding; this.command = command; + this.commandArgs = commandArgs; this.actualCommand = this.command ? this.command.replace(/^\^/, '') : this.command; this.when = when; this.isDefault = isDefault; @@ -97,7 +101,8 @@ export class KeybindingResolver { let entry: ICommandEntry = { when: k.when, keybinding: k.keybinding, - commandId: k.command + commandId: k.command, + commandArgs: k.commandArgs }; if (BinaryKeybindings.hasChord(k.keybinding)) { @@ -313,13 +318,15 @@ export class KeybindingResolver { if (currentChord === 0 && BinaryKeybindings.hasChord(result.keybinding)) { return { enterChord: keypress, - commandId: null + commandId: null, + commandArgs: null }; } return { enterChord: 0, - commandId: result.commandId + commandId: result.commandId, + commandArgs: result.commandArgs }; } @@ -424,9 +431,15 @@ export class IOSupport { command = input.command; } + let commandArgs: any = null; + if (typeof input.args !== 'undefined') { + commandArgs = input.args; + } + return { keybinding: key, command: command, + commandArgs: commandArgs, when: when, weight1: 1000, weight2: index diff --git a/src/vs/platform/keybinding/common/keybindingsRegistry.ts b/src/vs/platform/keybinding/common/keybindingsRegistry.ts index 72eeecec833..02d64285d52 100644 --- a/src/vs/platform/keybinding/common/keybindingsRegistry.ts +++ b/src/vs/platform/keybinding/common/keybindingsRegistry.ts @@ -113,6 +113,7 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry { this._keybindings.push({ keybinding: keybinding, command: commandId, + commandArgs: null, when: when, weight1: weight1, weight2: weight2 diff --git a/src/vs/platform/keybinding/test/common/keybindingIO.test.ts b/src/vs/platform/keybinding/test/common/keybindingIO.test.ts index e5d746a40e9..5eec3a14f0c 100644 --- a/src/vs/platform/keybinding/test/common/keybindingIO.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingIO.test.ts @@ -136,4 +136,12 @@ suite('Keybinding IO', () => { let normalizedKeybindingItem = NormalizedKeybindingItem.fromKeybindingItem(keybindingItem, false); assert.equal(normalizedKeybindingItem.keybinding, 0); }); + + test('test commands args', () => { + let strJSON = `[{ "key": "ctrl+k ctrl+f", "command": "firstcommand", "when": [], "args": { "text": "theText" } }]`; + let userKeybinding = JSON.parse(strJSON)[0]; + let keybindingItem = IOSupport.readKeybindingItem(userKeybinding, 0); + let normalizedKeybindingItem = NormalizedKeybindingItem.fromKeybindingItem(keybindingItem, false); + assert.equal(normalizedKeybindingItem.commandArgs.text, 'theText'); + }); }); diff --git a/src/vs/platform/keybinding/test/common/keybindingService.test.ts b/src/vs/platform/keybinding/test/common/keybindingService.test.ts index 22e4191d7cd..5b7cd0f0d3b 100644 --- a/src/vs/platform/keybinding/test/common/keybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingService.test.ts @@ -31,6 +31,23 @@ suite('Keybinding Service', () => { assert.equal(resolver.resolve({ bar: 'bz' }, 0, keybinding), null); }); + test('resolve key with arguments', function () { + let commandArgs = { text: 'no' }; + let keybinding = KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z; + let contextRules = ContextKeyExpr.equals('bar', 'baz'); + let keybindingItem: IKeybindingItem = { + command: 'yes', + commandArgs: commandArgs, + when: contextRules, + keybinding: keybinding, + weight1: 0, + weight2: 0 + }; + + let resolver = new KeybindingResolver([keybindingItem], []); + assert.equal(resolver.resolve({ bar: 'baz' }, 0, keybinding).commandArgs, commandArgs); + }); + test('KbAndExpression.equals', function () { let a = ContextKeyExpr.and( ContextKeyExpr.has('a1'), @@ -74,8 +91,8 @@ suite('Keybinding Service', () => { }]; let actual = KeybindingResolver.combine(defaults, overrides); assert.deepEqual(actual, [ - new NormalizedKeybindingItem(KeyCode.KEY_A, 'yes1', ContextKeyExpr.equals('1', 'a'), true), - new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', ContextKeyExpr.equals('2', 'b'), false), + new NormalizedKeybindingItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), + new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), false), ]); }); @@ -102,9 +119,9 @@ suite('Keybinding Service', () => { }]; let actual = KeybindingResolver.combine(defaults, overrides); assert.deepEqual(actual, [ - new NormalizedKeybindingItem(KeyCode.KEY_A, 'yes1', ContextKeyExpr.equals('1', 'a'), true), - new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', ContextKeyExpr.equals('2', 'b'), true), - new NormalizedKeybindingItem(KeyCode.KEY_C, 'yes3', ContextKeyExpr.equals('3', 'c'), false), + new NormalizedKeybindingItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), + new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true), + new NormalizedKeybindingItem(KeyCode.KEY_C, 'yes3', null, ContextKeyExpr.equals('3', 'c'), false), ]); }); @@ -131,8 +148,8 @@ suite('Keybinding Service', () => { }]; let actual = KeybindingResolver.combine(defaults, overrides); assert.deepEqual(actual, [ - new NormalizedKeybindingItem(KeyCode.KEY_A, 'yes1', ContextKeyExpr.equals('1', 'a'), true), - new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', ContextKeyExpr.equals('2', 'b'), true) + new NormalizedKeybindingItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), + new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) ]); }); @@ -159,8 +176,8 @@ suite('Keybinding Service', () => { }]; let actual = KeybindingResolver.combine(defaults, overrides); assert.deepEqual(actual, [ - new NormalizedKeybindingItem(KeyCode.KEY_A, 'yes1', ContextKeyExpr.equals('1', 'a'), true), - new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', ContextKeyExpr.equals('2', 'b'), true) + new NormalizedKeybindingItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), + new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) ]); }); @@ -187,7 +204,7 @@ suite('Keybinding Service', () => { }]; let actual = KeybindingResolver.combine(defaults, overrides); assert.deepEqual(actual, [ - new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', ContextKeyExpr.equals('2', 'b'), true) + new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) ]); }); @@ -214,7 +231,7 @@ suite('Keybinding Service', () => { }]; let actual = KeybindingResolver.combine(defaults, overrides); assert.deepEqual(actual, [ - new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', ContextKeyExpr.equals('2', 'b'), true) + new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) ]); }); @@ -241,7 +258,7 @@ suite('Keybinding Service', () => { }]; let actual = KeybindingResolver.combine(defaults, overrides); assert.deepEqual(actual, [ - new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', ContextKeyExpr.equals('2', 'b'), true) + new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) ]); }); @@ -268,7 +285,7 @@ suite('Keybinding Service', () => { }]; let actual = KeybindingResolver.combine(defaults, overrides); assert.deepEqual(actual, [ - new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', ContextKeyExpr.equals('2', 'b'), true) + new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) ]); }); @@ -295,7 +312,7 @@ suite('Keybinding Service', () => { }]; let actual = KeybindingResolver.combine(defaults, overrides); assert.deepEqual(actual, [ - new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', ContextKeyExpr.equals('2', 'b'), true) + new NormalizedKeybindingItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) ]); }); @@ -471,8 +488,6 @@ suite('Keybinding Service', () => { let resolver = new KeybindingResolver(items, [], false); - - let testKey = (commandId: string, expectedKeys: number[]) => { // Test lookup let lookupResult = resolver.lookupKeybinding(commandId); diff --git a/src/vs/platform/url/common/urlIpc.ts b/src/vs/platform/url/common/urlIpc.ts index 95e3a657b96..52cb4b8d386 100644 --- a/src/vs/platform/url/common/urlIpc.ts +++ b/src/vs/platform/url/common/urlIpc.ts @@ -9,7 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel, eventToCall, eventFromCall, Serializer, Deserializer } from 'vs/base/parts/ipc/common/ipc'; import { IURLService } from './url'; import Event, { filterEvent } from 'vs/base/common/event'; -import { IWindowsService } from 'vs/code/electron-main/windows'; +import { IWindowsMainService } from 'vs/code/electron-main/windows'; import URI from 'vs/base/common/uri'; const URISerializer: Serializer = uri => uri.toJSON(); @@ -24,7 +24,7 @@ export class URLChannel implements IURLChannel { constructor( private service: IURLService, - @IWindowsService private windowsService: IWindowsService + @IWindowsMainService private windowsService: IWindowsMainService ) { } call(command: string, arg?: any): TPromise { diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts new file mode 100644 index 00000000000..9e81bbfecca --- /dev/null +++ b/src/vs/platform/windows/common/windows.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IWindowsService = createDecorator('windowsService'); + +export interface IWindowsService { + + _serviceBrand: any; + + openFileFolderPicker(windowId: number, forceNewWindow?: boolean): TPromise; + openFilePicker(windowId: number, forceNewWindow?: boolean, path?: string): TPromise; + openFolderPicker(windowId: number, forceNewWindow?: boolean): TPromise; + reloadWindow(windowId: number): TPromise; +} + +export const IWindowService = createDecorator('windowService'); + +export interface IWindowService { + + _serviceBrand: any; + + openFileFolderPicker(forceNewWindow?: boolean): TPromise; + openFilePicker(forceNewWindow?: boolean, path?: string): TPromise; + openFolderPicker(forceNewWindow?: boolean): TPromise; + reloadWindow(): TPromise; +} \ No newline at end of file diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts new file mode 100644 index 00000000000..a83ac7f6783 --- /dev/null +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IWindowsService } from './windows'; + +export interface IWindowsChannel extends IChannel { + call(command: 'openFileFolderPicker', args: [number, boolean]): TPromise; + call(command: 'openFilePicker', args: [number, boolean, string]): TPromise; + call(command: 'openFolderPicker', args: [number, boolean]): TPromise; + call(command: 'reloadWindow', arg: number): TPromise; + call(command: string, arg?: any): TPromise; +} + +export class WindowsChannel implements IWindowsChannel { + + constructor(private service: IWindowsService) { } + + call(command: string, arg?: any): TPromise { + switch (command) { + case 'openFileFolderPicker': return this.service.openFileFolderPicker(arg[0], arg[1]); + case 'openFilePicker': return this.service.openFilePicker(arg[0], arg[1], arg[2]); + case 'openFolderPicker': return this.service.openFolderPicker(arg[0], arg[1]); + case 'reloadWindow': return this.service.reloadWindow(arg); + } + } +} + +export class WindowsChannelClient implements IWindowsService { + + _serviceBrand: any; + + constructor(private channel: IWindowsChannel) { } + + openFileFolderPicker(windowId: number, forceNewWindow?: boolean): TPromise { + return this.channel.call('openFileFolderPicker', [windowId, forceNewWindow]); + } + + openFilePicker(windowId: number, forceNewWindow?: boolean, path?: string): TPromise { + return this.channel.call('openFilePicker', [windowId, forceNewWindow, path]); + } + + openFolderPicker(windowId: number, forceNewWindow?: boolean): TPromise { + return this.channel.call('openFolderPicker', [windowId, forceNewWindow]); + } + + reloadWindow(windowId: number): TPromise { + return this.channel.call('reloadWindow', windowId); + } +} \ No newline at end of file diff --git a/src/vs/platform/windows/electron-browser/windowService.ts b/src/vs/platform/windows/electron-browser/windowService.ts new file mode 100644 index 00000000000..aee56786b0e --- /dev/null +++ b/src/vs/platform/windows/electron-browser/windowService.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; + +export class WindowService implements IWindowService { + + _serviceBrand: any; + + constructor( + private windowId: number, + @IWindowsService private windowsService: IWindowsService + ) { } + + openFileFolderPicker(forceNewWindow?: boolean): TPromise { + return this.windowsService.openFileFolderPicker(this.windowId, forceNewWindow); + } + + openFilePicker(forceNewWindow?: boolean, path?: string): TPromise { + return this.windowsService.openFilePicker(this.windowId, forceNewWindow, path); + } + + openFolderPicker(forceNewWindow?: boolean): TPromise { + return this.windowsService.openFolderPicker(this.windowId, forceNewWindow); + } + + reloadWindow(): TPromise { + return this.windowsService.reloadWindow(this.windowId); + } +} \ No newline at end of file diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts new file mode 100644 index 00000000000..72407812187 --- /dev/null +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IWindowsService } from 'vs/platform/windows/common/windows'; + +// TODO@Joao: remove this dependency, move all implementation to this class +import { IWindowsMainService } from 'vs/code/electron-main/windows'; + +export class WindowsService implements IWindowsService { + + _serviceBrand: any; + + constructor( + @IWindowsMainService private windowsMainService: IWindowsMainService + ) { } + + openFileFolderPicker(windowId: number, forceNewWindow?: boolean): TPromise { + this.windowsMainService.openFileFolderPicker(forceNewWindow); + return TPromise.as(null); + } + + openFilePicker(windowId: number, forceNewWindow?: boolean, path?: string): TPromise { + this.windowsMainService.openFilePicker(forceNewWindow, path); + return TPromise.as(null); + } + + openFolderPicker(windowId: number, forceNewWindow?: boolean): TPromise { + this.windowsMainService.openFolderPicker(forceNewWindow); + return TPromise.as(null); + } + + reloadWindow(windowId: number): TPromise { + const vscodeWindow = this.windowsMainService.getWindowById(windowId); + + if (vscodeWindow) { + this.windowsMainService.reload(vscodeWindow); + } + + return TPromise.as(null); + } +} \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index f7b8eea2f56..8ef3e391451 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -47,11 +47,11 @@ import * as languageConfiguration from 'vs/editor/common/modes/languageConfigura export interface IExtensionApiFactory { - (extension: IExtensionDescription): typeof vscode; + (extension?: IExtensionDescription): typeof vscode; } function proposedApiFunction(extension: IExtensionDescription, fn: T): T { - if (extension.enableProposedApi) { + if (extension && extension.enableProposedApi) { return fn; } else { return (() => { @@ -92,9 +92,9 @@ export function createApiFactory(initDataConfiguration: IInitConfiguration, init // Register API-ish commands ExtHostApiCommands.register(extHostCommands); - return function (extension: IExtensionDescription): typeof vscode { + return function (extension?: IExtensionDescription): typeof vscode { - if (extension.enableProposedApi) { + if (extension && extension.enableProposedApi) { console.warn(`${extension.name} (${extension.id}) uses PROPOSED API which is subject to change and removal without notice`); } @@ -435,7 +435,7 @@ export function defineAPI(factory: IExtensionApiFactory, extensionService: ExtHo const trie = new TrieMap(TrieMap.PathSplitter); const extensions = extensionService.getAllExtensionDescriptions(); for (const ext of extensions) { - if (ext.name) { + if (ext.main) { const path = realpathSync(ext.extensionFolderPath); trie.insert(path, ext); } diff --git a/src/vs/workbench/api/node/extHostApiCommands.ts b/src/vs/workbench/api/node/extHostApiCommands.ts index 56796b4f7c0..c270f87afd0 100644 --- a/src/vs/workbench/api/node/extHostApiCommands.ts +++ b/src/vs/workbench/api/node/extHostApiCommands.ts @@ -183,12 +183,12 @@ export class ExtHostApiCommands { ] }); - this._register('vscode.openFolder', (uri?: URI, newWindow?: boolean) => { + this._register('vscode.openFolder', (uri?: URI, forceNewWindow?: boolean) => { if (!uri) { - return this._commands.executeCommand('_workbench.ipc', 'vscode:openFolderPicker', [newWindow]); + return this._commands.executeCommand('_files.openFolderPicker', forceNewWindow); } - return this._commands.executeCommand('_workbench.ipc', 'vscode:windowOpen', [[uri.fsPath], newWindow]); + return this._commands.executeCommand('_workbench.ipc', 'vscode:windowOpen', [[uri.fsPath], forceNewWindow]); }, { description: 'Open a folder in the current window or new window depending on the newWindow argument. Note that opening in the same window will shutdown the current extension host process and start a new one on the given folder unless the newWindow parameter is set to true.', args: [ diff --git a/src/vs/workbench/api/node/mainThreadExtensionService.ts b/src/vs/workbench/api/node/mainThreadExtensionService.ts index 2521594f6fe..c1039bcd5ed 100644 --- a/src/vs/workbench/api/node/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/node/mainThreadExtensionService.ts @@ -11,7 +11,8 @@ import { localize } from 'vs/nls'; import * as path from 'path'; import URI from 'vs/base/common/uri'; import { AbstractExtensionService, ActivatedExtension } from 'vs/platform/extensions/common/abstractExtensionService'; -import { IExtensionRuntimeService, IMessage, IExtensionDescription, IExtensionsStatus } from 'vs/platform/extensions/common/extensions'; +import { IMessage, IExtensionDescription, IExtensionsStatus } from 'vs/platform/extensions/common/extensions'; +import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionsRegistry, ExtensionPoint, IExtensionPointUser, ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry'; import { ExtensionScanner, MessagesCollector } from 'vs/workbench/node/extensionPoints'; import { IMessageService } from 'vs/platform/message/common/message'; @@ -61,7 +62,7 @@ export class MainProcessExtensionService extends AbstractExtensionService { this._onExtensionDescriptions(disabledExtensions.length ? extensionDescriptions.filter(e => disabledExtensions.indexOf(`${e.publisher}.${e.name}`) === -1) : extensionDescriptions); }); diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledEditorInput.ts index 4aa1218678e..2527da20261 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledEditorInput.ts @@ -83,7 +83,11 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { } public isDirty(): boolean { - return this.cachedModel && this.cachedModel.isDirty(); + if (this.cachedModel) { + return this.cachedModel.isDirty(); + } + + return this.hasAssociatedFilePath; // untitled files with associated path are always dirty } public confirmSave(): ConfirmResult { diff --git a/src/vs/workbench/common/editor/untitledEditorModel.ts b/src/vs/workbench/common/editor/untitledEditorModel.ts index d1f1eccc43b..a6f8060767d 100644 --- a/src/vs/workbench/common/editor/untitledEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledEditorModel.ts @@ -144,13 +144,6 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS // Listen to content changes this.textModelChangeListener = this.textEditorModel.onDidChangeContent(e => this.onModelContentChanged()); - // Emit initial dirty event if we are - if (this.dirty) { - setTimeout(() => { - this._onDidChangeDirty.fire(); - }, 0 /* prevent race condition between creating model and emitting dirty event */); - } - return model; }); } diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index 2dd461675f8..c37f2a181ea 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -9,7 +9,8 @@ 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 { IWindowService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; @@ -66,7 +67,7 @@ export class CloseWindowAction extends Action { public static ID = 'workbench.action.closeWindow'; public static LABEL = nls.localize('closeWindow', "Close Window"); - constructor(id: string, label: string, @IWindowService private windowService: IWindowService) { + constructor(id: string, label: string, @IWindowIPCService private windowService: IWindowIPCService) { super(id, label); } @@ -85,7 +86,7 @@ export class SwitchWindow extends Action { constructor( id: string, label: string, - @IWindowService private windowService: IWindowService, + @IWindowIPCService private windowService: IWindowIPCService, @IQuickOpenService private quickOpenService: IQuickOpenService ) { super(id, label); @@ -121,7 +122,7 @@ export class CloseFolderAction extends Action { label: string, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IMessageService private messageService: IMessageService, - @IWindowService private windowService: IWindowService + @IWindowIPCService private windowService: IWindowIPCService ) { super(id, label); } @@ -145,7 +146,7 @@ export class NewWindowAction extends Action { constructor( id: string, label: string, - @IWindowService private windowService: IWindowService + @IWindowIPCService private windowService: IWindowIPCService ) { super(id, label); } @@ -162,7 +163,7 @@ export class ToggleFullScreenAction extends Action { public static ID = 'workbench.action.toggleFullScreen'; public static LABEL = nls.localize('toggleFullScreen', "Toggle Full Screen"); - constructor(id: string, label: string, @IWindowService private windowService: IWindowService) { + constructor(id: string, label: string, @IWindowIPCService private windowService: IWindowIPCService) { super(id, label); } @@ -178,7 +179,7 @@ export class ToggleMenuBarAction extends Action { public static ID = 'workbench.action.toggleMenuBar'; public static LABEL = nls.localize('toggleMenuBar', "Toggle Menu Bar"); - constructor(id: string, label: string, @IWindowService private windowService: IWindowService) { + constructor(id: string, label: string, @IWindowIPCService private windowService: IWindowIPCService) { super(id, label); } @@ -194,7 +195,7 @@ export class ToggleDevToolsAction extends Action { public static ID = 'workbench.action.toggleDevTools'; public static LABEL = nls.localize('toggleDevTools', "Toggle Developer Tools"); - constructor(id: string, label: string, @IWindowService private windowService: IWindowService) { + constructor(id: string, label: string, @IWindowIPCService private windowService: IWindowIPCService) { super(id, label); } @@ -329,7 +330,7 @@ export class ShowStartupPerformance extends Action { constructor( id: string, label: string, - @IWindowService private windowService: IWindowService, + @IWindowIPCService private windowService: IWindowIPCService, @IEnvironmentService environmentService: IEnvironmentService ) { super(id, label); @@ -420,8 +421,8 @@ export class ShowStartupPerformance extends Action { export class ReloadWindowAction extends Action { - public static ID = 'workbench.action.reloadWindow'; - public static LABEL = nls.localize('reloadWindow', "Reload Window"); + static ID = 'workbench.action.reloadWindow'; + static LABEL = nls.localize('reloadWindow', "Reload Window"); constructor( id: string, @@ -432,11 +433,9 @@ export class ReloadWindowAction extends Action { super(id, label); } - public run(): TPromise { + run(): TPromise { this.partService.setRestoreSidebar(); // we want the same sidebar after a reload restored - this.windowService.getWindow().reload(); - - return TPromise.as(true); + return this.windowService.reloadWindow().then(() => true); } } @@ -448,7 +447,7 @@ export class OpenRecentAction extends Action { constructor( id: string, label: string, - @IWindowService private windowService: IWindowService, + @IWindowIPCService private windowService: IWindowIPCService, @IQuickOpenService private quickOpenService: IQuickOpenService, @IWorkspaceContextService private contextService: IWorkspaceContextService ) { diff --git a/src/vs/workbench/electron-browser/bootstrap/index.js b/src/vs/workbench/electron-browser/bootstrap/index.js index 9595b257339..7c66ad35cf6 100644 --- a/src/vs/workbench/electron-browser/bootstrap/index.js +++ b/src/vs/workbench/electron-browser/bootstrap/index.js @@ -81,8 +81,9 @@ function registerListeners(enableDeveloperTools) { const key = extractKey(e); if (key === TOGGLE_DEV_TOOLS_KB) { ipc.send('vscode:toggleDevTools', windowId); + // remote.getCurrentWebContents().toggleDevTools(); } else if (key === RELOAD_KB) { - ipc.send('vscode:reloadWindow', windowId); + remote.getCurrentWindow().reload(); } }); } diff --git a/src/vs/workbench/electron-browser/extensionHost.ts b/src/vs/workbench/electron-browser/extensionHost.ts index 0fab91fa5c4..d48a8687bc8 100644 --- a/src/vs/workbench/electron-browser/extensionHost.ts +++ b/src/vs/workbench/electron-browser/extensionHost.ts @@ -17,7 +17,7 @@ import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { ILifecycleService, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWindowService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; import { ChildProcess, fork } from 'child_process'; import { ipcRenderer as ipc } from 'electron'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -25,6 +25,7 @@ import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import Event, { Emitter } from 'vs/base/common/event'; +import { WatchDog } from 'vs/base/common/watchDog'; import { createQueuedSender, IQueuedSender } from 'vs/base/node/processes'; import { IInitData, IInitConfiguration } from 'vs/workbench/api/node/extHost.protocol'; import { MainProcessExtensionService } from 'vs/workbench/api/node/mainThreadExtensionService'; @@ -55,6 +56,8 @@ export class ExtensionHostProcessWorker { private isExtensionDevelopmentTestFromCli: boolean; private isExtensionDevelopmentDebugging: boolean; + private extHostWatchDog = new WatchDog(250, 4); + private _onMessage = new Emitter(); public get onMessage(): Event { return this._onMessage.event; @@ -65,7 +68,7 @@ export class ExtensionHostProcessWorker { constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService, @IMessageService private messageService: IMessageService, - @IWindowService private windowService: IWindowService, + @IWindowIPCService private windowService: IWindowIPCService, @ILifecycleService lifecycleService: ILifecycleService, @IInstantiationService private instantiationService: IInstantiationService, @IEnvironmentService private environmentService: IEnvironmentService, @@ -110,6 +113,29 @@ export class ExtensionHostProcessWorker { // Initialize extension host process with hand shakes this.initializeExtensionHostProcess = this.doInitializeExtensionHostProcess(opts); + + // Check how well the extension host is doing + if (this.environmentService.isBuilt) { + this.initializeExtensionHostProcess.done(() => { + this.extHostWatchDog.start(); + this.extHostWatchDog.onAlert(() => { + + this.extHostWatchDog.stop(); + + // log the identifiers of those extensions that + // have code and are loaded in the extension host + this.extensionService.getExtensions().then(extensions => { + const ids: string[] = []; + for (const ext of extensions) { + if (ext.main) { + ids.push(ext.id); + } + } + this.telemetryService.publicLog('extHostUnresponsive', ids); + }); + }); + }); + } } public get messagingProtocol(): IMessagePassingProtocol { @@ -191,6 +217,12 @@ export class ExtensionHostProcessWorker { return true; } + // Heartbeat message + if (msg === '__$heartbeat') { + this.extHostWatchDog.reset(); + return false; + } + // Support logging from extension host if (msg && (msg).type === '__$console') { this.logExtensionHostMessage(msg); @@ -339,4 +371,4 @@ export class ExtensionHostProcessWorker { event.veto(TPromise.timeout(100 /* wait a bit for IPC to get delivered */).then(() => false)); } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/electron-browser/integration.ts b/src/vs/workbench/electron-browser/integration.ts index 6718cac9b89..ed76f6de9de 100644 --- a/src/vs/workbench/electron-browser/integration.ts +++ b/src/vs/workbench/electron-browser/integration.ts @@ -21,7 +21,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ICommandService } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IWindowService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; import { AutoSaveConfiguration } from 'vs/platform/files/common/files'; import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; @@ -58,7 +58,7 @@ export class ElectronIntegration { constructor( @IInstantiationService private instantiationService: IInstantiationService, - @IWindowService private windowService: IWindowService, + @IWindowIPCService private windowService: IWindowIPCService, @IPartService private partService: IPartService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @ITelemetryService private telemetryService: ITelemetryService, diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index afc8ea109be..cc6c348b772 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -15,7 +15,7 @@ import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import platform = require('vs/base/common/platform'); import { IKeybindings } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IWindowService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; import { CloseEditorAction, ReloadWindowAction, ShowStartupPerformance, ReportIssueAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleDevToolsAction, ToggleFullScreenAction, ToggleMenuBarAction, OpenRecentAction, CloseFolderAction, CloseWindowAction, SwitchWindow, NewWindowAction, CloseMessagesAction } from 'vs/workbench/electron-browser/actions'; import { MessagesVisibleContext, NoEditorsVisibleContext } from 'vs/workbench/electron-browser/workbench'; @@ -61,7 +61,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: NoEditorsVisibleContext, primary: closeEditorOrWindowKeybindings.primary, handler: accessor => { - const windowService = accessor.get(IWindowService); + const windowService = accessor.get(IWindowIPCService); windowService.getWindow().close(); } }); @@ -151,6 +151,11 @@ configurationRegistry.registerConfiguration({ 'type': 'number', 'default': 0, 'description': nls.localize('zoomLevel', "Adjust the zoom level of the window. The original size is 0 and each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity.") + }, + 'window.showFullPath': { + 'type': 'boolean', + 'default': false, + 'description': nls.localize('showFullPath', "If enabled, will show the full path of opened files in the window title.") } } }); \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index accdd15b4ab..2028336d7f5 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -30,7 +30,10 @@ import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/workbenchCommonProperties'; import { ElectronIntegration } from 'vs/workbench/electron-browser/integration'; import { WorkspaceStats } from 'vs/workbench/services/telemetry/common/workspaceStats'; -import { IWindowService, WindowService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowIPCService, WindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; +import { WindowsChannelClient } from 'vs/platform/windows/common/windowsIpc'; +import { WindowService } from 'vs/platform/windows/electron-browser/windowService'; import { MessageService } from 'vs/workbench/services/message/electron-browser/messageService'; import { IRequestService } from 'vs/platform/request/common/request'; import { RequestService } from 'vs/platform/request/node/requestService'; @@ -50,6 +53,7 @@ import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerServ import { MainProcessExtensionService } from 'vs/workbench/api/node/mainThreadExtensionService'; import { IOptions } from 'vs/workbench/common/options'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; @@ -64,7 +68,7 @@ import { IThreadService } from 'vs/workbench/services/thread/common/threadServic import { ICommandService } from 'vs/platform/commands/common/commands'; import { CommandService } from 'vs/platform/commands/common/commandService'; import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace'; -import { IExtensionService, IExtensionRuntimeService } from 'vs/platform/extensions/common/extensions'; +import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { MainThreadModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; @@ -75,13 +79,13 @@ import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; import { Client as ElectronIPCClient } from 'vs/base/parts/ipc/electron-browser/ipc.electron-browser'; import { IExtensionManagementChannel, ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; -import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; import { URLChannelClient } from 'vs/platform/url/common/urlIpc'; import { IURLService } from 'vs/platform/url/common/url'; import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions'; import { WorkspaceConfigurationService } from 'vs/workbench/services/configuration/node/configurationService'; import { ExtensionHostProcessWorker } from 'vs/workbench/electron-browser/extensionHost'; -import { ExtensionRuntimeService } from 'vs/workbench/services/extensions/common/extensionRuntimeService'; import { remote } from 'electron'; // self registering services @@ -107,7 +111,6 @@ export class WorkbenchShell { private eventService: IEventService; private environmentService: IEnvironmentService; private contextViewService: ContextViewService; - private windowService: IWindowService; private threadService: MainThreadService; private configurationService: IConfigurationService; private themeService: ThemeService; @@ -221,12 +224,21 @@ export class WorkbenchShell { serviceCollection.set(IConfigurationService, this.configurationService); serviceCollection.set(IEnvironmentService, this.environmentService); - const instantiationService = new InstantiationService(serviceCollection, true); + const instantiationServiceImpl = new InstantiationService(serviceCollection, true); + const instantiationService = instantiationServiceImpl as IInstantiationService; - this.windowService = instantiationService.createInstance(WindowService); - serviceCollection.set(IWindowService, this.windowService); + // TODO@joao remove this + const windowIPCService = instantiationService.createInstance(WindowIPCService); + serviceCollection.set(IWindowIPCService, windowIPCService); - const sharedProcess = connectNet(this.environmentService.sharedIPCHandle, `window:${this.windowService.getWindowId()}`); + const windowsChannel = mainProcessClient.getChannel('windows'); + const windowsChannelClient = new WindowsChannelClient(windowsChannel); + serviceCollection.set(IWindowsService, windowsChannelClient); + + const windowService = new WindowService(windowIPCService.getWindowId(), windowsChannelClient); + serviceCollection.set(IWindowService, windowService); + + const sharedProcess = connectNet(this.environmentService.sharedIPCHandle, `window:${windowIPCService.getWindowId()}`); sharedProcess.done(client => { client.registerChannel('choice', new ChoiceChannel(this.messageService)); @@ -292,9 +304,9 @@ export class WorkbenchShell { const extensionManagementChannelClient = new ExtensionManagementChannelClient(extensionManagementChannel); serviceCollection.set(IExtensionManagementService, extensionManagementChannelClient); - const extensionsRuntimeService = instantiationService.createInstance(ExtensionRuntimeService); - serviceCollection.set(IExtensionRuntimeService, extensionsRuntimeService); - disposables.add(extensionsRuntimeService); + const extensionEnablementService = instantiationService.createInstance(ExtensionEnablementService); + serviceCollection.set(IExtensionEnablementService, extensionEnablementService); + disposables.add(extensionEnablementService); const extensionHostProcessWorker = instantiationService.createInstance(ExtensionHostProcessWorker); this.threadService = instantiationService.createInstance(MainThreadService, extensionHostProcessWorker.messagingProtocol); @@ -333,17 +345,17 @@ export class WorkbenchShell { const searchService = instantiationService.createInstance(SearchService); serviceCollection.set(ISearchService, searchService); - const codeEditorService = instantiationService.createInstance(CodeEditorServiceImpl); + const codeEditorService = instantiationServiceImpl.createInstance(CodeEditorServiceImpl); serviceCollection.set(ICodeEditorService, codeEditorService); const integrityService = instantiationService.createInstance(IntegrityServiceImpl); serviceCollection.set(IIntegrityService, integrityService); const urlChannel = mainProcessClient.getChannel('url'); - const urlChannelClient = new URLChannelClient(urlChannel, this.windowService.getWindowId()); + const urlChannelClient = new URLChannelClient(urlChannel, windowIPCService.getWindowId()); serviceCollection.set(IURLService, urlChannelClient); - return [instantiationService, serviceCollection]; + return [instantiationServiceImpl, serviceCollection]; } public open(): void { diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index fc6f72def1f..13d0d826f5d 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -149,6 +149,7 @@ export class ElectronWindow { } public reload(): void { + this.partService.setRestoreSidebar(); // we want the same sidebar after a reload restored ipc.send('vscode:reloadWindow', this.windowId); } diff --git a/src/vs/workbench/node/extensionHostProcess.ts b/src/vs/workbench/node/extensionHostProcess.ts index 726c7231287..19a9b81ef78 100644 --- a/src/vs/workbench/node/extensionHostProcess.ts +++ b/src/vs/workbench/node/extensionHostProcess.ts @@ -106,6 +106,12 @@ function connectToRenderer(): TPromise { stats.length = 0; }, 1000); + + // Send heartbeat + setInterval(function () { + queuedSender.send('__$heartbeat'); + }, 250); + // Tell the outside that we are initialized queuedSender.send('initialized'); @@ -121,4 +127,4 @@ connectToRenderer().then(renderer => { const extensionHostMain = new ExtensionHostMain(renderer.remoteCom, renderer.initData); onTerminate = () => extensionHostMain.terminate(); return extensionHostMain.start(); -}).done(null, err => console.error(err)); \ No newline at end of file +}).done(null, err => console.error(err)); diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index f4151de8993..02615345c64 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -106,59 +106,50 @@ export abstract class ExpressionContainer implements debug.IExpressionContainer private static BASE_CHUNK_SIZE = 100; public valueChanged: boolean; - private children: TPromise; private _value: string; constructor( public stackFrame: debug.IStackFrame, public reference: number, private id: string, - private cacheChildren: boolean, public namedVariables: number, public indexedVariables: number, private startOfVariables = 0 - ) { - // noop - } + ) { } public getChildren(): TPromise { - if (!this.cacheChildren || !this.children) { - // only variables with reference > 0 have children. - if (this.reference <= 0) { - this.children = TPromise.as([]); - } else { - if (!this.getChildrenInChunks) { - return this.fetchVariables(undefined, undefined, undefined); - } - - // Check if object has named variables, fetch them independent from indexed variables #9670 - this.children = (!!this.namedVariables ? this.fetchVariables(undefined, undefined, 'named') - : TPromise.as([])).then(childrenArray => { - // Use a dynamic chunk size based on the number of elements #9774 - let chunkSize = ExpressionContainer.BASE_CHUNK_SIZE; - while (this.indexedVariables > chunkSize * ExpressionContainer.BASE_CHUNK_SIZE) { - chunkSize *= ExpressionContainer.BASE_CHUNK_SIZE; - } - - if (this.indexedVariables > chunkSize) { - // There are a lot of children, create fake intermediate values that represent chunks #9537 - const numberOfChunks = Math.ceil(this.indexedVariables / chunkSize); - for (let i = 0; i < numberOfChunks; i++) { - const start = this.startOfVariables + i * chunkSize; - const count = Math.min(chunkSize, this.indexedVariables - i * chunkSize); - childrenArray.push(new Variable(this.stackFrame, this, this.reference, `[${start}..${start + count - 1}]`, '', '', null, count, null, true, start)); - } - - return childrenArray; - } - - return this.fetchVariables(this.startOfVariables, this.indexedVariables, 'indexed') - .then(variables => childrenArray.concat(variables)); - }); - } + // only variables with reference > 0 have children. + if (this.reference <= 0) { + return TPromise.as([]); } - return this.children; + if (!this.getChildrenInChunks) { + return this.fetchVariables(undefined, undefined, undefined); + } + + // Check if object has named variables, fetch them independent from indexed variables #9670 + return (!!this.namedVariables ? this.fetchVariables(undefined, undefined, 'named') : TPromise.as([])).then(childrenArray => { + // Use a dynamic chunk size based on the number of elements #9774 + let chunkSize = ExpressionContainer.BASE_CHUNK_SIZE; + while (this.indexedVariables > chunkSize * ExpressionContainer.BASE_CHUNK_SIZE) { + chunkSize *= ExpressionContainer.BASE_CHUNK_SIZE; + } + + if (this.indexedVariables > chunkSize) { + // There are a lot of children, create fake intermediate values that represent chunks #9537 + const numberOfChunks = Math.ceil(this.indexedVariables / chunkSize); + for (let i = 0; i < numberOfChunks; i++) { + const start = this.startOfVariables + i * chunkSize; + const count = Math.min(chunkSize, this.indexedVariables - i * chunkSize); + childrenArray.push(new Variable(this.stackFrame, this, this.reference, `[${start}..${start + count - 1}]`, '', '', null, count, null, true, start)); + } + + return childrenArray; + } + + return this.fetchVariables(this.startOfVariables, this.indexedVariables, 'indexed') + .then(variables => childrenArray.concat(variables)); + }); } public getId(): string { @@ -205,10 +196,14 @@ export class Expression extends ExpressionContainer implements debug.IExpression public available: boolean; public type: string; - constructor(public name: string, cacheChildren: boolean, id = generateUuid()) { - super(null, 0, id, cacheChildren, 0, 0); - this.value = Expression.DEFAULT_VALUE; + constructor(public name: string, id = generateUuid()) { + super(null, 0, id, 0, 0); this.available = false; + // name is not set if the expression is just being added + // in that case do not set default value to prevent flashing #14499 + if (name) { + this.value = Expression.DEFAULT_VALUE; + } } public evaluate(process: debug.IProcess, stackFrame: debug.IStackFrame, context: string): TPromise { @@ -265,7 +260,7 @@ export class Variable extends ExpressionContainer implements debug.IExpression { public available = true, startOfVariables = 0 ) { - super(stackFrame, reference, `variable:${parent.getId()}:${name}:${reference}`, true, namedVariables, indexedVariables, startOfVariables); + super(stackFrame, reference, `variable:${parent.getId()}:${name}:${reference}`, namedVariables, indexedVariables, startOfVariables); this.value = massageValue(value); } @@ -327,7 +322,7 @@ export class Scope extends ExpressionContainer implements debug.IScope { namedVariables: number, indexedVariables: number ) { - super(stackFrame, reference, `scope:${stackFrame.getId()}:${name}:${reference}`, true, namedVariables, indexedVariables); + super(stackFrame, reference, `scope:${stackFrame.getId()}:${name}:${reference}`, namedVariables, indexedVariables); } } @@ -817,7 +812,7 @@ export class Model implements debug.IModel { } public addReplExpression(process: debug.IProcess, stackFrame: debug.IStackFrame, name: string): TPromise { - const expression = new Expression(name, true); + const expression = new Expression(name); this.addReplElements([expression]); return expression.evaluate(process, stackFrame, 'repl') .then(() => this._onDidChangeREPLElements.fire()); @@ -892,7 +887,7 @@ export class Model implements debug.IModel { } public addWatchExpression(process: debug.IProcess, stackFrame: debug.IStackFrame, name: string): TPromise { - const we = new Expression(name, false); + const we = new Expression(name); this.watchExpressions.push(we); if (!name) { this._onDidChangeWatchExpressions.fire(we); diff --git a/src/vs/workbench/parts/debug/electron-browser/debugHover.ts b/src/vs/workbench/parts/debug/electron-browser/debugHover.ts index a731609d321..79390175568 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugHover.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugHover.ts @@ -160,7 +160,7 @@ export class DebugHoverWidget implements editorbrowser.IContentWidget { const matchingExpression = lineContent.substring(expressionRange.startColumn - 1, expressionRange.endColumn); let promise: TPromise; if (process.session.configuration.capabilities.supportsEvaluateForHovers) { - const result = new Expression(matchingExpression, true); + const result = new Expression(matchingExpression); promise = result.evaluate(process, focusedStackFrame, 'hover').then(() => result); } else { promise = this.findExpressionInStackFrame(matchingExpression.split('.').map(word => word.trim()).filter(word => !!word)); diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index 895d17d7e28..be06e7a9d4d 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -52,7 +52,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IWindowService, IBroadcast } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowIPCService, IBroadcast } from 'vs/workbench/services/window/electron-browser/windowService'; import { ILogEntry, EXTENSION_LOG_BROADCAST_CHANNEL, EXTENSION_ATTACH_BROADCAST_CHANNEL, EXTENSION_TERMINATE_BROADCAST_CHANNEL } from 'vs/workbench/electron-browser/extensionHost'; import { ipcRenderer as ipc } from 'electron'; @@ -87,7 +87,7 @@ export class DebugService implements debug.IDebugService { @IFileService private fileService: IFileService, @IMessageService private messageService: IMessageService, @IPartService private partService: IPartService, - @IWindowService private windowService: IWindowService, + @IWindowIPCService private windowService: IWindowIPCService, @ITelemetryService private telemetryService: ITelemetryService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IContextKeyService contextKeyService: IContextKeyService, @@ -402,7 +402,7 @@ export class DebugService implements debug.IDebugService { let result: Expression[]; try { result = JSON.parse(this.storageService.get(DEBUG_WATCH_EXPRESSIONS_KEY, StorageScope.WORKSPACE, '[]')).map((watchStoredData: { name: string, id: string }) => { - return new Expression(watchStoredData.name, false, watchStoredData.id); + return new Expression(watchStoredData.name, watchStoredData.id); }); } catch (e) { } @@ -641,7 +641,8 @@ export class DebugService implements debug.IDebugService { const process = this.model.addProcess(configuration.name, session); if (!this.viewModel.focusedProcess) { - this.setFocusedStackFrameAndEvaluate(null, process); + this.viewModel.setFocusedStackFrame(null, process); + this._onDidChangeState.fire(); } this.toDisposeOnSessionEnd[session.getId()] = []; if (client) { diff --git a/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts b/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts index 81f99847f38..f179a4825f1 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts @@ -56,7 +56,7 @@ export function renderExpressionValue(expressionOrValue: debug.IExpression | str dom.addClass(container, 'string'); } - if (showChanged && (expressionOrValue).valueChanged) { + if (showChanged && (expressionOrValue).valueChanged && value !== Expression.DEFAULT_VALUE) { // value changed color has priority over other colors. container.className = 'value changed'; } @@ -502,7 +502,7 @@ export class CallStackRenderer implements IRenderer { } private renderProcess(process: debug.IProcess, data: IProcessTemplateData): void { - data.process.title = nls.localize('process', "Process"); + data.process.title = nls.localize({ key: 'process', comment: ['Process is a noun'] }, "Process"); data.name.textContent = process.name; } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugViews.ts b/src/vs/workbench/parts/debug/electron-browser/debugViews.ts index 0296c400ada..5cd54d20588 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugViews.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugViews.ts @@ -177,7 +177,7 @@ export class WatchExpressionsView extends CollapsibleViewletView { this.tree.refresh().done(() => { return this.toReveal instanceof Expression ? this.tree.reveal(this.toReveal) : TPromise.as(true); }, errors.onUnexpectedError); - }, 250); + }, 50); } public renderHeader(container: HTMLElement): void { diff --git a/src/vs/workbench/parts/debug/electron-browser/repl.ts b/src/vs/workbench/parts/debug/electron-browser/repl.ts index 1e477563652..de22f76fdb4 100644 --- a/src/vs/workbench/parts/debug/electron-browser/repl.ts +++ b/src/vs/workbench/parts/debug/electron-browser/repl.ts @@ -18,10 +18,12 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { KeyCode } from 'vs/base/common/keyCodes'; import tree = require('vs/base/parts/tree/browser/tree'); import treeimpl = require('vs/base/parts/tree/browser/treeImpl'); +import { Context as SuggestContext } from 'vs/editor/contrib/suggest/common/suggest'; +import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; import { IEditorOptions, IReadOnlyModel, EditorContextKeys, ICommonCodeEditor } from 'vs/editor/common/editorCommon'; import { Position } from 'vs/editor/common/core/position'; import * as modes from 'vs/editor/common/modes'; -import { editorAction, ServicesAccessor, EditorAction } from 'vs/editor/common/editorCommonExtensions'; +import { editorAction, ServicesAccessor, EditorAction, EditorCommand, CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -270,7 +272,8 @@ export class Repl extends Panel implements IPrivateReplService { scrollBeyondLastLine: false, theme: this.themeService.getColorTheme(), renderLineHighlight: false, - fixedOverflowWidgets: true + fixedOverflowWidgets: true, + acceptSuggestionOnEnter: false }; } @@ -349,6 +352,19 @@ class AcceptReplInputAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void | TPromise { + SuggestController.get(editor).acceptSelectedSuggestion(); accessor.get(IPrivateReplService).acceptReplInput(); } } + +const SuggestCommand = EditorCommand.bindToContribution(SuggestController.get); +CommonEditorRegistry.registerEditorCommand(new SuggestCommand({ + id: 'repl.action.acceptSuggestion', + precondition: ContextKeyExpr.and(debug.CONTEXT_IN_DEBUG_REPL, SuggestContext.Visible), + handler: x => x.acceptSelectedSuggestion(), + kbOpts: { + weight: 50, + kbExpr: EditorContextKeys.TextFocus, + primary: KeyCode.RightArrow + } +})); diff --git a/src/vs/workbench/parts/debug/test/common/debugViewModel.test.ts b/src/vs/workbench/parts/debug/test/common/debugViewModel.test.ts index 85d9a893eac..c4da203b73f 100644 --- a/src/vs/workbench/parts/debug/test/common/debugViewModel.test.ts +++ b/src/vs/workbench/parts/debug/test/common/debugViewModel.test.ts @@ -34,7 +34,7 @@ suite('Debug - View Model', () => { test('selected expression', () => { assert.equal(model.getSelectedExpression(), null); - const expression = new Expression('my expression', false); + const expression = new Expression('my expression'); model.setSelectedExpression(expression); assert.equal(model.getSelectedExpression(), expression); diff --git a/src/vs/workbench/parts/extensions/electron-browser/dependenciesViewer.ts b/src/vs/workbench/parts/extensions/browser/dependenciesViewer.ts similarity index 99% rename from src/vs/workbench/parts/extensions/electron-browser/dependenciesViewer.ts rename to src/vs/workbench/parts/extensions/browser/dependenciesViewer.ts index 1ec64f9cb96..e6f7aef8bf8 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/dependenciesViewer.ts +++ b/src/vs/workbench/parts/extensions/browser/dependenciesViewer.ts @@ -11,7 +11,7 @@ import { TPromise, Promise } from 'vs/base/common/winjs.base'; import { IDataSource, ITree, IRenderer } from 'vs/base/parts/tree/browser/tree'; import { DefaultController } from 'vs/base/parts/tree/browser/treeDefaults'; import { Action } from 'vs/base/common/actions'; -import { IExtensionDependencies, IExtensionsWorkbenchService } from '../common/extensions'; +import { IExtensionDependencies, IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; import { once } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/parts/extensions/browser/extensionEditor.ts similarity index 97% rename from src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts rename to src/vs/workbench/parts/extensions/browser/extensionEditor.ts index 9c3e8878c46..18798447bb5 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionEditor.ts @@ -27,15 +27,15 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IExtensionGalleryService, IExtensionManifest, IKeyBinding } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput'; -import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, IExtensionDependencies } from '../common/extensions'; -import { Renderer, DataSource, Controller } from './dependenciesViewer'; +import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, IExtensionDependencies } from 'vs/workbench/parts/extensions/common/extensions'; +import { Renderer, DataSource, Controller } from 'vs/workbench/parts/extensions/browser/dependenciesViewer'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ITemplateData } from './extensionsList'; -import { RatingsWidget, InstallWidget } from './extensionsWidgets'; +import { ITemplateData } from 'vs/workbench/parts/extensions/browser/extensionsList'; +import { RatingsWidget, InstallWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets'; import { EditorOptions } from 'vs/workbench/common/editor'; import product from 'vs/platform/product'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, ReloadAction, BuiltinStatusLabelAction } from './extensionsActions'; +import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, BuiltinStatusLabelAction, ReloadAction } from 'vs/workbench/parts/extensions/browser/extensionsActions'; import WebView from 'vs/workbench/parts/html/browser/webview'; import { Keybinding } from 'vs/base/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/browser/extensionTipsService.ts similarity index 97% rename from src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts rename to src/vs/workbench/parts/extensions/browser/extensionTipsService.ts index 8c5c2161f9b..ec14e2e4e0e 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionTipsService.ts @@ -8,14 +8,14 @@ import { forEach } from 'vs/base/common/collections'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { match } from 'vs/base/common/glob'; import { IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, LocalExtensionType, EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IExtensionsConfiguration, ConfigurationKey } from '../common/extensions'; +import { IExtensionsConfiguration, ConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModel } from 'vs/editor/common/editorCommon'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import product from 'vs/platform/product'; import { IChoiceService } from 'vs/platform/message/common/message'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ShowRecommendedExtensionsAction, ShowWorkspaceRecommendedExtensionsAction } from './extensionsActions'; +import { ShowRecommendedExtensionsAction, ShowWorkspaceRecommendedExtensionsAction } from 'vs/workbench/parts/extensions/browser/extensionsActions'; import Severity from 'vs/base/common/severity'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Schemas } from 'vs/base/common/network'; diff --git a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts new file mode 100644 index 00000000000..506f02d3007 --- /dev/null +++ b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts @@ -0,0 +1,1258 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/extensionActions'; +import { localize } from 'vs/nls'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IAction, Action } from 'vs/base/common/actions'; +import { Throttler } from 'vs/base/common/async'; +import * as DOM from 'vs/base/browser/dom'; +import severity from 'vs/base/common/severity'; +import paths = require('vs/base/common/paths'); +import Event from 'vs/base/common/event'; +import { ActionItem, IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, ConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions'; +import { ExtensionsConfigurationInitialContent } from 'vs/workbench/parts/extensions/common/extensionsFileTemplate'; +import { LocalExtensionType, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IMessageService } from 'vs/platform/message/common/message'; +import { ToggleViewletAction } from 'vs/workbench/browser/viewlet'; +import { IViewletService } from 'vs/workbench/services/viewlet/common/viewletService'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { Query } from 'vs/workbench/parts/extensions/common/extensionQuery'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IExtensionService, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import URI from 'vs/base/common/uri'; + + +export class InstallAction extends Action { + + private static InstallLabel = localize('installAction', "Install"); + private static InstallingLabel = localize('installing', "Installing"); + + private static Class = 'extension-action install'; + private static InstallingClass = 'extension-action install installing'; + + private disposables: IDisposable[] = []; + private _extension: IExtension; + get extension(): IExtension { return this._extension; } + set extension(extension: IExtension) { this._extension = extension; this.update(); } + + constructor( + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService + ) { + super('extensions.install', InstallAction.InstallLabel, InstallAction.Class, false); + + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + this.update(); + } + + private update(): void { + if (!this.extension || this.extension.type === LocalExtensionType.System) { + this.enabled = false; + this.class = InstallAction.Class; + this.label = InstallAction.InstallLabel; + return; + } + + this.enabled = this.extensionsWorkbenchService.canInstall(this.extension) && this.extension.state === ExtensionState.Uninstalled; + + if (this.extension.state === ExtensionState.Installing) { + this.label = InstallAction.InstallingLabel; + this.class = InstallAction.InstallingClass; + } else { + this.label = InstallAction.InstallLabel; + this.class = InstallAction.Class; + } + } + + run(): TPromise { + return this.extensionsWorkbenchService.install(this.extension); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export class UninstallAction extends Action { + + private static UninstallLabel = localize('uninstallAction', "Uninstall"); + private static UninstallingLabel = localize('Uninstalling', "Uninstalling"); + + private static UninstallClass = 'extension-action uninstall'; + private static UnInstallingClass = 'extension-action uninstall uninstalling'; + + private disposables: IDisposable[] = []; + private _extension: IExtension; + get extension(): IExtension { return this._extension; } + set extension(extension: IExtension) { this._extension = extension; this.update(); } + + constructor( + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IMessageService private messageService: IMessageService, + @IInstantiationService private instantiationService: IInstantiationService + ) { + super('extensions.uninstall', UninstallAction.UninstallLabel, UninstallAction.UninstallClass, false); + + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + this.update(); + } + + private update(): void { + if (!this.extension) { + this.enabled = false; + return; + } + + const state = this.extension.state; + + if (state === ExtensionState.Uninstalling) { + this.label = UninstallAction.UninstallingLabel; + this.class = UninstallAction.UnInstallingClass; + this.enabled = false; + return; + } + + this.label = UninstallAction.UninstallLabel; + this.class = UninstallAction.UninstallClass; + + const installedExtensions = this.extensionsWorkbenchService.local.filter(e => e.identifier === this.extension.identifier); + + if (!installedExtensions.length) { + this.enabled = false; + return; + } + + if (installedExtensions[0].type !== LocalExtensionType.User) { + this.enabled = false; + return; + } + + this.enabled = true; + } + + run(): TPromise { + return this.extensionsWorkbenchService.uninstall(this.extension); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export class CombinedInstallAction extends Action { + + private static NoExtensionClass = 'extension-action install no-extension'; + private installAction: InstallAction; + private uninstallAction: UninstallAction; + private disposables: IDisposable[] = []; + private _extension: IExtension; + get extension(): IExtension { return this._extension; } + set extension(extension: IExtension) { + this._extension = extension; + this.installAction.extension = extension; + this.uninstallAction.extension = extension; + } + + constructor( + @IInstantiationService instantiationService: IInstantiationService + ) { + super('extensions.combinedInstall', '', '', false); + + this.installAction = instantiationService.createInstance(InstallAction); + this.uninstallAction = instantiationService.createInstance(UninstallAction); + this.disposables.push(this.installAction, this.uninstallAction); + + this.installAction.onDidChange(this.update, this, this.disposables); + this.uninstallAction.onDidChange(this.update, this, this.disposables); + this.update(); + } + + private update(): void { + if (!this.extension || this.extension.type === LocalExtensionType.System) { + this.enabled = false; + this.class = CombinedInstallAction.NoExtensionClass; + } else if (this.installAction.enabled) { + this.enabled = true; + this.label = this.installAction.label; + this.class = this.installAction.class; + } else if (this.uninstallAction.enabled) { + this.enabled = true; + this.label = this.uninstallAction.label; + this.class = this.uninstallAction.class; + } else if (this.extension.state === ExtensionState.Installing) { + this.enabled = false; + this.label = this.installAction.label; + this.class = this.installAction.class; + } else if (this.extension.state === ExtensionState.Uninstalling) { + this.enabled = false; + this.label = this.uninstallAction.label; + this.class = this.uninstallAction.class; + } else { + this.enabled = false; + this.label = this.installAction.label; + this.class = this.installAction.class; + } + } + + run(): TPromise { + if (this.installAction.enabled) { + return this.installAction.run(); + } else if (this.uninstallAction.enabled) { + return this.uninstallAction.run(); + } + + return TPromise.as(null); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export class UpdateAction extends Action { + + private static EnabledClass = 'extension-action update'; + private static DisabledClass = `${UpdateAction.EnabledClass} disabled`; + + private disposables: IDisposable[] = []; + private _extension: IExtension; + get extension(): IExtension { return this._extension; } + set extension(extension: IExtension) { this._extension = extension; this.update(); } + + constructor( + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService + ) { + super('extensions.update', localize('updateAction', "Update"), UpdateAction.DisabledClass, false); + + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + this.update(); + } + + private update(): void { + if (!this.extension) { + this.enabled = false; + this.class = UpdateAction.DisabledClass; + return; + } + + if (this.extension.type !== LocalExtensionType.User) { + this.enabled = false; + this.class = UpdateAction.DisabledClass; + return; + } + + const canInstall = this.extensionsWorkbenchService.canInstall(this.extension); + const isInstalled = this.extension.state === ExtensionState.Installed; + + this.enabled = canInstall && isInstalled && this.extension.outdated; + this.class = this.enabled ? UpdateAction.EnabledClass : UpdateAction.DisabledClass; + } + + run(): TPromise { + return this.extensionsWorkbenchService.install(this.extension); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export interface IExtensionAction extends IAction { + extension: IExtension; +} + +export class DropDownMenuActionItem extends ActionItem { + + private disposables: IDisposable[] = []; + private _extension: IExtension; + + constructor(action: IAction, private menuActionGroups: IExtensionAction[][], private contextMenuService: IContextMenuService) { + super(null, action, { icon: true, label: true }); + for (const menuActions of menuActionGroups) { + this.disposables = [...this.disposables, ...menuActions]; + } + } + + get extension(): IExtension { return this._extension; } + + set extension(extension: IExtension) { + this._extension = extension; + for (const menuActions of this.menuActionGroups) { + for (const menuAction of menuActions) { + menuAction.extension = extension; + } + } + } + + public showMenu(): void { + const actions = this.getActions(); + let elementPosition = DOM.getDomNodePagePosition(this.builder.getHTMLElement()); + const anchor = { x: elementPosition.left, y: elementPosition.top + elementPosition.height + 10 }; + this.contextMenuService.showContextMenu({ + getAnchor: () => anchor, + getActions: () => TPromise.wrap(actions) + }); + } + + private getActions(): IAction[] { + let actions = []; + for (const menuActions of this.menuActionGroups) { + const filtered = menuActions.filter(a => a.enabled); + if (filtered.length > 0) { + actions = [...actions, ...filtered, new Separator()]; + } + } + return actions.length ? actions.slice(0, actions.length - 1) : actions; + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export class ManageExtensionActionItem extends DropDownMenuActionItem { + constructor( + action: IAction, + @IInstantiationService instantiationService: IInstantiationService, + @IContextMenuService contextMenuService: IContextMenuService) { + super(action, [ + [instantiationService.createInstance(EnableForWorkspaceAction, localize('enableForWorkspaceAction.label', "Enable (Workspace)")), + instantiationService.createInstance(EnableGloballyAction, localize('enableAlwaysAction.label', "Enable")), + instantiationService.createInstance(DisableForWorkspaceAction, localize('disableForWorkspaceAction.label', "Disable (Workspace)")), + instantiationService.createInstance(DisableGloballyAction, localize('disableAlwaysAction.label', "Disable"))], + [instantiationService.createInstance(UninstallAction)] + ], contextMenuService); + } +} + +export class ManageExtensionAction extends Action { + + static ID = 'extensions.manage'; + + private static Class = 'extension-action manage'; + private static NoExtensionClass = `${ManageExtensionAction.Class} no-extension`; + + private _actionItem: EnableActionItem; + get actionItem(): IActionItem { return this._actionItem; } + + private disposables: IDisposable[] = []; + private _extension: IExtension; + get extension(): IExtension { return this._extension; } + set extension(extension: IExtension) { this._extension = extension; this._actionItem.extension = extension; this.update(); } + + constructor( + @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService, + @IInstantiationService private instantiationService: IInstantiationService + ) { + super(ManageExtensionAction.ID); + + this._actionItem = this.instantiationService.createInstance(ManageExtensionActionItem, this); + this.disposables.push(this._actionItem); + + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + this.update(); + } + + private update(): void { + this.class = ManageExtensionAction.NoExtensionClass; + this.tooltip = ''; + this.enabled = false; + if (this.extension) { + const state = this.extension.state; + this.enabled = state === ExtensionState.Installed; + this.class = this.enabled || state === ExtensionState.Uninstalling ? ManageExtensionAction.Class : ManageExtensionAction.NoExtensionClass; + this.tooltip = state === ExtensionState.Uninstalling ? localize('ManageExtensionAction.uninstallingTooltip', "Uninstalling") : ''; + } + } + + public run(): TPromise { + this._actionItem.showMenu(); + return TPromise.wrap(null); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export class EnableForWorkspaceAction extends Action implements IExtensionAction { + + static ID = 'extensions.enableForWorkspace'; + static LABEL = localize('enableForWorkspaceAction', "Workspace"); + + private disposables: IDisposable[] = []; + + private _extension: IExtension; + get extension(): IExtension { return this._extension; } + set extension(extension: IExtension) { this._extension = extension; this.update(); } + + constructor(label: string, + @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService, + @IInstantiationService private instantiationService: IInstantiationService + ) { + super(EnableForWorkspaceAction.ID, label); + + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + this.update(); + } + + private update(): void { + this.enabled = false; + if (this.extension) { + this.enabled = !this.extension.disabledGlobally && this.extension.disabledForWorkspace && this.extensionEnablementService.canEnable(this.extension.identifier); + } + } + + run(): TPromise { + return this.extensionsWorkbenchService.setEnablement(this.extension, true, true); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export class EnableGloballyAction extends Action implements IExtensionAction { + + static ID = 'extensions.enableGlobally'; + static LABEL = localize('enableGloballyAction', "Always"); + + private disposables: IDisposable[] = []; + + private _extension: IExtension; + get extension(): IExtension { return this._extension; } + set extension(extension: IExtension) { this._extension = extension; this.update(); } + + constructor(label: string, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService, + @IInstantiationService private instantiationService: IInstantiationService + ) { + super(EnableGloballyAction.ID, label); + + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + this.update(); + } + + private update(): void { + this.enabled = false; + if (this.extension) { + this.enabled = this.extension.disabledGlobally && this.extensionEnablementService.canEnable(this.extension.identifier); + } + } + + run(): TPromise { + return this.extensionsWorkbenchService.setEnablement(this.extension, true, false); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export class EnableAction extends Action { + + static ID = 'extensions.enable'; + private static EnabledClass = 'extension-action enable'; + private static DisabledClass = `${EnableAction.EnabledClass} disabled`; + + private disposables: IDisposable[] = []; + + private _actionItem: EnableActionItem; + get actionItem(): IActionItem { return this._actionItem; } + + private _extension: IExtension; + get extension(): IExtension { return this._extension; } + set extension(extension: IExtension) { this._extension = extension; this._actionItem.extension = extension; this.update(); } + + + constructor( + @IInstantiationService private instantiationService: IInstantiationService, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService + ) { + super(EnableAction.ID, localize('enableAction', "Enable"), EnableAction.DisabledClass, false); + + this._actionItem = this.instantiationService.createInstance(EnableActionItem, this); + this.disposables.push(this._actionItem); + + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + this.update(); + } + + private update(): void { + if (!this.extension) { + this.enabled = false; + this.class = EnableAction.DisabledClass; + return; + } + + this.enabled = this.extension.state === ExtensionState.Installed && (this.extension.disabledGlobally || this.extension.disabledForWorkspace) && this.extensionEnablementService.canEnable(this.extension.identifier); + this.class = this.enabled ? EnableAction.EnabledClass : EnableAction.DisabledClass; + } + + public run(): TPromise { + this._actionItem.showMenu(); + return TPromise.wrap(null); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } + +} + +export class DisableForWorkspaceAction extends Action implements IExtensionAction { + + static ID = 'extensions.disableForWorkspace'; + static LABEL = localize('disableForWorkspaceAction', "Workspace"); + + private disposables: IDisposable[] = []; + + private _extension: IExtension; + get extension(): IExtension { return this._extension; } + set extension(extension: IExtension) { this._extension = extension; this.update(); } + + constructor(label: string, + @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IInstantiationService private instantiationService: IInstantiationService + ) { + super(DisableForWorkspaceAction.ID, label); + + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + this.update(); + } + + private update(): void { + this.enabled = false; + if (this.extension && this.workspaceContextService.getWorkspace()) { + this.enabled = this.extension.type !== LocalExtensionType.System && !this.extension.disabledGlobally && !this.extension.disabledForWorkspace; + } + } + + run(): TPromise { + return this.extensionsWorkbenchService.setEnablement(this.extension, false, true); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export class DisableGloballyAction extends Action implements IExtensionAction { + + static ID = 'extensions.disableGlobally'; + static LABEL = localize('disableGloballyAction', "Always"); + + private disposables: IDisposable[] = []; + + private _extension: IExtension; + get extension(): IExtension { return this._extension; } + set extension(extension: IExtension) { this._extension = extension; this.update(); } + + constructor(label: string, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IInstantiationService private instantiationService: IInstantiationService + ) { + super(DisableGloballyAction.ID, label); + + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + this.update(); + } + + private update(): void { + this.enabled = false; + if (this.extension) { + this.enabled = this.extension.type !== LocalExtensionType.System && !this.extension.disabledGlobally && !this.extension.disabledForWorkspace; + } + } + + run(): TPromise { + return this.extensionsWorkbenchService.setEnablement(this.extension, false, false); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export class DisableAction extends Action { + + static ID = 'extensions.disable'; + + private static EnabledClass = 'extension-action disable'; + private static DisabledClass = `${DisableAction.EnabledClass} disabled`; + + private disposables: IDisposable[] = []; + private _actionItem: DisableActionItem; + get actionItem(): IActionItem { return this._actionItem; } + + private _extension: IExtension; + get extension(): IExtension { return this._extension; } + set extension(extension: IExtension) { this._extension = extension; this._actionItem.extension = extension; this.update(); } + + + constructor( + @IInstantiationService private instantiationService: IInstantiationService, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + ) { + super(DisableAction.ID, localize('disableAction', "Disable"), DisableAction.DisabledClass, false); + this._actionItem = this.instantiationService.createInstance(DisableActionItem, this); + this.disposables.push(this._actionItem); + + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + this.update(); + } + + private update(): void { + if (!this.extension) { + this.enabled = false; + this.class = DisableAction.DisabledClass; + return; + } + + this.enabled = this.extension.state === ExtensionState.Installed && this.extension.type !== LocalExtensionType.System && !this.extension.disabledGlobally && !this.extension.disabledForWorkspace; + this.class = this.enabled ? DisableAction.EnabledClass : DisableAction.DisabledClass; + } + + public run(): TPromise { + this._actionItem.showMenu(); + return TPromise.wrap(null); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export class EnableActionItem extends DropDownMenuActionItem { + constructor( + action: IAction, + @IInstantiationService instantiationService: IInstantiationService, + @IContextMenuService contextMenuService: IContextMenuService) { + super(action, [[instantiationService.createInstance(EnableForWorkspaceAction, EnableForWorkspaceAction.LABEL), instantiationService.createInstance(EnableGloballyAction, EnableGloballyAction.LABEL)]], contextMenuService); + } +} + +export class DisableActionItem extends DropDownMenuActionItem { + constructor( + action: IAction, + @IInstantiationService instantiationService: IInstantiationService, + @IContextMenuService contextMenuService: IContextMenuService) { + super(action, [[instantiationService.createInstance(DisableForWorkspaceAction, DisableForWorkspaceAction.LABEL), instantiationService.createInstance(DisableGloballyAction, DisableGloballyAction.LABEL)]], contextMenuService); + } +} + + + +export class UpdateAllAction extends Action { + + static ID = 'workbench.extensions.action.updateAllExtensions'; + static LABEL = localize('updateAll', "Update All Extensions"); + + private disposables: IDisposable[] = []; + + constructor( + id = UpdateAllAction.ID, + label = UpdateAllAction.LABEL, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService + ) { + super(id, label, '', false); + + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + this.update(); + } + + private get outdated(): IExtension[] { + return this.extensionsWorkbenchService.local.filter(e => e.outdated && e.state !== ExtensionState.Installing); + } + + private update(): void { + this.enabled = this.outdated.length > 0; + } + + run(): TPromise { + return TPromise.join(this.outdated.map(e => this.extensionsWorkbenchService.install(e))); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export class ReloadAction extends Action { + + private static EnabledClass = 'extension-action reload'; + private static DisabledClass = `${ReloadAction.EnabledClass} disabled`; + + private disposables: IDisposable[] = []; + private _extension: IExtension; + get extension(): IExtension { return this._extension; } + set extension(extension: IExtension) { this._extension = extension; this.update(); } + + private reloadMessaage: string = ''; + private throttler: Throttler; + + constructor( + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IMessageService private messageService: IMessageService, + @IInstantiationService private instantiationService: IInstantiationService, + @IExtensionService private extensionService: IExtensionService, + @ICommandService private commandService: ICommandService + ) { + super('extensions.reload', localize('reloadAction', "Reload"), ReloadAction.DisabledClass, false); + this.throttler = new Throttler(); + + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + this.update(); + } + + private update(): void { + this.throttler.queue(() => { + this.enabled = false; + this.tooltip = ''; + this.reloadMessaage = ''; + if (!this.extension) { + return TPromise.wrap(null); + } + const state = this.extension.state; + if (state === ExtensionState.Installing || state === ExtensionState.Uninstalling) { + return TPromise.wrap(null); + } + return this.extensionService.getExtensions() + .then(runningExtensions => this.computeReloadState(runningExtensions)); + }).done(() => { + this.class = this.enabled ? ReloadAction.EnabledClass : ReloadAction.DisabledClass; + }); + } + + private computeReloadState(runningExtensions: IExtensionDescription[]): void { + const isInstalled = this.extensionsWorkbenchService.local.some(e => e.identifier === this.extension.identifier); + const isUninstalled = this.extension.state === ExtensionState.Uninstalled; + const isDisabled = this.extension.disabledForWorkspace || this.extension.disabledGlobally; + + const filteredExtensions = runningExtensions.filter(e => e.id === this.extension.identifier); + const isExtensionRunning = filteredExtensions.length > 0; + const isDifferentVersionRunning = filteredExtensions.length > 0 && this.extension.version !== filteredExtensions[0].version; + + if (isInstalled) { + if (isDifferentVersionRunning && !isDisabled) { + // Requires reload to run the updated extension + this.enabled = true; + this.tooltip = localize('postUpdateTooltip', "Reload to update"); + this.reloadMessaage = localize('postUpdateMessage', "Reload this window to activate the updated extension '{0}'?", this.extension.displayName); + return; + } + + if (!isExtensionRunning && !isDisabled) { + // Requires reload to enable the extension + this.enabled = true; + this.tooltip = localize('postEnableTooltip', "Reload to activate"); + this.reloadMessaage = localize('postEnableMessage', "Reload this window to activate the extension '{0}'?", this.extension.displayName); + return; + } + + if (isExtensionRunning && isDisabled) { + // Requires reload to disable the extension + this.enabled = true; + this.tooltip = localize('postDisableTooltip', "Reload to deactivate"); + this.reloadMessaage = localize('postDisableMessage', "Reload this window to deactivate the extension '{0}'?", this.extension.displayName); + return; + } + return; + } + + if (isUninstalled && isExtensionRunning) { + // Requires reload to deactivate the extension + this.enabled = true; + this.tooltip = localize('postUninstallTooltip', "Reload to deactivate"); + this.reloadMessaage = localize('postUninstallMessage', "Reload this window to deactivate the uninstalled extension '{0}'?", this.extension.displayName); + return; + } + } + + run(): TPromise { + if (this.messageService.confirm({ message: this.reloadMessaage })) { + // TODO: @sandy: Temporary hack. Adopt to new IWindowService from @bpasero and @jaoa + this.commandService.executeCommand('workbench.action.reloadWindow'); + } + return TPromise.wrap(null); + } +} + +export class OpenExtensionsViewletAction extends ToggleViewletAction { + + static ID = VIEWLET_ID; + static LABEL = localize('toggleExtensionsViewlet', "Show Extensions"); + + constructor( + id: string, + label: string, + @IViewletService viewletService: IViewletService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService + ) { + super(id, label, VIEWLET_ID, viewletService, editorService); + } +} + +export class InstallExtensionsAction extends OpenExtensionsViewletAction { + static ID = 'workbench.extensions.action.installExtensions'; + static LABEL = localize('installExtensions', "Install Extensions"); +} + +export class ShowInstalledExtensionsAction extends Action { + + static ID = 'workbench.extensions.action.showInstalledExtensions'; + static LABEL = localize('showInstalledExtensions', "Show Installed Extensions"); + + constructor( + id: string, + label: string, + @IViewletService private viewletService: IViewletService, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService + ) { + super(id, label, 'clear-extensions', true); + } + + run(): TPromise { + return this.viewletService.openViewlet(VIEWLET_ID, true) + .then(viewlet => viewlet as IExtensionsViewlet) + .then(viewlet => { + viewlet.search(''); + viewlet.focus(); + }); + } +} + +export class ShowDisabledExtensionsAction extends Action { + + static ID = 'workbench.extensions.action.showDisabledExtensions'; + static LABEL = localize('showDisabledExtensions', "Show Disabled Extensions"); + + constructor( + id: string, + label: string, + @IViewletService private viewletService: IViewletService, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService + ) { + super(id, label, 'null', true); + } + + run(): TPromise { + return this.viewletService.openViewlet(VIEWLET_ID, true) + .then(viewlet => viewlet as IExtensionsViewlet) + .then(viewlet => { + viewlet.search('@disabled '); + viewlet.focus(); + }); + } +} + +export class ClearExtensionsInputAction extends ShowInstalledExtensionsAction { + + static ID = 'workbench.extensions.action.clearExtensionsInput'; + static LABEL = localize('clearExtensionsInput', "Clear Extensions Input"); + + private disposables: IDisposable[] = []; + + constructor( + id: string, + label: string, + onSearchChange: Event, + @IViewletService viewletService: IViewletService, + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService + ) { + super(id, label, viewletService, extensionsWorkbenchService); + this.enabled = false; + onSearchChange(this.onSearchChange, this, this.disposables); + } + + private onSearchChange(value: string): void { + this.enabled = !!value; + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } +} + +export class ShowOutdatedExtensionsAction extends Action { + + static ID = 'workbench.extensions.action.listOutdatedExtensions'; + static LABEL = localize('showOutdatedExtensions', "Show Outdated Extensions"); + + constructor( + id: string, + label: string, + @IViewletService private viewletService: IViewletService + ) { + super(id, label, null, true); + } + + run(): TPromise { + return this.viewletService.openViewlet(VIEWLET_ID, true) + .then(viewlet => viewlet as IExtensionsViewlet) + .then(viewlet => { + viewlet.search('@outdated '); + viewlet.focus(); + }); + } + + protected isEnabled(): boolean { + return true; + } +} + +export class ShowPopularExtensionsAction extends Action { + + static ID = 'workbench.extensions.action.showPopularExtensions'; + static LABEL = localize('showPopularExtensions', "Show Popular Extensions"); + + constructor( + id: string, + label: string, + @IViewletService private viewletService: IViewletService + ) { + super(id, label, null, true); + } + + run(): TPromise { + return this.viewletService.openViewlet(VIEWLET_ID, true) + .then(viewlet => viewlet as IExtensionsViewlet) + .then(viewlet => { + viewlet.search('@sort:installs '); + viewlet.focus(); + }); + } + + protected isEnabled(): boolean { + return true; + } +} + +export class ShowRecommendedExtensionsAction extends Action { + + static ID = 'workbench.extensions.action.showRecommendedExtensions'; + static LABEL = localize('showRecommendedExtensions', "Show Recommended Extensions"); + + constructor( + id: string, + label: string, + @IViewletService private viewletService: IViewletService + ) { + super(id, label, null, true); + } + + run(): TPromise { + return this.viewletService.openViewlet(VIEWLET_ID, true) + .then(viewlet => viewlet as IExtensionsViewlet) + .then(viewlet => { + viewlet.search('@recommended '); + viewlet.focus(); + }); + } + + protected isEnabled(): boolean { + return true; + } +} + +export class ShowWorkspaceRecommendedExtensionsAction extends Action { + + static ID = 'workbench.extensions.action.showWorkspaceRecommendedExtensions'; + static LABEL = localize('showWorkspaceRecommendedExtensions', "Show Workspace Recommended Extensions"); + + constructor( + id: string, + label: string, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IViewletService private viewletService: IViewletService + ) { + super(id, label, null, !!contextService.getWorkspace()); + } + + run(): TPromise { + return this.viewletService.openViewlet(VIEWLET_ID, true) + .then(viewlet => viewlet as IExtensionsViewlet) + .then(viewlet => { + viewlet.search('@recommended:workspace '); + viewlet.focus(); + }); + } + + protected isEnabled(): boolean { + return true; + } +} + +export class ChangeSortAction extends Action { + + private query: Query; + private disposables: IDisposable[] = []; + + constructor( + id: string, + label: string, + onSearchChange: Event, + private sortBy: string, + private sortOrder: string, + @IViewletService private viewletService: IViewletService + ) { + super(id, label, null, true); + + if (sortBy === undefined && sortOrder === undefined) { + throw new Error('bad arguments'); + } + + this.query = Query.parse(''); + this.enabled = false; + onSearchChange(this.onSearchChange, this, this.disposables); + } + + private onSearchChange(value: string): void { + const query = Query.parse(value); + this.query = new Query(query.value, this.sortBy || query.sortBy, this.sortOrder || query.sortOrder); + this.enabled = value && this.query.isValid() && !this.query.equals(query); + } + + run(): TPromise { + return this.viewletService.openViewlet(VIEWLET_ID, true) + .then(viewlet => viewlet as IExtensionsViewlet) + .then(viewlet => { + viewlet.search(this.query.toString()); + viewlet.focus(); + }); + } + + protected isEnabled(): boolean { + return true; + } +} + +export class ConfigureWorkspaceRecommendedExtensionsAction extends Action { + + static ID = 'workbench.extensions.action.configureWorkspaceRecommendedExtensions'; + static LABEL = localize('configureWorkspaceRecommendedExtensions', "Configure Recommended Extensions (Workspace)"); + + constructor( + id: string, + label: string, + @IFileService private fileService: IFileService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IExtensionsWorkbenchService private extensionsService: IExtensionsWorkbenchService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IMessageService private messageService: IMessageService + ) { + super(id, label, null, !!contextService.getWorkspace()); + } + + public run(event: any): TPromise { + return this.openExtensionsFile(); + } + + private openExtensionsFile(): TPromise { + if (!this.contextService.getWorkspace()) { + this.messageService.show(severity.Info, localize('ConfigureWorkspaceRecommendations.noWorkspace', 'Recommendations are only available on a workspace folder.')); + return TPromise.as(undefined); + } + + return this.getOrCreateExtensionsFile().then(value => { + return this.editorService.openEditor({ + resource: value.extensionsFileResource, + options: { + forceOpen: true, + pinned: value.created + }, + }); + }, (error) => TPromise.wrapError(new Error(localize('OpenExtensionsFile.failed', "Unable to create 'extensions.json' file inside the '.vscode' folder ({0}).", error)))); + } + + private getOrCreateExtensionsFile(): TPromise<{ created: boolean, extensionsFileResource: URI }> { + const extensionsFileResource = URI.file(paths.join(this.contextService.getWorkspace().resource.fsPath, '.vscode', `${ConfigurationKey}.json`)); + + return this.fileService.resolveContent(extensionsFileResource).then(content => { + return { created: false, extensionsFileResource }; + }, err => { + return this.fileService.updateContent(extensionsFileResource, ExtensionsConfigurationInitialContent).then(() => { + return { created: true, extensionsFileResource }; + }); + }); + } +} + +export class BuiltinStatusLabelAction extends Action { + + private static Class = 'extension-action built-in-status'; + + private _extension: IExtension; + get extension(): IExtension { return this._extension; } + set extension(extension: IExtension) { this._extension = extension; this.update(); } + + constructor() { + super('extensions.install', localize('builtin', "Built-in"), '', false); + } + + private update(): void { + if (this.extension && this.extension.type === LocalExtensionType.System) { + this.class = `${BuiltinStatusLabelAction.Class} system`; + } else { + this.class = `${BuiltinStatusLabelAction.Class} user`; + } + } + + run(): TPromise { + return TPromise.as(null); + } +} + +export class DisableAllAction extends Action { + + static ID = 'workbench.extensions.action.disableAll'; + static LABEL = localize('disableAll', "Disable All Installed Extensions"); + + private disposables: IDisposable[] = []; + + constructor( + id: string = DisableAllAction.ID, label: string = DisableAllAction.LABEL, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService + ) { + super(id, label); + this.update(); + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + } + + private update(): void { + this.enabled = this.extensionsWorkbenchService.local.some(e => e.type === LocalExtensionType.User && !e.disabledForWorkspace && !e.disabledGlobally); + } + + run(): TPromise { + return TPromise.join(this.extensionsWorkbenchService.local.map(e => this.extensionsWorkbenchService.setEnablement(e, false))); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export class DisableAllWorkpsaceAction extends Action { + + static ID = 'workbench.extensions.action.disableAllWorkspace'; + static LABEL = localize('disableAllWorkspace', "Disable All Installed Extensions for this Workspace"); + + private disposables: IDisposable[] = []; + + constructor( + id: string = DisableAllWorkpsaceAction.ID, label: string = DisableAllWorkpsaceAction.LABEL, + @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService + ) { + super(id, label); + this.update(); + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + } + + private update(): void { + this.enabled = !!this.workspaceContextService.getWorkspace() && this.extensionsWorkbenchService.local.some(e => e.type === LocalExtensionType.User && !e.disabledForWorkspace && !e.disabledGlobally); + } + + run(): TPromise { + return TPromise.join(this.extensionsWorkbenchService.local.map(e => this.extensionsWorkbenchService.setEnablement(e, false, true))); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export class EnableAllAction extends Action { + + static ID = 'workbench.extensions.action.enableAll'; + static LABEL = localize('enableAll', "Enable All Installed Extensions"); + + private disposables: IDisposable[] = []; + + constructor( + id: string = EnableAllAction.ID, label: string = EnableAllAction.LABEL, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService + ) { + super(id, label); + this.update(); + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + } + + private update(): void { + this.enabled = this.extensionsWorkbenchService.local.some(e => this.extensionEnablementService.canEnable(e.identifier) && e.disabledGlobally); + } + + run(): TPromise { + return TPromise.join(this.extensionsWorkbenchService.local.map(e => this.extensionsWorkbenchService.setEnablement(e, true))); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + +export class EnableAllWorkpsaceAction extends Action { + + static ID = 'workbench.extensions.action.enableAllWorkspace'; + static LABEL = localize('enableAllWorkspace', "Enable All Installed Extensions for this Workspace"); + + private disposables: IDisposable[] = []; + + constructor( + id: string = EnableAllWorkpsaceAction.ID, label: string = EnableAllWorkpsaceAction.LABEL, + @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService + ) { + super(id, label); + this.update(); + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + } + + private update(): void { + this.enabled = !!this.workspaceContextService.getWorkspace() && this.extensionsWorkbenchService.local.some(e => this.extensionEnablementService.canEnable(e.identifier) && !e.disabledGlobally && e.disabledForWorkspace); + } + + run(): TPromise { + return TPromise.join(this.extensionsWorkbenchService.local.map(e => this.extensionsWorkbenchService.setEnablement(e, true, true))); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts b/src/vs/workbench/parts/extensions/browser/extensionsList.ts similarity index 95% rename from src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts rename to src/vs/workbench/parts/extensions/browser/extensionsList.ts index d4bd7444441..c5dd01e143a 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionsList.ts @@ -15,9 +15,9 @@ import { IDelegate } from 'vs/base/browser/ui/list/list'; import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { once } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; -import { IExtension, IExtensionsWorkbenchService } from '../common/extensions'; -import { InstallAction, UpdateAction, BuiltinStatusLabelAction, ReloadAction, ManageExtensionAction } from './extensionsActions'; -import { Label, RatingsWidget, InstallWidget } from './extensionsWidgets'; +import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; +import { InstallAction, UpdateAction, BuiltinStatusLabelAction, ManageExtensionAction, ReloadAction } from 'vs/workbench/parts/extensions/browser/extensionsActions'; +import { Label, RatingsWidget, InstallWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets'; import { EventType } from 'vs/base/common/events'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen.ts b/src/vs/workbench/parts/extensions/browser/extensionsQuickOpen.ts similarity index 95% rename from src/vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen.ts rename to src/vs/workbench/parts/extensions/browser/extensionsQuickOpen.ts index d900f5f9ae2..152bff0beac 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionsQuickOpen.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import nls = require('vs/nls'); +import * as nls from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import { IAutoFocus, Mode, IModel } from 'vs/base/parts/quickopen/common/quickOpen'; import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; -import { IExtensionsViewlet, VIEWLET_ID } from '../common/extensions'; +import { IExtensionsViewlet, VIEWLET_ID } from 'vs/workbench/parts/extensions/common/extensions'; import { IViewletService } from 'vs/workbench/services/viewlet/common/viewletService'; class SimpleEntry extends QuickOpenEntry { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsWidgets.ts b/src/vs/workbench/parts/extensions/browser/extensionsWidgets.ts similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/extensionsWidgets.ts rename to src/vs/workbench/parts/extensions/browser/extensionsWidgets.ts diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/EmptyStar.svg b/src/vs/workbench/parts/extensions/browser/media/EmptyStar.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/EmptyStar.svg rename to src/vs/workbench/parts/extensions/browser/media/EmptyStar.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/FullStarLight.svg b/src/vs/workbench/parts/extensions/browser/media/FullStarLight.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/FullStarLight.svg rename to src/vs/workbench/parts/extensions/browser/media/FullStarLight.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/HalfStarLight.svg b/src/vs/workbench/parts/extensions/browser/media/HalfStarLight.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/HalfStarLight.svg rename to src/vs/workbench/parts/extensions/browser/media/HalfStarLight.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/clear-inverse.svg b/src/vs/workbench/parts/extensions/browser/media/clear-inverse.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/clear-inverse.svg rename to src/vs/workbench/parts/extensions/browser/media/clear-inverse.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/clear.svg b/src/vs/workbench/parts/extensions/browser/media/clear.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/clear.svg rename to src/vs/workbench/parts/extensions/browser/media/clear.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/defaultIcon.png b/src/vs/workbench/parts/extensions/browser/media/defaultIcon.png similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/defaultIcon.png rename to src/vs/workbench/parts/extensions/browser/media/defaultIcon.png diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionActions.css b/src/vs/workbench/parts/extensions/browser/media/extensionActions.css similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/extensionActions.css rename to src/vs/workbench/parts/extensions/browser/media/extensionActions.css diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css b/src/vs/workbench/parts/extensions/browser/media/extensionEditor.css similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css rename to src/vs/workbench/parts/extensions/browser/media/extensionEditor.css diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionsWidgets.css b/src/vs/workbench/parts/extensions/browser/media/extensionsWidgets.css similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/extensionsWidgets.css rename to src/vs/workbench/parts/extensions/browser/media/extensionsWidgets.css diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/manage-inverse.svg b/src/vs/workbench/parts/extensions/browser/media/manage-inverse.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/manage-inverse.svg rename to src/vs/workbench/parts/extensions/browser/media/manage-inverse.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/manage.svg b/src/vs/workbench/parts/extensions/browser/media/manage.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/manage.svg rename to src/vs/workbench/parts/extensions/browser/media/manage.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/markdown.css b/src/vs/workbench/parts/extensions/browser/media/markdown.css similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/markdown.css rename to src/vs/workbench/parts/extensions/browser/media/markdown.css diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsFileTemplate.ts b/src/vs/workbench/parts/extensions/common/extensionsFileTemplate.ts similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/extensionsFileTemplate.ts rename to src/vs/workbench/parts/extensions/common/extensionsFileTemplate.ts diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts index 4d1ba542fe8..89cde057706 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts @@ -12,7 +12,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionGalleryService, IExtensionTipsService, ExtensionsLabel, ExtensionsChannelId } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actionRegistry'; -import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService'; +import { ExtensionTipsService } from 'vs/workbench/parts/extensions/browser/extensionTipsService'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/parts/output/common/output'; import { EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor'; @@ -22,17 +22,18 @@ import { VIEWLET_ID, IExtensionsWorkbenchService } from '../common/extensions'; import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowWorkspaceRecommendedExtensionsAction, ShowPopularExtensionsAction, - ShowInstalledExtensionsAction, ShowDisabledExtensionsAction, UpdateAllAction, OpenExtensionsFolderAction, ConfigureWorkspaceRecommendedExtensionsAction, InstallVSIXAction, + ShowInstalledExtensionsAction, ShowDisabledExtensionsAction, UpdateAllAction, ConfigureWorkspaceRecommendedExtensionsAction, EnableAllAction, EnableAllWorkpsaceAction, DisableAllAction, DisableAllWorkpsaceAction -} from './extensionsActions'; +} from 'vs/workbench/parts/extensions/browser/extensionsActions'; +import { OpenExtensionsFolderAction, InstallVSIXAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; -import { ExtensionEditor } from './extensionEditor'; -import { StatusUpdater } from './extensionsViewlet'; +import { ExtensionEditor } from 'vs/workbench/parts/extensions/browser/extensionEditor'; +import { StatusUpdater } from 'vs/workbench/parts/extensions/electron-browser/extensionsViewlet'; import { IQuickOpenRegistry, Extensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import jsonContributionRegistry = require('vs/platform/jsonschemas/common/jsonContributionRegistry'); -import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from 'vs/workbench/parts/extensions/electron-browser/extensionsFileTemplate'; +import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from 'vs/workbench/parts/extensions/common/extensionsFileTemplate'; // Singletons registerSingleton(IExtensionGalleryService, ExtensionGalleryService); @@ -48,7 +49,7 @@ Registry.as(OutputExtensions.OutputChannels) // Quickopen Registry.as(Extensions.Quickopen).registerQuickOpenHandler( new QuickOpenHandlerDescriptor( - 'vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen', + 'vs/workbench/parts/extensions/browser/extensionsQuickOpen', 'ExtensionsHandler', 'ext ', localize('extensionsCommands', "Manage Extensions"), @@ -58,7 +59,7 @@ Registry.as(Extensions.Quickopen).registerQuickOpenHandler( Registry.as(Extensions.Quickopen).registerQuickOpenHandler( new QuickOpenHandlerDescriptor( - 'vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen', + 'vs/workbench/parts/extensions/browser/extensionsQuickOpen', 'GalleryExtensionsHandler', 'ext install ', localize('galleryExtensionsCommands', "Install Gallery Extensions"), @@ -70,7 +71,7 @@ Registry.as(Extensions.Quickopen).registerQuickOpenHandler( const editorDescriptor = new EditorDescriptor( ExtensionEditor.ID, localize('extension', "Extension"), - 'vs/workbench/parts/extensions/electron-browser/extensionEditor', + 'vs/workbench/parts/extensions/browser/extensionEditor', 'ExtensionEditor' ); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index 8ef86d5cb5b..d763f2c2a37 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -3,1058 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/extensionActions'; import { localize } from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; -import { Throttler } from 'vs/base/common/async'; -import { IAction, Action } from 'vs/base/common/actions'; -import * as DOM from 'vs/base/browser/dom'; +import { Action } from 'vs/base/common/actions'; import severity from 'vs/base/common/severity'; import paths = require('vs/base/common/paths'); -import Event from 'vs/base/common/event'; -import { ActionItem, IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions'; -import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, ConfigurationKey } from '../common/extensions'; -import { LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMessageService } from 'vs/platform/message/common/message'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ToggleViewletAction } from 'vs/workbench/browser/viewlet'; -import { IViewletService } from 'vs/workbench/services/viewlet/common/viewletService'; -import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { Query } from '../common/extensionQuery'; import { remote } from 'electron'; -import { ExtensionsConfigurationInitialContent } from 'vs/workbench/parts/extensions/electron-browser/extensionsFileTemplate'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import URI from 'vs/base/common/uri'; -import { IExtensionService, IExtensionRuntimeService, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { IWindowService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; const dialog = remote.dialog; -export class InstallAction extends Action { - - private static InstallLabel = localize('installAction', "Install"); - private static InstallingLabel = localize('installing', "Installing"); - - private static Class = 'extension-action install'; - private static InstallingClass = 'extension-action install installing'; - - private disposables: IDisposable[] = []; - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this.update(); } - - constructor( - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService - ) { - super('extensions.install', InstallAction.InstallLabel, InstallAction.Class, false); - - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); - this.update(); - } - - private update(): void { - if (!this.extension || this.extension.type === LocalExtensionType.System) { - this.enabled = false; - this.class = InstallAction.Class; - this.label = InstallAction.InstallLabel; - return; - } - - this.enabled = this.extensionsWorkbenchService.canInstall(this.extension) && this.extension.state === ExtensionState.Uninstalled; - - if (this.extension.state === ExtensionState.Installing) { - this.label = InstallAction.InstallingLabel; - this.class = InstallAction.InstallingClass; - } else { - this.label = InstallAction.InstallLabel; - this.class = InstallAction.Class; - } - } - - run(): TPromise { - return this.extensionsWorkbenchService.install(this.extension); - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } -} - -export class UninstallAction extends Action { - - private static UninstallLabel = localize('uninstallAction', "Uninstall"); - private static UninstallingLabel = localize('Uninstalling', "Uninstalling"); - - private static UninstallClass = 'extension-action uninstall'; - private static UnInstallingClass = 'extension-action uninstall uninstalling'; - - private disposables: IDisposable[] = []; - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this.update(); } - - constructor( - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IMessageService private messageService: IMessageService, - @IInstantiationService private instantiationService: IInstantiationService - ) { - super('extensions.uninstall', UninstallAction.UninstallLabel, UninstallAction.UninstallClass, false); - - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); - this.update(); - } - - private update(): void { - if (!this.extension) { - this.enabled = false; - return; - } - - const state = this.extension.state; - - if (state === ExtensionState.Uninstalling) { - this.label = UninstallAction.UninstallingLabel; - this.class = UninstallAction.UnInstallingClass; - this.enabled = false; - return; - } - - this.label = UninstallAction.UninstallLabel; - this.class = UninstallAction.UninstallClass; - - const installedExtensions = this.extensionsWorkbenchService.local.filter(e => e.identifier === this.extension.identifier); - - if (!installedExtensions.length) { - this.enabled = false; - return; - } - - if (installedExtensions[0].type !== LocalExtensionType.User) { - this.enabled = false; - return; - } - - this.enabled = true; - } - - run(): TPromise { - return this.extensionsWorkbenchService.uninstall(this.extension); - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } -} - -export class CombinedInstallAction extends Action { - - private static NoExtensionClass = 'extension-action install no-extension'; - private installAction: InstallAction; - private uninstallAction: UninstallAction; - private disposables: IDisposable[] = []; - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { - this._extension = extension; - this.installAction.extension = extension; - this.uninstallAction.extension = extension; - } - - constructor( - @IInstantiationService instantiationService: IInstantiationService - ) { - super('extensions.combinedInstall', '', '', false); - - this.installAction = instantiationService.createInstance(InstallAction); - this.uninstallAction = instantiationService.createInstance(UninstallAction); - this.disposables.push(this.installAction, this.uninstallAction); - - this.installAction.onDidChange(this.update, this, this.disposables); - this.uninstallAction.onDidChange(this.update, this, this.disposables); - this.update(); - } - - private update(): void { - if (!this.extension || this.extension.type === LocalExtensionType.System) { - this.enabled = false; - this.class = CombinedInstallAction.NoExtensionClass; - } else if (this.installAction.enabled) { - this.enabled = true; - this.label = this.installAction.label; - this.class = this.installAction.class; - } else if (this.uninstallAction.enabled) { - this.enabled = true; - this.label = this.uninstallAction.label; - this.class = this.uninstallAction.class; - } else if (this.extension.state === ExtensionState.Installing) { - this.enabled = false; - this.label = this.installAction.label; - this.class = this.installAction.class; - } else if (this.extension.state === ExtensionState.Uninstalling) { - this.enabled = false; - this.label = this.uninstallAction.label; - this.class = this.uninstallAction.class; - } else { - this.enabled = false; - this.label = this.installAction.label; - this.class = this.installAction.class; - } - } - - run(): TPromise { - if (this.installAction.enabled) { - return this.installAction.run(); - } else if (this.uninstallAction.enabled) { - return this.uninstallAction.run(); - } - - return TPromise.as(null); - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } -} - -export class UpdateAction extends Action { - - private static EnabledClass = 'extension-action update'; - private static DisabledClass = `${UpdateAction.EnabledClass} disabled`; - - private disposables: IDisposable[] = []; - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this.update(); } - - constructor( - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService - ) { - super('extensions.update', localize('updateAction', "Update"), UpdateAction.DisabledClass, false); - - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); - this.update(); - } - - private update(): void { - if (!this.extension) { - this.enabled = false; - this.class = UpdateAction.DisabledClass; - return; - } - - if (this.extension.type !== LocalExtensionType.User) { - this.enabled = false; - this.class = UpdateAction.DisabledClass; - return; - } - - const canInstall = this.extensionsWorkbenchService.canInstall(this.extension); - const isInstalled = this.extension.state === ExtensionState.Installed; - - this.enabled = canInstall && isInstalled && this.extension.outdated; - this.class = this.enabled ? UpdateAction.EnabledClass : UpdateAction.DisabledClass; - } - - run(): TPromise { - return this.extensionsWorkbenchService.install(this.extension); - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } -} - -export interface IExtensionAction extends IAction { - extension: IExtension; -} - -export class DropDownMenuActionItem extends ActionItem { - - private disposables: IDisposable[] = []; - private _extension: IExtension; - - constructor(action: IAction, private menuActionGroups: IExtensionAction[][], private contextMenuService: IContextMenuService) { - super(null, action, { icon: true, label: true }); - for (const menuActions of menuActionGroups) { - this.disposables = [...this.disposables, ...menuActions]; - } - } - - get extension(): IExtension { return this._extension; } - - set extension(extension: IExtension) { - this._extension = extension; - for (const menuActions of this.menuActionGroups) { - for (const menuAction of menuActions) { - menuAction.extension = extension; - } - } - } - - public showMenu(): void { - const actions = this.getActions(); - let elementPosition = DOM.getDomNodePagePosition(this.builder.getHTMLElement()); - const anchor = { x: elementPosition.left, y: elementPosition.top + elementPosition.height + 10 }; - this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, - getActions: () => TPromise.wrap(actions) - }); - } - - private getActions(): IAction[] { - let actions = []; - for (const menuActions of this.menuActionGroups) { - const filtered = menuActions.filter(a => a.enabled); - if (filtered.length > 0) { - actions = [...actions, ...filtered, new Separator()]; - } - } - return actions.length ? actions.slice(0, actions.length - 1) : actions; - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } -} - -export class ManageExtensionActionItem extends DropDownMenuActionItem { - constructor( - action: IAction, - @IInstantiationService instantiationService: IInstantiationService, - @IContextMenuService contextMenuService: IContextMenuService) { - super(action, [ - [instantiationService.createInstance(EnableForWorkspaceAction, localize('enableForWorkspaceAction.label', "Enable (Workspace)")), - instantiationService.createInstance(EnableGloballyAction, localize('enableAlwaysAction.label', "Enable")), - instantiationService.createInstance(DisableForWorkspaceAction, localize('disableForWorkspaceAction.label', "Disable (Workspace)")), - instantiationService.createInstance(DisableGloballyAction, localize('disableAlwaysAction.label', "Disable"))], - [instantiationService.createInstance(UninstallAction)] - ], contextMenuService); - } -} - -export class ManageExtensionAction extends Action { - - static ID = 'extensions.manage'; - - private static Class = 'extension-action manage'; - private static NoExtensionClass = `${ManageExtensionAction.Class} no-extension`; - - private _actionItem: EnableActionItem; - get actionItem(): IActionItem { return this._actionItem; } - - private disposables: IDisposable[] = []; - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this._actionItem.extension = extension; this.update(); } - - constructor( - @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionRuntimeService private extensionRuntimeService: IExtensionRuntimeService, - @IInstantiationService private instantiationService: IInstantiationService - ) { - super(ManageExtensionAction.ID); - - this._actionItem = this.instantiationService.createInstance(ManageExtensionActionItem, this); - this.disposables.push(this._actionItem); - - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); - this.update(); - } - - private update(): void { - this.class = ManageExtensionAction.NoExtensionClass; - this.tooltip = ''; - this.enabled = false; - if (this.extension) { - const state = this.extension.state; - this.enabled = state === ExtensionState.Installed; - this.class = this.enabled || state === ExtensionState.Uninstalling ? ManageExtensionAction.Class : ManageExtensionAction.NoExtensionClass; - this.tooltip = state === ExtensionState.Uninstalling ? localize('ManageExtensionAction.uninstallingTooltip', "Uninstalling") : ''; - } - } - - public run(): TPromise { - this._actionItem.showMenu(); - return TPromise.wrap(null); - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } -} - -export class EnableForWorkspaceAction extends Action implements IExtensionAction { - - static ID = 'extensions.enableForWorkspace'; - static LABEL = localize('enableForWorkspaceAction', "Workspace"); - - private disposables: IDisposable[] = []; - - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this.update(); } - - constructor(label: string, - @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionRuntimeService private extensionRuntimeService: IExtensionRuntimeService, - @IInstantiationService private instantiationService: IInstantiationService - ) { - super(EnableForWorkspaceAction.ID, label); - - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); - this.update(); - } - - private update(): void { - this.enabled = false; - if (this.extension) { - this.enabled = !this.extension.disabledGlobally && this.extension.disabledForWorkspace && this.extensionRuntimeService.canEnable(this.extension.identifier); - } - } - - run(): TPromise { - return this.extensionsWorkbenchService.setEnablement(this.extension, true, true); - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } -} - -export class EnableGloballyAction extends Action implements IExtensionAction { - - static ID = 'extensions.enableGlobally'; - static LABEL = localize('enableGloballyAction', "Always"); - - private disposables: IDisposable[] = []; - - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this.update(); } - - constructor(label: string, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionRuntimeService private extensionRuntimeService: IExtensionRuntimeService, - @IInstantiationService private instantiationService: IInstantiationService - ) { - super(EnableGloballyAction.ID, label); - - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); - this.update(); - } - - private update(): void { - this.enabled = false; - if (this.extension) { - this.enabled = this.extension.disabledGlobally && this.extensionRuntimeService.canEnable(this.extension.identifier); - } - } - - run(): TPromise { - return this.extensionsWorkbenchService.setEnablement(this.extension, true, false); - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } -} - -export class EnableAction extends Action { - - static ID = 'extensions.enable'; - private static EnabledClass = 'extension-action enable'; - private static DisabledClass = `${EnableAction.EnabledClass} disabled`; - - private disposables: IDisposable[] = []; - - private _actionItem: EnableActionItem; - get actionItem(): IActionItem { return this._actionItem; } - - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this._actionItem.extension = extension; this.update(); } - - - constructor( - @IInstantiationService private instantiationService: IInstantiationService, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionRuntimeService private extensionRuntimeService: IExtensionRuntimeService - ) { - super(EnableAction.ID, localize('enableAction', "Enable"), EnableAction.DisabledClass, false); - - this._actionItem = this.instantiationService.createInstance(EnableActionItem, this); - this.disposables.push(this._actionItem); - - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); - this.update(); - } - - private update(): void { - if (!this.extension) { - this.enabled = false; - this.class = EnableAction.DisabledClass; - return; - } - - this.enabled = this.extension.state === ExtensionState.Installed && (this.extension.disabledGlobally || this.extension.disabledForWorkspace) && this.extensionRuntimeService.canEnable(this.extension.identifier); - this.class = this.enabled ? EnableAction.EnabledClass : EnableAction.DisabledClass; - } - - public run(): TPromise { - this._actionItem.showMenu(); - return TPromise.wrap(null); - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } - -} - -export class DisableForWorkspaceAction extends Action implements IExtensionAction { - - static ID = 'extensions.disableForWorkspace'; - static LABEL = localize('disableForWorkspaceAction', "Workspace"); - - private disposables: IDisposable[] = []; - - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this.update(); } - - constructor(label: string, - @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IInstantiationService private instantiationService: IInstantiationService - ) { - super(DisableForWorkspaceAction.ID, label); - - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); - this.update(); - } - - private update(): void { - this.enabled = false; - if (this.extension && this.workspaceContextService.getWorkspace()) { - this.enabled = this.extension.type !== LocalExtensionType.System && !this.extension.disabledGlobally && !this.extension.disabledForWorkspace; - } - } - - run(): TPromise { - return this.extensionsWorkbenchService.setEnablement(this.extension, false, true); - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } -} - -export class DisableGloballyAction extends Action implements IExtensionAction { - - static ID = 'extensions.disableGlobally'; - static LABEL = localize('disableGloballyAction', "Always"); - - private disposables: IDisposable[] = []; - - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this.update(); } - - constructor(label: string, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IInstantiationService private instantiationService: IInstantiationService - ) { - super(DisableGloballyAction.ID, label); - - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); - this.update(); - } - - private update(): void { - this.enabled = false; - if (this.extension) { - this.enabled = this.extension.type !== LocalExtensionType.System && !this.extension.disabledGlobally && !this.extension.disabledForWorkspace; - } - } - - run(): TPromise { - return this.extensionsWorkbenchService.setEnablement(this.extension, false, false); - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } -} - -export class DisableAction extends Action { - - static ID = 'extensions.disable'; - - private static EnabledClass = 'extension-action disable'; - private static DisabledClass = `${DisableAction.EnabledClass} disabled`; - - private disposables: IDisposable[] = []; - private _actionItem: DisableActionItem; - get actionItem(): IActionItem { return this._actionItem; } - - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this._actionItem.extension = extension; this.update(); } - - - constructor( - @IInstantiationService private instantiationService: IInstantiationService, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - ) { - super(DisableAction.ID, localize('disableAction', "Disable"), DisableAction.DisabledClass, false); - this._actionItem = this.instantiationService.createInstance(DisableActionItem, this); - this.disposables.push(this._actionItem); - - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); - this.update(); - } - - private update(): void { - if (!this.extension) { - this.enabled = false; - this.class = DisableAction.DisabledClass; - return; - } - - this.enabled = this.extension.state === ExtensionState.Installed && this.extension.type !== LocalExtensionType.System && !this.extension.disabledGlobally && !this.extension.disabledForWorkspace; - this.class = this.enabled ? DisableAction.EnabledClass : DisableAction.DisabledClass; - } - - public run(): TPromise { - this._actionItem.showMenu(); - return TPromise.wrap(null); - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } -} - -export class EnableActionItem extends DropDownMenuActionItem { - constructor( - action: IAction, - @IInstantiationService instantiationService: IInstantiationService, - @IContextMenuService contextMenuService: IContextMenuService) { - super(action, [[instantiationService.createInstance(EnableForWorkspaceAction, EnableForWorkspaceAction.LABEL), instantiationService.createInstance(EnableGloballyAction, EnableGloballyAction.LABEL)]], contextMenuService); - } -} - -export class DisableActionItem extends DropDownMenuActionItem { - constructor( - action: IAction, - @IInstantiationService instantiationService: IInstantiationService, - @IContextMenuService contextMenuService: IContextMenuService) { - super(action, [[instantiationService.createInstance(DisableForWorkspaceAction, DisableForWorkspaceAction.LABEL), instantiationService.createInstance(DisableGloballyAction, DisableGloballyAction.LABEL)]], contextMenuService); - } -} - -export class ReloadAction extends Action { - - private static EnabledClass = 'extension-action reload'; - private static DisabledClass = `${ReloadAction.EnabledClass} disabled`; - - private disposables: IDisposable[] = []; - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this.update(); } - - private reloadMessaage: string = ''; - private throttler: Throttler; - - constructor( - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IMessageService private messageService: IMessageService, - @IInstantiationService private instantiationService: IInstantiationService, - @IExtensionService private extensionService: IExtensionService - ) { - super('extensions.reload', localize('reloadAction', "Reload"), ReloadAction.DisabledClass, false); - this.throttler = new Throttler(); - - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); - this.update(); - } - - private update(): void { - this.throttler.queue(() => { - this.enabled = false; - this.tooltip = ''; - this.reloadMessaage = ''; - if (!this.extension) { - return TPromise.wrap(null); - } - const state = this.extension.state; - if (state === ExtensionState.Installing || state === ExtensionState.Uninstalling) { - return TPromise.wrap(null); - } - return this.extensionService.getExtensions() - .then(runningExtensions => this.computeReloadState(runningExtensions)); - }).done(() => { - this.class = this.enabled ? ReloadAction.EnabledClass : ReloadAction.DisabledClass; - }); - } - - private computeReloadState(runningExtensions: IExtensionDescription[]): void { - const isInstalled = this.extensionsWorkbenchService.local.some(e => e.identifier === this.extension.identifier); - const isUninstalled = this.extension.state === ExtensionState.Uninstalled; - const isDisabled = this.extension.disabledForWorkspace || this.extension.disabledGlobally; - - const filteredExtensions = runningExtensions.filter(e => e.id === this.extension.identifier); - const isExtensionRunning = filteredExtensions.length > 0; - const isDifferentVersionRunning = filteredExtensions.length > 0 && this.extension.version !== filteredExtensions[0].version; - - if (isInstalled) { - if (isDifferentVersionRunning && !isDisabled) { - // Requires reload to run the updated extension - this.enabled = true; - this.tooltip = localize('postUpdateTooltip', "Reload to update"); - this.reloadMessaage = localize('postUpdateMessage', "Reload this window to activate the updated extension '{0}'?", this.extension.displayName); - return; - } - - if (!isExtensionRunning && !isDisabled) { - // Requires reload to enable the extension - this.enabled = true; - this.tooltip = localize('postEnableTooltip', "Reload to activate"); - this.reloadMessaage = localize('postEnableMessage', "Reload this window to activate the extension '{0}'?", this.extension.displayName); - return; - } - - if (isExtensionRunning && isDisabled) { - // Requires reload to disable the extension - this.enabled = true; - this.tooltip = localize('postDisableTooltip', "Reload to deactivate"); - this.reloadMessaage = localize('postDisableMessage', "Reload this window to deactivate the extension '{0}'?", this.extension.displayName); - return; - } - return; - } - - if (isUninstalled && isExtensionRunning) { - // Requires reload to deactivate the extension - this.enabled = true; - this.tooltip = localize('postUninstallTooltip', "Reload to deactivate"); - this.reloadMessaage = localize('postUninstallMessage', "Reload this window to deactivate the uninstalled extension '{0}'?", this.extension.displayName); - return; - } - } - - run(): TPromise { - if (window.confirm(this.reloadMessaage)) { - this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, localize('reloadNow', "Reload Now")).run(); - } - return TPromise.wrap(null); - } -} - -export class UpdateAllAction extends Action { - - static ID = 'workbench.extensions.action.updateAllExtensions'; - static LABEL = localize('updateAll', "Update All Extensions"); - - private disposables: IDisposable[] = []; - - constructor( - id = UpdateAllAction.ID, - label = UpdateAllAction.LABEL, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService - ) { - super(id, label, '', false); - - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); - this.update(); - } - - private get outdated(): IExtension[] { - return this.extensionsWorkbenchService.local.filter(e => e.outdated && e.state !== ExtensionState.Installing); - } - - private update(): void { - this.enabled = this.outdated.length > 0; - } - - run(): TPromise { - return TPromise.join(this.outdated.map(e => this.extensionsWorkbenchService.install(e))); - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } -} - -export class OpenExtensionsViewletAction extends ToggleViewletAction { - - static ID = VIEWLET_ID; - static LABEL = localize('toggleExtensionsViewlet', "Show Extensions"); - - constructor( - id: string, - label: string, - @IViewletService viewletService: IViewletService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService - ) { - super(id, label, VIEWLET_ID, viewletService, editorService); - } -} - -export class InstallExtensionsAction extends OpenExtensionsViewletAction { - static ID = 'workbench.extensions.action.installExtensions'; - static LABEL = localize('installExtensions', "Install Extensions"); -} - -export class ShowInstalledExtensionsAction extends Action { - - static ID = 'workbench.extensions.action.showInstalledExtensions'; - static LABEL = localize('showInstalledExtensions', "Show Installed Extensions"); - - constructor( - id: string, - label: string, - @IViewletService private viewletService: IViewletService, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService - ) { - super(id, label, 'clear-extensions', true); - } - - run(): TPromise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - viewlet.search(''); - viewlet.focus(); - }); - } -} - -export class ShowDisabledExtensionsAction extends Action { - - static ID = 'workbench.extensions.action.showDisabledExtensions'; - static LABEL = localize('showDisabledExtensions', "Show Disabled Extensions"); - - constructor( - id: string, - label: string, - @IViewletService private viewletService: IViewletService, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService - ) { - super(id, label, 'null', true); - } - - run(): TPromise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - viewlet.search('@disabled '); - viewlet.focus(); - }); - } -} - -export class ClearExtensionsInputAction extends ShowInstalledExtensionsAction { - - static ID = 'workbench.extensions.action.clearExtensionsInput'; - static LABEL = localize('clearExtensionsInput', "Clear Extensions Input"); - - private disposables: IDisposable[] = []; - - constructor( - id: string, - label: string, - onSearchChange: Event, - @IViewletService viewletService: IViewletService, - @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService - ) { - super(id, label, viewletService, extensionsWorkbenchService); - this.enabled = false; - onSearchChange(this.onSearchChange, this, this.disposables); - } - - private onSearchChange(value: string): void { - this.enabled = !!value; - } - - dispose(): void { - this.disposables = dispose(this.disposables); - } -} - -export class ShowOutdatedExtensionsAction extends Action { - - static ID = 'workbench.extensions.action.listOutdatedExtensions'; - static LABEL = localize('showOutdatedExtensions', "Show Outdated Extensions"); - - constructor( - id: string, - label: string, - @IViewletService private viewletService: IViewletService - ) { - super(id, label, null, true); - } - - run(): TPromise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - viewlet.search('@outdated '); - viewlet.focus(); - }); - } - - protected isEnabled(): boolean { - return true; - } -} - -export class ShowPopularExtensionsAction extends Action { - - static ID = 'workbench.extensions.action.showPopularExtensions'; - static LABEL = localize('showPopularExtensions', "Show Popular Extensions"); - - constructor( - id: string, - label: string, - @IViewletService private viewletService: IViewletService - ) { - super(id, label, null, true); - } - - run(): TPromise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - viewlet.search('@sort:installs '); - viewlet.focus(); - }); - } - - protected isEnabled(): boolean { - return true; - } -} - -export class ShowRecommendedExtensionsAction extends Action { - - static ID = 'workbench.extensions.action.showRecommendedExtensions'; - static LABEL = localize('showRecommendedExtensions', "Show Recommended Extensions"); - - constructor( - id: string, - label: string, - @IViewletService private viewletService: IViewletService - ) { - super(id, label, null, true); - } - - run(): TPromise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - viewlet.search('@recommended '); - viewlet.focus(); - }); - } - - protected isEnabled(): boolean { - return true; - } -} - -export class ShowWorkspaceRecommendedExtensionsAction extends Action { - - static ID = 'workbench.extensions.action.showWorkspaceRecommendedExtensions'; - static LABEL = localize('showWorkspaceRecommendedExtensions', "Show Workspace Recommended Extensions"); - - constructor( - id: string, - label: string, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IViewletService private viewletService: IViewletService - ) { - super(id, label, null, !!contextService.getWorkspace()); - } - - run(): TPromise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - viewlet.search('@recommended:workspace '); - viewlet.focus(); - }); - } - - protected isEnabled(): boolean { - return true; - } -} - -export class ChangeSortAction extends Action { - - private query: Query; - private disposables: IDisposable[] = []; - - constructor( - id: string, - label: string, - onSearchChange: Event, - private sortBy: string, - private sortOrder: string, - @IViewletService private viewletService: IViewletService - ) { - super(id, label, null, true); - - if (sortBy === undefined && sortOrder === undefined) { - throw new Error('bad arguments'); - } - - this.query = Query.parse(''); - this.enabled = false; - onSearchChange(this.onSearchChange, this, this.disposables); - } - - private onSearchChange(value: string): void { - const query = Query.parse(value); - this.query = new Query(query.value, this.sortBy || query.sortBy, this.sortOrder || query.sortOrder); - this.enabled = value && this.query.isValid() && !this.query.equals(query); - } - - run(): TPromise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - viewlet.search(this.query.toString()); - viewlet.focus(); - }); - } - - protected isEnabled(): boolean { - return true; - } -} - export class OpenExtensionsFolderAction extends Action { static ID = 'workbench.extensions.action.openExtensionsFolder'; @@ -1063,7 +26,7 @@ export class OpenExtensionsFolderAction extends Action { constructor( id: string, label: string, - @IWindowService private windowService: IWindowService, + @IWindowIPCService private windowService: IWindowIPCService, @IEnvironmentService private environmentService: IEnvironmentService ) { super(id, label, null, true); @@ -1081,57 +44,6 @@ export class OpenExtensionsFolderAction extends Action { } } -export class ConfigureWorkspaceRecommendedExtensionsAction extends Action { - - static ID = 'workbench.extensions.action.configureWorkspaceRecommendedExtensions'; - static LABEL = localize('configureWorkspaceRecommendedExtensions', "Configure Recommended Extensions (Workspace)"); - - constructor( - id: string, - label: string, - @IFileService private fileService: IFileService, - @IWorkspaceContextService private contextService: IWorkspaceContextService, - @IExtensionsWorkbenchService private extensionsService: IExtensionsWorkbenchService, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IMessageService private messageService: IMessageService - ) { - super(id, label, null, !!contextService.getWorkspace()); - } - - public run(event: any): TPromise { - return this.openExtensionsFile(); - } - - private openExtensionsFile(): TPromise { - if (!this.contextService.getWorkspace()) { - this.messageService.show(severity.Info, localize('ConfigureWorkspaceRecommendations.noWorkspace', 'Recommendations are only available on a workspace folder.')); - return TPromise.as(undefined); - } - - return this.getOrCreateExtensionsFile().then(value => { - return this.editorService.openEditor({ - resource: value.extensionsFileResource, - options: { - forceOpen: true, - pinned: value.created - }, - }); - }, (error) => TPromise.wrapError(new Error(localize('OpenExtensionsFile.failed', "Unable to create 'extensions.json' file inside the '.vscode' folder ({0}).", error)))); - } - - private getOrCreateExtensionsFile(): TPromise<{ created: boolean, extensionsFileResource: URI }> { - const extensionsFileResource = URI.file(paths.join(this.contextService.getWorkspace().resource.fsPath, '.vscode', `${ConfigurationKey}.json`)); - - return this.fileService.resolveContent(extensionsFileResource).then(content => { - return { created: false, extensionsFileResource }; - }, err => { - return this.fileService.updateContent(extensionsFileResource, ExtensionsConfigurationInitialContent).then(() => { - return { created: true, extensionsFileResource }; - }); - }); - } -} - export class InstallVSIXAction extends Action { static ID = 'workbench.extensions.action.installVSIX'; @@ -1167,155 +79,4 @@ export class InstallVSIXAction extends Action { ); }); } -} - -export class BuiltinStatusLabelAction extends Action { - - private static Class = 'extension-action built-in-status'; - - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this.update(); } - - constructor() { - super('extensions.install', localize('builtin', "Built-in"), '', false); - } - - private update(): void { - if (this.extension && this.extension.type === LocalExtensionType.System) { - this.class = `${BuiltinStatusLabelAction.Class} system`; - } else { - this.class = `${BuiltinStatusLabelAction.Class} user`; - } - } - - run(): TPromise { - return TPromise.as(null); - } -} - -export class DisableAllAction extends Action { - - static ID = 'workbench.extensions.action.disableAll'; - static LABEL = localize('disableAll', "Disable All Installed Extensions"); - - private disposables: IDisposable[] = []; - - constructor( - id: string = DisableAllAction.ID, label: string = DisableAllAction.LABEL, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionRuntimeService private extensionRuntimeService: IExtensionRuntimeService - ) { - super(id, label); - this.update(); - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); - } - - private update(): void { - this.enabled = this.extensionsWorkbenchService.local.some(e => e.type === LocalExtensionType.User && !e.disabledForWorkspace && !e.disabledGlobally); - } - - run(): TPromise { - return TPromise.join(this.extensionsWorkbenchService.local.map(e => this.extensionsWorkbenchService.setEnablement(e, false))); - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } -} - -export class DisableAllWorkpsaceAction extends Action { - - static ID = 'workbench.extensions.action.disableAllWorkspace'; - static LABEL = localize('disableAllWorkspace', "Disable All Installed Extensions for this Workspace"); - - private disposables: IDisposable[] = []; - - constructor( - id: string = DisableAllWorkpsaceAction.ID, label: string = DisableAllWorkpsaceAction.LABEL, - @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionRuntimeService private extensionRuntimeService: IExtensionRuntimeService - ) { - super(id, label); - this.update(); - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); - } - - private update(): void { - this.enabled = !!this.workspaceContextService.getWorkspace() && this.extensionsWorkbenchService.local.some(e => e.type === LocalExtensionType.User && !e.disabledForWorkspace && !e.disabledGlobally); - } - - run(): TPromise { - return TPromise.join(this.extensionsWorkbenchService.local.map(e => this.extensionsWorkbenchService.setEnablement(e, false, true))); - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } -} - -export class EnableAllAction extends Action { - - static ID = 'workbench.extensions.action.enableAll'; - static LABEL = localize('enableAll', "Enable All Installed Extensions"); - - private disposables: IDisposable[] = []; - - constructor( - id: string = EnableAllAction.ID, label: string = EnableAllAction.LABEL, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionRuntimeService private extensionRuntimeService: IExtensionRuntimeService - ) { - super(id, label); - this.update(); - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); - } - - private update(): void { - this.enabled = this.extensionsWorkbenchService.local.some(e => this.extensionRuntimeService.canEnable(e.identifier) && e.disabledGlobally); - } - - run(): TPromise { - return TPromise.join(this.extensionsWorkbenchService.local.map(e => this.extensionsWorkbenchService.setEnablement(e, true))); - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } -} - -export class EnableAllWorkpsaceAction extends Action { - - static ID = 'workbench.extensions.action.enableAllWorkspace'; - static LABEL = localize('enableAllWorkspace', "Enable All Installed Extensions for this Workspace"); - - private disposables: IDisposable[] = []; - - constructor( - id: string = EnableAllWorkpsaceAction.ID, label: string = EnableAllWorkpsaceAction.LABEL, - @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionRuntimeService private extensionRuntimeService: IExtensionRuntimeService - ) { - super(id, label); - this.update(); - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); - } - - private update(): void { - this.enabled = !!this.workspaceContextService.getWorkspace() && this.extensionsWorkbenchService.local.some(e => this.extensionRuntimeService.canEnable(e.identifier) && !e.disabledGlobally && e.disabledForWorkspace); - } - - run(): TPromise { - return TPromise.join(this.extensionsWorkbenchService.local.map(e => this.extensionsWorkbenchService.setEnablement(e, true, true))); - } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } } \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index 05052c5a8bf..fb94987213e 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -28,12 +28,13 @@ import { PagedModel, IPagedModel } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { PagedList } from 'vs/base/browser/ui/list/listPaging'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Delegate, Renderer } from './extensionsList'; +import { Delegate, Renderer } from 'vs/workbench/parts/extensions/browser/extensionsList'; import { IExtensionsWorkbenchService, IExtension, IExtensionsViewlet, VIEWLET_ID, ExtensionState } from '../common/extensions'; import { ShowRecommendedExtensionsAction, ShowWorkspaceRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowInstalledExtensionsAction, ShowDisabledExtensionsAction, - ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, InstallVSIXAction -} from './extensionsActions'; + ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction +} from 'vs/workbench/parts/extensions/browser/extensionsActions'; +import { InstallVSIXAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; import { IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, SortBy, SortOrder, IQueryOptions, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput'; import { Query } from '../common/extensionQuery'; diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 3595ad29503..29739425a0f 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -22,7 +22,7 @@ import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, IExtensionManifest, - InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent + InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionTelemetry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -34,7 +34,6 @@ import { IExtension, IExtensionDependencies, ExtensionState, IExtensionsWorkbenc import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IURLService } from 'vs/platform/url/common/url'; import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput'; -import { IExtensionRuntimeService } from 'vs/platform/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; interface IExtensionStateProvider { @@ -139,7 +138,7 @@ class Extension implements IExtension { } private get defaultIconUrl(): string { - return require.toUrl('./media/defaultIcon.png'); + return require.toUrl('../browser/media/defaultIcon.png'); } get licenseUrl(): string { @@ -299,10 +298,6 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { private autoUpdateDelayer: ThrottledDelayer; private disposables: IDisposable[] = []; - // TODO: @sandy - Remove these when IExtensionRuntimeService exposes sync API to get extensions. - private newlyInstalled: Extension[] = []; - private unInstalled: Extension[] = []; - private _onChange: Emitter = new Emitter(); get onChange(): Event { return this._onChange.event; } @@ -315,7 +310,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { @ITelemetryService private telemetryService: ITelemetryService, @IMessageService private messageService: IMessageService, @IURLService urlService: IURLService, - @IExtensionRuntimeService private extensionRuntimeService: IExtensionRuntimeService, + @IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService, @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, ) { this.stateProvider = ext => this.getExtensionState(ext); @@ -324,6 +319,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { extensionService.onDidInstallExtension(this.onDidInstallExtension, this, this.disposables); extensionService.onUninstallExtension(this.onUninstallExtension, this, this.disposables); extensionService.onDidUninstallExtension(this.onDidUninstallExtension, this, this.disposables); + extensionEnablementService.onEnablementChanged(this.onEnablementChanged, this, this.disposables); this.syncDelayer = new ThrottledDelayer(ExtensionsWorkbenchService.SyncPeriod); this.autoUpdateDelayer = new ThrottledDelayer(1000); @@ -346,8 +342,8 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { queryLocal(): TPromise { return this.extensionService.getInstalled().then(result => { const installedById = index(this.installed, e => e.local.id); - const globallyDisabledExtensions = this.extensionRuntimeService.getGloballyDisabledExtensions(); - const workspaceDisabledExtensions = this.extensionRuntimeService.getWorkspaceDisabledExtensions(); + const globallyDisabledExtensions = this.extensionEnablementService.getGloballyDisabledExtensions(); + const workspaceDisabledExtensions = this.extensionEnablementService.getWorkspaceDisabledExtensions(); this.installed = result.map(local => { const extension = installedById[local.id] || new Extension(this.galleryService, this.stateProvider, local); extension.local = local; @@ -489,12 +485,6 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { } return this.doSetEnablement(extension, enable, workspace).then(reload => { - // update the disable flags - const globallyDisabledExtensions = this.extensionRuntimeService.getGloballyDisabledExtensions(); - const workspaceDisabledExtensions = this.extensionRuntimeService.getWorkspaceDisabledExtensions(); - extension.disabledGlobally = globallyDisabledExtensions.indexOf(extension.identifier) !== -1; - extension.disabledForWorkspace = workspaceDisabledExtensions.indexOf(extension.identifier) !== -1; - this.telemetryService.publicLog(enable ? 'extension:enable' : 'extension:disable', extension.telemetryData); this._onChange.fire(); }); @@ -517,14 +507,14 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { private doSetEnablement(extension: IExtension, enable: boolean, workspace: boolean): TPromise { if (workspace) { - return this.extensionRuntimeService.setEnablement(extension.identifier, enable, workspace); + return this.extensionEnablementService.setEnablement(extension.identifier, enable, workspace); } - const globalElablement = this.extensionRuntimeService.setEnablement(extension.identifier, enable, false); + const globalElablement = this.extensionEnablementService.setEnablement(extension.identifier, enable, false); if (!this.workspaceContextService.getWorkspace()) { return globalElablement; } - return TPromise.join([globalElablement, this.extensionRuntimeService.setEnablement(extension.identifier, enable, true)]) + return TPromise.join([globalElablement, this.extensionEnablementService.setEnablement(extension.identifier, enable, true)]) .then(values => values[0] || values[1]); } @@ -560,7 +550,6 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { this.installing = this.installing.filter(e => e.id !== id); if (!error) { - this.newlyInstalled.push(extension); extension.local = local; const galleryId = local.metadata && local.metadata.id; @@ -599,10 +588,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { } private onDidUninstallExtension({id, error}: DidUninstallExtensionEvent): void { - let newlyInstalled = false; if (!error) { - newlyInstalled = this.newlyInstalled.filter(e => e.local.id === id).length > 0; - this.newlyInstalled = this.newlyInstalled.filter(e => e.local.id !== id); this.installed = this.installed.filter(e => e.local.id !== id); } @@ -613,17 +599,22 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { } if (!error) { - this.unInstalled.push(uninstalling.extension); - const globallyDisabledExtensions = this.extensionRuntimeService.getGloballyDisabledExtensions(); - const workspaceDisabledExtensions = this.extensionRuntimeService.getWorkspaceDisabledExtensions(); - uninstalling.extension.disabledGlobally = globallyDisabledExtensions.indexOf(uninstalling.extension.identifier) !== -1; - uninstalling.extension.disabledForWorkspace = workspaceDisabledExtensions.indexOf(uninstalling.extension.identifier) !== -1; this.reportTelemetry(uninstalling, true); } this._onChange.fire(); } + private onEnablementChanged(extensionIdentifier: string) { + const [extension] = this.local.filter(e => e.identifier === extensionIdentifier); + if (extension) { + const globallyDisabledExtensions = this.extensionEnablementService.getGloballyDisabledExtensions(); + const workspaceDisabledExtensions = this.extensionEnablementService.getWorkspaceDisabledExtensions(); + extension.disabledGlobally = globallyDisabledExtensions.indexOf(extension.identifier) !== -1; + extension.disabledForWorkspace = workspaceDisabledExtensions.indexOf(extension.identifier) !== -1; + } + } + private getExtensionState(extension: Extension): ExtensionState { if (extension.gallery && this.installing.some(e => e.extension.gallery.id === extension.gallery.id)) { return ExtensionState.Installing; diff --git a/src/vs/workbench/parts/files/browser/media/explorerviewlet.css b/src/vs/workbench/parts/files/browser/media/explorerviewlet.css index 8faf4014dd8..5cf0f855b9e 100644 --- a/src/vs/workbench/parts/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/parts/files/browser/media/explorerviewlet.css @@ -122,9 +122,8 @@ .explorer-viewlet:lang(ko) .explorer-open-editors .monaco-tree .monaco-tree-row .editor-group { font-weight: normal; } /* Disable tree twistie */ -.explorer-viewlet .explorer-open-editors > .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content:after, .explorer-viewlet .explorer-open-editors > .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content:before { - border: none; + display: none; } /* High Contrast Theming */ diff --git a/src/vs/workbench/parts/files/electron-browser/dirtyFilesTracker.ts b/src/vs/workbench/parts/files/electron-browser/dirtyFilesTracker.ts index b0bd17d7111..88443df9bde 100644 --- a/src/vs/workbench/parts/files/electron-browser/dirtyFilesTracker.ts +++ b/src/vs/workbench/parts/files/electron-browser/dirtyFilesTracker.ts @@ -11,7 +11,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { VIEWLET_ID } from 'vs/workbench/parts/files/common/files'; import { TextFileModelChangeEvent, ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; import { platform, Platform } from 'vs/base/common/platform'; -import { IWindowService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; import { Position } from 'vs/platform/editor/common/editor'; import { IEditorStacksModel } from 'vs/workbench/common/editor'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; @@ -41,7 +41,7 @@ export class DirtyFilesTracker implements IWorkbenchContribution { @IEditorGroupService editorGroupService: IEditorGroupService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IActivityService private activityService: IActivityService, - @IWindowService private windowService: IWindowService, + @IWindowIPCService private windowService: IWindowIPCService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService ) { this.toUnbind = []; diff --git a/src/vs/workbench/parts/files/electron-browser/electronFileActions.ts b/src/vs/workbench/parts/files/electron-browser/electronFileActions.ts index ccd67bbb34f..d973c906835 100644 --- a/src/vs/workbench/parts/files/electron-browser/electronFileActions.ts +++ b/src/vs/workbench/parts/files/electron-browser/electronFileActions.ts @@ -17,7 +17,8 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi import { asFileEditorInput } from 'vs/workbench/common/editor'; import { IMessageService } from 'vs/platform/message/common/message'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; -import { IWindowService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { ipcRenderer as ipc, clipboard } from 'electron'; @@ -26,7 +27,7 @@ export class RevealInOSAction extends Action { constructor( resource: uri, - @IWindowService private windowService: IWindowService + @IWindowIPCService private windowService: IWindowIPCService ) { super('workbench.action.files.revealInWindows', platform.isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : (platform.isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder"))); @@ -51,7 +52,7 @@ export class GlobalRevealInOSAction extends Action { id: string, label: string, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IWindowService private windowService: IWindowService, + @IWindowIPCService private windowService: IWindowIPCService, @IMessageService private messageService: IMessageService ) { super(id, label); @@ -116,63 +117,65 @@ export class GlobalCopyPathAction extends Action { } } -export class BaseOpenAction extends Action { - - private ipcMsg: string; - - constructor(id: string, label: string, ipcMsg: string) { - super(id, label); - - this.ipcMsg = ipcMsg; - } - - public run(): TPromise { - ipc.send(this.ipcMsg); // Handle in browser process - - return TPromise.as(true); - } -} - export class OpenFileAction extends Action { - public static ID = 'workbench.action.files.openFile'; - public static LABEL = nls.localize('openFile', "Open File..."); + static ID = 'workbench.action.files.openFile'; + static LABEL = nls.localize('openFile', "Open File..."); - constructor(id: string, label: string, @IWorkbenchEditorService private editorService: IWorkbenchEditorService) { + constructor( + id: string, + label: string, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IWindowService private windowService: IWindowService + ) { super(id, label); } - public run(): TPromise { + run(): TPromise { const fileInput = asFileEditorInput(this.editorService.getActiveEditorInput(), true); // Handle in browser process if (fileInput) { - ipc.send('vscode:openFilePicker', false, paths.dirname(fileInput.getResource().fsPath)); - } else { - ipc.send('vscode:openFilePicker'); + return this.windowService.openFilePicker(false, paths.dirname(fileInput.getResource().fsPath)); } - return TPromise.as(true); + return this.windowService.openFilePicker(); } } -export class OpenFolderAction extends BaseOpenAction { +export class OpenFolderAction extends Action { - public static ID = 'workbench.action.files.openFolder'; - public static LABEL = nls.localize('openFolder', "Open Folder..."); + static ID = 'workbench.action.files.openFolder'; + static LABEL = nls.localize('openFolder', "Open Folder..."); - constructor(id: string, label: string) { - super(id, label, 'vscode:openFolderPicker'); + constructor( + id: string, + label: string, + @IWindowService private windowService: IWindowService + ) { + super(id, label); + } + + run(): TPromise { + return this.windowService.openFolderPicker(); } } -export class OpenFileFolderAction extends BaseOpenAction { +export class OpenFileFolderAction extends Action { - public static ID = 'workbench.action.files.openFileFolder'; - public static LABEL = nls.localize('openFileFolder', "Open..."); + static ID = 'workbench.action.files.openFileFolder'; + static LABEL = nls.localize('openFileFolder', "Open..."); - constructor(id: string, label: string) { - super(id, label, 'vscode:openFileFolderPicker'); + constructor( + id: string, + label: string, + @IWindowService private windowService: IWindowService + ) { + super(id, label); + } + + run(): TPromise { + return this.windowService.openFileFolderPicker(); } } diff --git a/src/vs/workbench/parts/files/electron-browser/files.electron.contribution.ts b/src/vs/workbench/parts/files/electron-browser/files.electron.contribution.ts index 09b6dbf8482..334f527af2b 100644 --- a/src/vs/workbench/parts/files/electron-browser/files.electron.contribution.ts +++ b/src/vs/workbench/parts/files/electron-browser/files.electron.contribution.ts @@ -19,6 +19,9 @@ import { DirtyFilesTracker } from 'vs/workbench/parts/files/electron-browser/dir import { OpenFolderAction, OpenFileAction, OpenFileFolderAction, ShowOpenedFileInNewWindow, GlobalRevealInOSAction, GlobalCopyPathAction, CopyPathAction, RevealInOSAction } from 'vs/workbench/parts/files/electron-browser/electronFileActions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IWindowService } from 'vs/platform/windows/common/windows'; class FileViewerActionContributor extends ActionBarContributor { @@ -76,4 +79,10 @@ actionsRegistry.registerActionBarContributor(Scope.VIEWER, FileViewerActionContr // Register Dirty Files Tracker (Registry.as(WorkbenchExtensions.Workbench)).registerWorkbenchContribution( DirtyFilesTracker -); \ No newline at end of file +); + +// Register Commands +CommandsRegistry.registerCommand('_files.openFolderPicker', (accessor: ServicesAccessor, forceNewWindow: boolean) => { + const windowService = accessor.get(IWindowService); + windowService.openFolderPicker(forceNewWindow); +}); \ No newline at end of file diff --git a/src/vs/workbench/parts/git/browser/gitActions.ts b/src/vs/workbench/parts/git/browser/gitActions.ts index 5288018c6ac..a9d7dd31615 100644 --- a/src/vs/workbench/parts/git/browser/gitActions.ts +++ b/src/vs/workbench/parts/git/browser/gitActions.ts @@ -29,7 +29,7 @@ import { IGitService, IFileStatus, Status, StatusType, ServiceState, IModel, IBr import { IQuickOpenService } from 'vs/workbench/services/quickopen/common/quickOpenService'; import paths = require('vs/base/common/paths'); import URI from 'vs/base/common/uri'; -import { IWindowService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; function flatten(context?: any, preferFocus = false): IFileStatus[] { @@ -1152,7 +1152,7 @@ export class SyncAction extends GitAction { @IGitService gitService: IGitService, @IConfigurationService private configurationService: IConfigurationService, @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService, - @IWindowService private windowService: IWindowService + @IWindowIPCService private windowService: IWindowIPCService ) { super(id, label, 'git-action sync', gitService); } diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 5427b575c82..10cd09a9078 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -9,6 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import errors = require('vs/base/common/errors'); import platform = require('vs/base/common/platform'); import nls = require('vs/nls'); +import labels = require('vs/base/common/labels'); import URI from 'vs/base/common/uri'; import product from 'vs/platform/product'; import { IEditor as IBaseEditor } from 'vs/platform/editor/common/editor'; @@ -26,6 +27,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/platform'; import { once } from 'vs/base/common/event'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -77,31 +79,49 @@ export abstract class BaseHistoryService { protected toUnbind: IDisposable[]; private activeEditorListeners: IDisposable[]; - private _isPure: boolean; + private isPure: boolean; + private showFullPath: boolean; + + private static NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]"); constructor( protected editorGroupService: IEditorGroupService, protected editorService: IWorkbenchEditorService, protected contextService: IWorkspaceContextService, + private configurationService: IConfigurationService, private environmentService: IEnvironmentService, integrityService: IIntegrityService ) { this.toUnbind = []; this.activeEditorListeners = []; - this._isPure = true; + this.isPure = true; // Window Title window.document.title = this.getWindowTitle(null); + // Integrity + integrityService.isPure().then(r => { + if (!r.isPure) { + this.isPure = false; + window.document.title = this.getWindowTitle(this.editorService.getActiveEditorInput()); + } + }); + // Editor Input Changes this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); - integrityService.isPure().then((r) => { - if (!r.isPure) { - this._isPure = false; - window.document.title = this.getWindowTitle(null); - } - }); + // Configuration Changes + this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(() => this.onConfigurationChanged(true))); + this.onConfigurationChanged(); + } + + private onConfigurationChanged(update?: boolean): void { + const currentShowPath = this.showFullPath; + this.showFullPath = this.configurationService.lookup('window.showFullPath').value; + + if (update && currentShowPath !== this.showFullPath) { + this.updateWindowTitle(this.editorService.getActiveEditorInput()); + } } private onEditorsChanged(): void { @@ -163,8 +183,8 @@ export abstract class BaseHistoryService { protected getWindowTitle(input?: IEditorInput): string { let title = this.doGetWindowTitle(input); - if (!this._isPure) { - title += nls.localize('patchedWindowTitle', " [Unsupported]"); + if (!this.isPure) { + title = `${title} ${BaseHistoryService.NLS_UNSUPPORTED}`; } // Extension Development Host gets a special title to identify itself @@ -178,9 +198,19 @@ export abstract class BaseHistoryService { private doGetWindowTitle(input?: IEditorInput): string { const appName = product.nameLong; - let prefix = input && input.getName(); + let prefix: string; + const fileInput = asFileEditorInput(input); + if (fileInput && this.showFullPath) { + prefix = labels.getPathLabel(fileInput.getResource()); + if ((platform.isMacintosh || platform.isLinux) && prefix.indexOf(this.environmentService.userHome) === 0) { + prefix = `~${prefix.substr(this.environmentService.userHome.length)}`; + } + } else { + prefix = input && input.getName(); + } + if (prefix && input) { - if ((input).isDirty() && !platform.isMacintosh /* Mac has its own decoration in window */) { + if (input.isDirty() && !platform.isMacintosh /* Mac has its own decoration in window */) { prefix = nls.localize('prefixDecoration', "\u25cf {0}", prefix); } } @@ -255,12 +285,13 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic @IEnvironmentService environmentService: IEnvironmentService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IStorageService private storageService: IStorageService, + @IConfigurationService configurationService: IConfigurationService, @ILifecycleService private lifecycleService: ILifecycleService, @IEventService private eventService: IEventService, @IInstantiationService private instantiationService: IInstantiationService, @IIntegrityService integrityService: IIntegrityService ) { - super(editorGroupService, editorService, contextService, environmentService, integrityService); + super(editorGroupService, editorService, contextService, configurationService, environmentService, integrityService); this.index = -1; this.stack = []; @@ -364,7 +395,7 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic openEditorPromise.done(() => { this.blockStackChanges = false; - }, (error) => { + }, error => { this.blockStackChanges = false; errors.onUnexpectedError(error); }); diff --git a/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts index 21bceef3d25..fe3b0d3fcaf 100644 --- a/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts @@ -9,7 +9,7 @@ import Severity from 'vs/base/common/severity'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ILifecycleService, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; import { IMessageService } from 'vs/platform/message/common/message'; -import { IWindowService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; import { ipcRenderer as ipc } from 'electron'; import Event, { Emitter } from 'vs/base/common/event'; @@ -24,7 +24,7 @@ export class LifecycleService implements ILifecycleService { constructor( @IMessageService private messageService: IMessageService, - @IWindowService private windowService: IWindowService + @IWindowIPCService private windowService: IWindowIPCService ) { this.registerListeners(); } diff --git a/src/vs/workbench/services/message/electron-browser/messageService.ts b/src/vs/workbench/services/message/electron-browser/messageService.ts index 5eb98d2cca3..676204fe1d4 100644 --- a/src/vs/workbench/services/message/electron-browser/messageService.ts +++ b/src/vs/workbench/services/message/electron-browser/messageService.ts @@ -5,7 +5,7 @@ 'use strict'; -import { IWindowService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; import nls = require('vs/nls'); import product from 'vs/platform/product'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -19,7 +19,7 @@ export class MessageService extends WorkbenchMessageService implements IChoiceSe constructor( container: HTMLElement, - @IWindowService private windowService: IWindowService, + @IWindowIPCService private windowService: IWindowIPCService, @ITelemetryService telemetryService: ITelemetryService ) { super(container, telemetryService); @@ -54,8 +54,8 @@ export class MessageService extends WorkbenchMessageService implements IChoiceSe return result === 0 ? true : false; } - choose(severity: Severity, message: string, options: string[], force: boolean = false): TPromise { - if (force) { + choose(severity: Severity, message: string, options: string[], modal: boolean = false): TPromise { + if (modal) { const type: 'none' | 'info' | 'error' | 'question' | 'warning' = severity === Severity.Info ? 'question' : severity === Severity.Error ? 'error' : severity === Severity.Warning ? 'warning' : 'none'; return TPromise.wrap(this.showMessageBox({ message, buttons: options, type })); } diff --git a/src/vs/workbench/services/textfile/electron-browser/textFileService.ts b/src/vs/workbench/services/textfile/electron-browser/textFileService.ts index 8c13f9808be..3343415fa80 100644 --- a/src/vs/workbench/services/textfile/electron-browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/textFileService.ts @@ -22,7 +22,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IWindowService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelBuilder } from 'vs/editor/node/model/modelBuilder'; @@ -46,7 +46,7 @@ export class TextFileService extends AbstractTextFileService { @IModeService private modeService: IModeService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IEditorGroupService editorGroupService: IEditorGroupService, - @IWindowService private windowService: IWindowService, + @IWindowIPCService private windowService: IWindowIPCService, @IModelService private modelService: IModelService, @IEnvironmentService private environmentService: IEnvironmentService, @IBackupService backupService: IBackupService diff --git a/src/vs/workbench/services/themes/electron-browser/themeService.ts b/src/vs/workbench/services/themes/electron-browser/themeService.ts index 604ed663470..fce74dd687e 100644 --- a/src/vs/workbench/services/themes/electron-browser/themeService.ts +++ b/src/vs/workbench/services/themes/electron-browser/themeService.ts @@ -14,7 +14,7 @@ import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/platform/exten import { IThemeService, IThemeData, IThemeSetting, IThemeDocument } from 'vs/workbench/services/themes/common/themeService'; import { TokenStylesContribution, EditorStylesContribution, SearchViewStylesContribution, TerminalStylesContribution } from 'vs/workbench/services/themes/electron-browser/stylesContributions'; import { getBaseThemeId } from 'vs/platform/theme/common/themes'; -import { IWindowService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Registry } from 'vs/platform/platform'; @@ -163,7 +163,7 @@ export class ThemeService implements IThemeService { constructor( @IExtensionService private extensionService: IExtensionService, - @IWindowService private windowService: IWindowService, + @IWindowIPCService private windowService: IWindowIPCService, @IStorageService private storageService: IStorageService, @ITelemetryService private telemetryService: ITelemetryService) { diff --git a/src/vs/workbench/services/untitled/common/untitledEditorService.ts b/src/vs/workbench/services/untitled/common/untitledEditorService.ts index b25e3ea8582..1a2419a8e0b 100644 --- a/src/vs/workbench/services/untitled/common/untitledEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledEditorService.ts @@ -186,6 +186,11 @@ export class UntitledEditorService implements IUntitledEditorService { } const input = this.instantiationService.createInstance(UntitledEditorInput, resource, hasAssociatedFilePath, modeId, restoreResource); + if (input.isDirty()) { + setTimeout(() => { + this._onDidChangeDirty.fire(resource); + }, 0 /* prevent race condition between creating input and emitting dirty event */); + } const contentListener = input.onDidModelChangeContent(() => { this._onDidChangeContent.fire(resource); diff --git a/src/vs/workbench/services/window/electron-browser/windowService.ts b/src/vs/workbench/services/window/electron-browser/windowService.ts index 3eece926686..40c876c545b 100644 --- a/src/vs/workbench/services/window/electron-browser/windowService.ts +++ b/src/vs/workbench/services/window/electron-browser/windowService.ts @@ -13,10 +13,10 @@ import { ipcRenderer as ipc, remote } from 'electron'; const windowId = remote.getCurrentWindow().id; -export const IWindowService = createDecorator('windowService'); +export const IWindowIPCService = createDecorator('windowIPCService'); export interface IWindowServices { - windowService?: IWindowService; + windowService?: IWindowIPCService; } export interface IBroadcast { @@ -24,7 +24,7 @@ export interface IBroadcast { payload: any; } -export interface IWindowService { +export interface IWindowIPCService { _serviceBrand: any; getWindowId(): number; @@ -38,7 +38,11 @@ export interface IWindowService { onBroadcast: Event; } -export class WindowService implements IWindowService { +/** + * TODO@Joao: remove this service + * @deprecated + */ +export class WindowIPCService implements IWindowIPCService { public _serviceBrand: any; private win: ElectronWindow; diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 78199895ca9..e78d493ce95 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -54,7 +54,7 @@ import 'vs/workbench/parts/markers/browser/markersPanel'; // can be packaged sep import 'vs/workbench/parts/html/browser/html.contribution'; import 'vs/workbench/parts/extensions/electron-browser/extensions.contribution'; -import 'vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen'; +import 'vs/workbench/parts/extensions/browser/extensionsQuickOpen'; import 'vs/workbench/parts/extensions/electron-browser/extensionsViewlet'; // can be packaged separately import 'vs/workbench/parts/output/browser/output.contribution';