diff --git a/extensions/json/server/npm-shrinkwrap.json b/extensions/json/server/npm-shrinkwrap.json index c43488d2d01..84caeb7f0ed 100644 --- a/extensions/json/server/npm-shrinkwrap.json +++ b/extensions/json/server/npm-shrinkwrap.json @@ -43,9 +43,16 @@ "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.1.0.tgz" }, "vscode-json-languageservice": { - "version": "1.1.8-next.2", + "version": "2.0.0-next.2", "from": "vscode-json-languageservice@next", - "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-1.1.8-next.2.tgz" + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-2.0.0-next.2.tgz", + "dependencies": { + "vscode-languageserver-types": { + "version": "1.0.4", + "from": "vscode-languageserver-types@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-1.0.4.tgz" + } + } }, "vscode-jsonrpc": { "version": "2.3.2-next.5", diff --git a/extensions/json/server/package.json b/extensions/json/server/package.json index e09bca6bf1d..acbadb593f1 100644 --- a/extensions/json/server/package.json +++ b/extensions/json/server/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "request-light": "^0.1.0", - "vscode-json-languageservice": "^1.1.8-next.2", + "vscode-json-languageservice": "^2.0.0-next.2", "vscode-languageserver": "^2.4.0-next.12", "vscode-nls": "^1.0.4" }, diff --git a/extensions/shellscript/syntaxes/Shell-Unix-Bash.tmLanguage.json b/extensions/shellscript/syntaxes/Shell-Unix-Bash.tmLanguage.json index 9391761b2b0..0d4fbc322b2 100644 --- a/extensions/shellscript/syntaxes/Shell-Unix-Bash.tmLanguage.json +++ b/extensions/shellscript/syntaxes/Shell-Unix-Bash.tmLanguage.json @@ -10,7 +10,7 @@ "bash_logout", ".textmate_init" ], - "firstLineMatch": "^#!.*\\b(bash|zsh|sh|tcsh)|^#\\s*-\\*-[^*]*mode:\\s*shell-script[^*]*-\\*-", + "firstLineMatch": "^#!.*\\b(bash|zsh|sh|tcsh)|^#.*-\\*-.*\\bshell-script\\b.*-\\*-", "keyEquivalent": "^~S", "name": "Shell Script (Bash)", "patterns": [ @@ -124,6 +124,16 @@ }, "end": "(?!\\G)", "patterns": [ + { + "begin": "^(#!)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.comment.line.shebang.shell" + } + }, + "end": "\\n", + "name": "comment.line.shebang.shell" + }, { "begin": "#", "beginCaptures": { @@ -264,7 +274,7 @@ "heredoc": { "patterns": [ { - "begin": "(<<)-(\"|'|)(RUBY)\\2", + "begin": "(<<)-\\s*(\"|'|)(RUBY)\\2", "beginCaptures": { "1": { "name": "keyword.operator.heredoc.shell" @@ -293,7 +303,7 @@ ] }, { - "begin": "(<<)(\"|'|)(RUBY)\\2", + "begin": "(<<)\\s*(\"|'|)(RUBY)\\2", "beginCaptures": { "1": { "name": "keyword.operator.heredoc.shell" @@ -322,7 +332,7 @@ ] }, { - "begin": "(<<)-(\"|'|)(PYTHON)\\2", + "begin": "(<<)-\\s*(\"|'|)(PYTHON)\\2", "beginCaptures": { "1": { "name": "keyword.operator.heredoc.shell" @@ -351,7 +361,7 @@ ] }, { - "begin": "(<<)(\"|'|)(PYTHON)\\2", + "begin": "(<<)\\s*(\"|'|)(PYTHON)\\2", "beginCaptures": { "1": { "name": "keyword.operator.heredoc.shell" @@ -380,7 +390,7 @@ ] }, { - "begin": "(<<)-(\"|'|)(APPLESCRIPT)\\2", + "begin": "(<<)-\\s*(\"|'|)(APPLESCRIPT)\\2", "beginCaptures": { "1": { "name": "keyword.operator.heredoc.shell" @@ -409,7 +419,7 @@ ] }, { - "begin": "(<<)(\"|'|)(APPLESCRIPT)\\2", + "begin": "(<<)\\s*(\"|'|)(APPLESCRIPT)\\2", "beginCaptures": { "1": { "name": "keyword.operator.heredoc.shell" @@ -438,7 +448,7 @@ ] }, { - "begin": "(<<)-(\"|'|)(HTML)\\2", + "begin": "(<<)-\\s*(\"|'|)(HTML)\\2", "beginCaptures": { "1": { "name": "keyword.operator.heredoc.shell" @@ -467,7 +477,7 @@ ] }, { - "begin": "(<<)(\"|'|)(HTML)\\2", + "begin": "(<<)\\s*(\"|'|)(HTML)\\2", "beginCaptures": { "1": { "name": "keyword.operator.heredoc.shell" @@ -496,7 +506,7 @@ ] }, { - "begin": "(<<)-(\"|'|)(MARKDOWN)\\2", + "begin": "(<<)-\\s*(\"|'|)(MARKDOWN)\\2", "beginCaptures": { "1": { "name": "keyword.operator.heredoc.shell" @@ -525,7 +535,7 @@ ] }, { - "begin": "(<<)(\"|'|)(MARKDOWN)\\2", + "begin": "(<<)\\s*(\"|'|)(MARKDOWN)\\2", "beginCaptures": { "1": { "name": "keyword.operator.heredoc.shell" @@ -554,7 +564,7 @@ ] }, { - "begin": "(<<)-(\"|'|)(TEXTILE)\\2", + "begin": "(<<)-\\s*(\"|'|)(TEXTILE)\\2", "beginCaptures": { "1": { "name": "keyword.operator.heredoc.shell" @@ -583,7 +593,7 @@ ] }, { - "begin": "(<<)(\"|'|)(TEXTILE)\\2", + "begin": "(<<)\\s*(\"|'|)(TEXTILE)\\2", "beginCaptures": { "1": { "name": "keyword.operator.heredoc.shell" @@ -612,7 +622,7 @@ ] }, { - "begin": "(<<)-(\"|'|)\\\\?(\\w+)\\2", + "begin": "(<<)-\\s*(\"|'|)\\\\?(\\w+)\\2", "beginCaptures": { "1": { "name": "keyword.operator.heredoc.shell" @@ -635,7 +645,7 @@ "name": "string.unquoted.heredoc.no-indent.shell" }, { - "begin": "(<<)(\"|'|)\\\\?(\\w+)\\2", + "begin": "(<<)\\s*(\"|'|)\\\\?(\\w+)\\2", "beginCaptures": { "1": { "name": "keyword.operator.heredoc.shell" @@ -676,7 +686,7 @@ "name": "punctuation.definition.string.end.shell" } }, - "match": "(<<<)((')[^']*('))", + "match": "(<<<)\\s*((')[^']*('))", "name": "meta.herestring.shell" }, { @@ -694,7 +704,7 @@ "name": "punctuation.definition.string.end.shell" } }, - "match": "(<<<)((\")(\\\\(\"|\\\\)|[^\"])*(\"))", + "match": "(<<<)\\s*((\")(\\\\(\"|\\\\)|[^\"])*(\"))", "name": "meta.herestring.shell" }, { @@ -706,7 +716,7 @@ "name": "string.unquoted.herestring.shell" } }, - "match": "(<<<)(([^\\s\\\\]|\\\\.)+)", + "match": "(<<<)\\s*(([^\\s\\\\]|\\\\.)+)", "name": "meta.herestring.shell" } ] @@ -782,7 +792,7 @@ "keyword": { "patterns": [ { - "match": "(?<=^|;|&|\\s)(?:if|then|else|elif|fi|for|in|do|done|select|case|continue|esac|while|until|return)(?=\\s|;|&|$)", + "match": "(?<=^|;|&|\\s)(?:if|then|else|elif|fi|for|in|do|done|select|case|continue|esac|while|until|return|coproc)(?=\\s|;|&|$)", "name": "keyword.control.shell" }, { @@ -830,7 +840,7 @@ ] }, { - "begin": "(?<=^|;|&|\\s)(for)\\s+((?:[^\\s\\\\]|\\\\.)+)(?=\\s|;|&|$)", + "begin": "(?<=^|;|&|\\s)(for)\\s+([^\\s\\\\]+)(?=\\s|;|&|$)", "beginCaptures": { "1": { "name": "keyword.control.shell" @@ -868,7 +878,7 @@ ] }, { - "begin": "(?<=^|;|&|\\s)(select)\\s+((?:[^\\s\\\\]|\\\\.)+)(?=\\s|;|&|$)", + "begin": "(?<=^|;|&|\\s)(select)\\s+([^\\s\\\\]+)(?=\\s|;|&|$)", "beginCaptures": { "1": { "name": "keyword.control.shell" @@ -1142,7 +1152,7 @@ "name": "support.function.builtin.shell" }, { - "match": "(?<=^|;|&|\\s)(?:alias|bg|bind|break|builtin|caller|cd|command|compgen|complete|dirs|disown|echo|enable|eval|exec|exit|false|fc|fg|getopts|hash|help|history|jobs|kill|let|logout|popd|printf|pushd|pwd|read|readonly|set|shift|shopt|source|suspend|test|times|trap|true|type|ulimit|umask|unalias|unset|wait)(?=\\s|;|&|$)", + "match": "(?<=^|;|&|\\s)(?:alias|bg|bind|break|builtin|caller|cd|command|compgen|complete|dirs|disown|echo|enable|eval|exec|exit|false|fc|fg|getopts|hash|help|history|jobs|kill|let|logout|mapfile|popd|printf|pushd|pwd|read(array)?|readonly|set|shift|shopt|source|suspend|test|times|trap|true|type|ulimit|umask|unalias|unset|wait)(?=\\s|;|&|$)", "name": "support.function.builtin.shell" } ] @@ -1217,5 +1227,5 @@ }, "scopeName": "source.shell", "uuid": "DDEEA3ED-6B1C-11D9-8B10-000D93589AF6", - "version": "https://github.com/textmate/shellscript.tmbundle/commit/887a69bdd7558f7aa2ecba28ffb224881bad6cb3" + "version": "https://github.com/textmate/shellscript.tmbundle/commit/2677fdc83ed9d6a517d5d204e003f49141fc72e4" } \ No newline at end of file diff --git a/extensions/shellscript/test/colorize-results/test_sh.json b/extensions/shellscript/test/colorize-results/test_sh.json index ac2503262e0..fa0b5e389c1 100644 --- a/extensions/shellscript/test/colorize-results/test_sh.json +++ b/extensions/shellscript/test/colorize-results/test_sh.json @@ -1,7 +1,7 @@ [ { - "c": "#", - "t": "comment.definition.line.number-sign.punctuation.shell", + "c": "#!", + "t": "comment.definition.line.punctuation.shebang.shell", "r": { "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.comment rgb(96, 139, 78)", "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.comment rgb(0, 128, 0)", @@ -11,8 +11,8 @@ } }, { - "c": "!/usr/bin/env bash", - "t": "comment.line.number-sign.shell", + "c": "/usr/bin/env bash", + "t": "comment.line.shebang.shell", "r": { "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.comment rgb(96, 139, 78)", "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.comment rgb(0, 128, 0)", diff --git a/extensions/xml/package.json b/extensions/xml/package.json index 644f2d79fb3..401a822ae5b 100644 --- a/extensions/xml/package.json +++ b/extensions/xml/package.json @@ -45,6 +45,7 @@ ".svg", ".targets", ".tld", + ".tmx", ".vbproj", ".vbproj.user", ".vcxproj", diff --git a/src/bootstrap.js b/src/bootstrap.js index ae4e67c1488..022aa50b6a3 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -98,9 +98,9 @@ if (!process.env['VSCODE_ALLOW_IO']) { write: function () { /* No OP */ } }); - process.__defineGetter__('stdout', function() { return writable; }); - process.__defineGetter__('stderr', function() { return writable; }); - process.__defineGetter__('stdin', function() { return writable; }); + process.__defineGetter__('stdout', function () { return writable; }); + process.__defineGetter__('stderr', function () { return writable; }); + process.__defineGetter__('stdin', function () { return writable; }); } // Handle uncaught exceptions diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 83b863c2a49..f2e40528c9c 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -694,10 +694,6 @@ export class SelectActionItem extends BaseActionItem { } } - public set enabled(value: boolean) { - this.selectBox.enabled = value; - } - public blur(): void { if (this.selectBox) { this.selectBox.blur(); diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 2d90236b64f..47ecaf823a3 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -16,6 +16,7 @@ import { ILogService } from 'vs/code/electron-main/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { parseArgs, ParsedArgs } from 'vs/platform/environment/node/argv'; import product from 'vs/platform/product'; +import { getCommonHTTPHeaders } from 'vs/platform/environment/common/http'; export interface IWindowState { width?: number; @@ -180,6 +181,21 @@ export class VSCodeWindow { this._win = new BrowserWindow(options); this._id = this._win.id; + // TODO@joao: hook this up to some initialization routine + // this causes a race between setting the headers and doing + // a request that needs them. chances are low + getCommonHTTPHeaders().done(headers => { + if (!this._win) { + return; + } + + const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; + + this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => { + cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) }); + }); + }); + if (isFullscreenOrMaximized) { this.win.maximize(); diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 4530d940889..2fe45b20603 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -118,9 +118,10 @@ export interface IWindowsMainService { getWindowById(windowId: number): VSCodeWindow; getWindows(): VSCodeWindow[]; getWindowCount(): number; - getRecentPathsList(): IRecentPathsList; + getRecentPathsList(workspacePath?: string, filesToOpen?: IPath[]): IRecentPathsList; removeFromRecentPathsList(path: string); clearRecentPathsList(): void; + toggleMenuBar(windowId: number): void; } export class WindowsManager implements IWindowsMainService, IWindowEventService { @@ -219,14 +220,6 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService crashReporter.start(config); }); - ipc.on('vscode:windowOpen', (event, paths: string[], forceNewWindow?: boolean) => { - this.logService.log('IPC#vscode-windowOpen: ', paths); - - if (paths && paths.length) { - this.open({ cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: forceNewWindow }); - } - }); - ipc.on('vscode:workbenchLoaded', (event, windowId: number) => { this.logService.log('IPC#vscode-workbenchLoaded'); @@ -239,157 +232,6 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService } }); - ipc.on('vscode:closeFolder', (event, windowId: number) => { - this.logService.log('IPC#vscode-closeFolder'); - - const win = this.getWindowById(windowId); - if (win) { - this.open({ cli: this.environmentService.args, forceEmpty: true, windowToUse: win }); - } - }); - - ipc.on('vscode:openNewWindow', () => { - this.logService.log('IPC#vscode-openNewWindow'); - - this.openNewWindow(); - }); - - ipc.on('vscode:toggleFullScreen', (event, windowId: number) => { - this.logService.log('IPC#vscode:toggleFullScreen'); - - const vscodeWindow = this.getWindowById(windowId); - if (vscodeWindow) { - vscodeWindow.toggleFullScreen(); - } - }); - - ipc.on('vscode:setFullScreen', (event, windowId: number, fullscreen: boolean) => { - this.logService.log('IPC#vscode:setFullScreen'); - - const vscodeWindow = this.getWindowById(windowId); - if (vscodeWindow) { - vscodeWindow.win.setFullScreen(fullscreen); - } - }); - - ipc.on('vscode:toggleDevTools', (event, windowId: number) => { - this.logService.log('IPC#vscode:toggleDevTools'); - - const vscodeWindow = this.getWindowById(windowId); - if (vscodeWindow) { - vscodeWindow.win.webContents.toggleDevTools(); - } - }); - - ipc.on('vscode:openDevTools', (event, windowId: number) => { - this.logService.log('IPC#vscode:openDevTools'); - - const vscodeWindow = this.getWindowById(windowId); - if (vscodeWindow) { - vscodeWindow.win.webContents.openDevTools(); - vscodeWindow.win.show(); - } - }); - - ipc.on('vscode:setRepresentedFilename', (event, windowId: number, fileName: string) => { - this.logService.log('IPC#vscode:setRepresentedFilename'); - - const vscodeWindow = this.getWindowById(windowId); - if (vscodeWindow) { - vscodeWindow.win.setRepresentedFilename(fileName); - } - }); - - ipc.on('vscode:setMenuBarVisibility', (event, windowId: number, visibility: boolean) => { - this.logService.log('IPC#vscode:setMenuBarVisibility'); - - const vscodeWindow = this.getWindowById(windowId); - if (vscodeWindow) { - vscodeWindow.win.setMenuBarVisibility(visibility); - } - }); - - ipc.on('vscode:flashFrame', (event, windowId: number) => { - this.logService.log('IPC#vscode:flashFrame'); - - const vscodeWindow = this.getWindowById(windowId); - if (vscodeWindow) { - vscodeWindow.win.flashFrame(!vscodeWindow.win.isFocused()); - } - }); - - ipc.on('vscode:openRecent', (event, windowId: number) => { - this.logService.log('IPC#vscode:openRecent'); - - const vscodeWindow = this.getWindowById(windowId); - if (vscodeWindow) { - const recents = this.getRecentPathsList(vscodeWindow.config.workspacePath, vscodeWindow.config.filesToOpen); - - vscodeWindow.send('vscode:openRecent', recents.files, recents.folders); - } - }); - - ipc.on('vscode:focusWindow', (event, windowId: number) => { - this.logService.log('IPC#vscode:focusWindow'); - - const vscodeWindow = this.getWindowById(windowId); - if (vscodeWindow) { - vscodeWindow.win.focus(); - } - }); - - ipc.on('vscode:showWindow', (event, windowId: number) => { - this.logService.log('IPC#vscode:showWindow'); - - let vscodeWindow = this.getWindowById(windowId); - if (vscodeWindow) { - vscodeWindow.win.show(); - } - }); - - ipc.on('vscode:setDocumentEdited', (event, windowId: number, edited: boolean) => { - this.logService.log('IPC#vscode:setDocumentEdited'); - - const vscodeWindow = this.getWindowById(windowId); - if (vscodeWindow && vscodeWindow.win.isDocumentEdited() !== edited) { - vscodeWindow.win.setDocumentEdited(edited); - } - }); - - ipc.on('vscode:toggleMenuBar', (event, windowId: number) => { - this.logService.log('IPC#vscode:toggleMenuBar'); - - // Update in settings - const menuBarHidden = this.storageService.getItem(VSCodeWindow.menuBarHiddenKey, false); - const newMenuBarHidden = !menuBarHidden; - this.storageService.setItem(VSCodeWindow.menuBarHiddenKey, newMenuBarHidden); - - // Update across windows - WindowsManager.WINDOWS.forEach(w => w.setMenuBarVisibility(!newMenuBarHidden)); - - // Inform user if menu bar is now hidden - if (newMenuBarHidden) { - const vscodeWindow = this.getWindowById(windowId); - if (vscodeWindow) { - vscodeWindow.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the **Alt** key.")); - } - } - }); - - ipc.on('vscode:setHeaders', (event, windowId: number, urls: string[], headers: any) => { - this.logService.log('IPC#vscode:setHeaders'); - - const vscodeWindow = this.getWindowById(windowId); - - if (!vscodeWindow || !urls || !urls.length || !headers) { - return; - } - - vscodeWindow.win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => { - cb({ cancel: false, requestHeaders: assign(details.requestHeaders, headers) }); - }); - }); - ipc.on('vscode:broadcast', (event, windowId: number, target: string, broadcast: { channel: string; payload: any; }) => { if (broadcast.channel && !types.isUndefinedOrNull(broadcast.payload)) { this.logService.log('IPC#vscode:broadcast', target, broadcast.channel, broadcast.payload); @@ -413,41 +255,6 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService } }); - ipc.on('vscode:log', (event, logEntry: ILogEntry) => { - const args = []; - try { - const parsed = JSON.parse(logEntry.arguments); - args.push(...Object.getOwnPropertyNames(parsed).map(o => parsed[o])); - } catch (error) { - args.push(logEntry.arguments); - } - - console[logEntry.severity].apply(console, args); - }); - - ipc.on('vscode:closeExtensionHostWindow', (event, extensionDevelopmentPath: string) => { - this.logService.log('IPC#vscode:closeExtensionHostWindow', extensionDevelopmentPath); - - const windowOnExtension = this.findWindow(null, null, extensionDevelopmentPath); - if (windowOnExtension) { - windowOnExtension.win.close(); - } - }); - - ipc.on('vscode:switchWindow', (event, windowId: number) => { - const windows = this.getWindows(); - const window = this.getWindowById(windowId); - window.send('vscode:switchWindow', windows.map(w => { - return { path: w.openedWorkspacePath, title: w.win.getTitle(), id: w.id }; - })); - }); - - ipc.on('vscode:showItemInFolder', (event, path: string) => { - this.logService.log('IPC#vscode-showItemInFolder'); - - shell.showItemInFolder(path); - }); - ipc.on('vscode:openExternal', (event, url: string) => { this.logService.log('IPC#vscode-openExternal'); @@ -1360,4 +1167,22 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService return pathA === pathB; } + + toggleMenuBar(windowId: number): void { + // Update in settings + const menuBarHidden = this.storageService.getItem(VSCodeWindow.menuBarHiddenKey, false); + const newMenuBarHidden = !menuBarHidden; + this.storageService.setItem(VSCodeWindow.menuBarHiddenKey, newMenuBarHidden); + + // Update across windows + WindowsManager.WINDOWS.forEach(w => w.setMenuBarVisibility(!newMenuBarHidden)); + + // Inform user if menu bar is now hidden + if (newMenuBarHidden) { + const vscodeWindow = this.getWindowById(windowId); + if (vscodeWindow) { + vscodeWindow.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the **Alt** key.")); + } + } + } } diff --git a/src/vs/editor/common/commonCodeEditor.ts b/src/vs/editor/common/commonCodeEditor.ts index 48833c7ed4d..b28e202cdaa 100644 --- a/src/vs/editor/common/commonCodeEditor.ts +++ b/src/vs/editor/common/commonCodeEditor.ts @@ -761,8 +761,14 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom validateViewPosition: (viewLineNumber: number, viewColumn: number, modelPosition: Position) => { return this.viewModel.validateViewPosition(viewLineNumber, viewColumn, modelPosition); }, + validateViewPosition2: (viewPosition: Position, modelPosition: Position): Position => { + return this.viewModel.validateViewPosition(viewPosition.lineNumber, viewPosition.column, modelPosition); + }, validateViewRange: (viewStartLineNumber: number, viewStartColumn: number, viewEndLineNumber: number, viewEndColumn: number, modelRange: Range) => { return this.viewModel.validateViewRange(viewStartLineNumber, viewStartColumn, viewEndLineNumber, viewEndColumn, modelRange); + }, + validateViewRange2: (viewRange: Range, modelRange: Range): Range => { + return this.viewModel.validateViewRange(viewRange.startLineNumber, viewRange.startColumn, viewRange.endLineNumber, viewRange.endColumn, modelRange); } }; diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index 569a9a272d3..6d4a7156321 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -11,13 +11,14 @@ import { EventEmitter } from 'vs/base/common/eventEmitter'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ReplaceCommand } from 'vs/editor/common/commands/replaceCommand'; import { CursorCollection, ICursorCollectionState } from 'vs/editor/common/controller/cursorCollection'; -import { WordNavigationType, IOneCursorOperationContext, IPostOperationRunnable, IViewModelHelper, OneCursor, OneCursorOp } from 'vs/editor/common/controller/oneCursor'; +import { IOneCursorOperationContext, IPostOperationRunnable, IViewModelHelper, OneCursor, OneCursorOp } from 'vs/editor/common/controller/oneCursor'; 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'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { IColumnSelectResult } from 'vs/editor/common/controller/cursorMoveHelper'; +import { IColumnSelectResult, CursorMove } from 'vs/editor/common/controller/cursorMoveHelper'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { WordNavigationType } from 'vs/editor/common/controller/cursorWordOperations'; export interface ITypingListener { (): void; @@ -316,6 +317,9 @@ export class Cursor extends EventEmitter { var handled = false; try { + // ensure valid state on all cursors + this.cursors.ensureValidState(); + var oldSelections = this.cursors.getSelections(); var oldViewSelections = this.cursors.getViewSelections(); var prevCursorsState = this.cursors.saveState(); @@ -1123,7 +1127,7 @@ export class Cursor extends EventEmitter { if (!this._columnSelectToVisualColumn) { let primaryCursor = this.cursors.getAll()[0]; let primaryPos = primaryCursor.viewState.position; - return primaryCursor.getViewVisibleColumnFromColumn(primaryPos.lineNumber, primaryPos.column); + return CursorMove.visibleColumnFromColumn2(primaryCursor.config, primaryCursor.viewModel, primaryPos); } return this._columnSelectToVisualColumn; } @@ -1510,10 +1514,10 @@ export class Cursor extends EventEmitter { let noOfLines = editorScrollArg.value || 1; switch (editorScrollArg.by) { case editorCommon.EditorScrollByUnit.Page: - noOfLines = cursor.getPageSize() * noOfLines; + noOfLines = cursor.config.pageSize * noOfLines; break; case editorCommon.EditorScrollByUnit.HalfPage: - noOfLines = Math.round(cursor.getPageSize() / 2) * noOfLines; + noOfLines = Math.round(cursor.config.pageSize / 2) * noOfLines; break; } this.emitCursorScrollRequest((up ? -1 : 1) * noOfLines, !!editorScrollArg.revealCursor); diff --git a/src/vs/editor/common/controller/cursorCollection.ts b/src/vs/editor/common/controller/cursorCollection.ts index 71870abb0e6..6671d58d51f 100644 --- a/src/vs/editor/common/controller/cursorCollection.ts +++ b/src/vs/editor/common/controller/cursorCollection.ts @@ -49,6 +49,13 @@ export class CursorCollection { this.killSecondaryCursors(); } + public ensureValidState(): void { + this.primaryCursor.ensureValidState(); + for (let i = 0, len = this.secondaryCursors.length; i < len; i++) { + this.secondaryCursors[i].ensureValidState(); + } + } + public saveState(): ICursorCollectionState { return { primary: this.primaryCursor.saveState(), diff --git a/src/vs/editor/common/controller/cursorMoveHelper.ts b/src/vs/editor/common/controller/cursorMoveHelper.ts index d0b99bc11f9..85a0f226885 100644 --- a/src/vs/editor/common/controller/cursorMoveHelper.ts +++ b/src/vs/editor/common/controller/cursorMoveHelper.ts @@ -5,6 +5,7 @@ 'use strict'; import { Selection } from 'vs/editor/common/core/selection'; +import { Position } from 'vs/editor/common/core/position'; import { CharCode } from 'vs/base/common/charCode'; import * as strings from 'vs/base/common/strings'; @@ -13,13 +14,16 @@ export class CursorMoveConfiguration { public readonly tabSize: number; public readonly pageSize: number; + public readonly wordSeparators: string; constructor( tabSize: number, - pageSize: number + pageSize: number, + wordSeparators: string ) { this.tabSize = tabSize; this.pageSize = pageSize; + this.wordSeparators = wordSeparators; } } @@ -32,26 +36,12 @@ export interface ICursorMoveHelperModel { 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 { + public static isLowSurrogate(model: ICursorMoveHelperModel, lineNumber: number, charOffset: number): boolean { let lineContent = model.getLineContent(lineNumber); if (charOffset < 0 || charOffset >= lineContent.length) { return false; @@ -59,7 +49,7 @@ export class CursorMove { return strings.isLowSurrogate(lineContent.charCodeAt(charOffset)); } - private static _isHighSurrogate(model: ICursorMoveHelperModel, lineNumber: number, charOffset: number): boolean { + public static isHighSurrogate(model: ICursorMoveHelperModel, lineNumber: number, charOffset: number): boolean { let lineContent = model.getLineContent(lineNumber); if (charOffset < 0 || charOffset >= lineContent.length) { return false; @@ -67,95 +57,8 @@ export class CursorMove { 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 isInsideSurrogatePair(model: ICursorMoveHelperModel, lineNumber: number, column: number): boolean { + return this.isHighSurrogate(model, lineNumber, column - 2); } public static visibleColumnFromColumn(lineContent: string, column: number, tabSize: number): number { @@ -176,6 +79,10 @@ export class CursorMove { return result; } + public static visibleColumnFromColumn2(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, position: Position): number { + return this.visibleColumnFromColumn(model.getLineContent(position.lineNumber), position.column, config.tabSize); + } + private static _columnFromVisibleColumn(lineContent: string, visibleColumn: number, tabSize: number): number { if (visibleColumn <= 0) { return 1; @@ -249,13 +156,7 @@ export interface IColumnSelectResult extends IViewColumnSelectResult { export class CursorMoveHelper { - private readonly _config: CursorMoveConfiguration; - - constructor(config: CursorMoveConfiguration) { - this._config = config; - } - - public columnSelect(model: ICursorMoveHelperModel, fromLineNumber: number, fromVisibleColumn: number, toLineNumber: number, toVisibleColumn: number): IViewColumnSelectResult { + public static columnSelect(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, fromLineNumber: number, fromVisibleColumn: number, toLineNumber: number, toVisibleColumn: number): IViewColumnSelectResult { let lineCount = Math.abs(toLineNumber - fromLineNumber) + 1; let reversed = (fromLineNumber > toLineNumber); let isRTL = (fromVisibleColumn > toVisibleColumn); @@ -268,10 +169,10 @@ export class CursorMoveHelper { for (let i = 0; i < lineCount; i++) { let lineNumber = fromLineNumber + (reversed ? -i : i); - let startColumn = this.columnFromVisibleColumn(model, lineNumber, fromVisibleColumn); - let endColumn = this.columnFromVisibleColumn(model, lineNumber, toVisibleColumn); - let visibleStartColumn = this.visibleColumnFromColumn(model, lineNumber, startColumn); - let visibleEndColumn = this.visibleColumnFromColumn(model, lineNumber, endColumn); + let startColumn = CursorMove.columnFromVisibleColumn(config, model, lineNumber, fromVisibleColumn); + let endColumn = CursorMove.columnFromVisibleColumn(config, model, lineNumber, toVisibleColumn); + let visibleStartColumn = this.visibleColumnFromColumn(model, lineNumber, startColumn, config.tabSize); + let visibleEndColumn = this.visibleColumnFromColumn(model, lineNumber, endColumn, config.tabSize); // console.log(`lineNumber: ${lineNumber}: visibleStartColumn: ${visibleStartColumn}, visibleEndColumn: ${visibleEndColumn}`); @@ -302,7 +203,7 @@ export class CursorMoveHelper { }; } - public getColumnAtBeginningOfLine(model: ICursorMoveHelperModel, lineNumber: number, column: number): number { + public static getColumnAtBeginningOfLine(model: ICursorMoveHelperModel, lineNumber: number, column: number): number { var firstNonBlankColumn = model.getLineFirstNonWhitespaceColumn(lineNumber) || 1; var minColumn = model.getLineMinColumn(lineNumber); @@ -315,7 +216,7 @@ export class CursorMoveHelper { return column; } - public getColumnAtEndOfLine(model: ICursorMoveHelperModel, lineNumber: number, column: number): number { + public static getColumnAtEndOfLine(model: ICursorMoveHelperModel, lineNumber: number, column: number): number { var maxColumn = model.getLineMaxColumn(lineNumber); var lastNonBlankColumn = model.getLineLastNonWhitespaceColumn(lineNumber) || maxColumn; @@ -328,10 +229,6 @@ export class CursorMoveHelper { return column; } - public visibleColumnFromColumn(model: ICursorMoveHelperModel, lineNumber: number, column: number): number { - return CursorMoveHelper.visibleColumnFromColumn(model, lineNumber, column, this._config.tabSize); - } - public static visibleColumnFromColumn(model: ICursorMoveHelperModel, lineNumber: number, column: number, tabSize: number): number { return CursorMoveHelper.visibleColumnFromColumn2(model.getLineContent(lineNumber), column, tabSize); } @@ -340,10 +237,6 @@ export class CursorMoveHelper { return CursorMove.visibleColumnFromColumn(line, column, tabSize); } - public columnFromVisibleColumn(model: ICursorMoveHelperModel, lineNumber: number, visibleColumn: number): number { - return CursorMove.columnFromVisibleColumn(this._config, model, lineNumber, visibleColumn); - } - /** * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns) */ diff --git a/src/vs/editor/common/controller/cursorMoveOperations.ts b/src/vs/editor/common/controller/cursorMoveOperations.ts new file mode 100644 index 00000000000..dd14458d3ea --- /dev/null +++ b/src/vs/editor/common/controller/cursorMoveOperations.ts @@ -0,0 +1,210 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { CursorMove, CursorMoveConfiguration, ICursorMoveHelperModel } from 'vs/editor/common/controller/cursorMoveHelper'; +import { CursorChangeReason } from 'vs/editor/common/editorCommon'; +import { CursorModelState } from 'vs/editor/common/controller/oneCursor'; + +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; + } +} + +export class MoveOperationResult { + _moveOperationBrand: void; + + readonly inSelectionMode: boolean; + readonly lineNumber: number; + readonly column: number; + readonly leftoverVisibleColumns: number; + readonly ensureInEditableRange: boolean; + readonly reason: CursorChangeReason; + + constructor( + inSelectionMode: boolean, + lineNumber: number, + column: number, + leftoverVisibleColumns: number, + ensureInEditableRange: boolean, + reason: CursorChangeReason + ) { + this.inSelectionMode = inSelectionMode; + this.lineNumber = lineNumber; + this.column = column; + this.leftoverVisibleColumns = leftoverVisibleColumns; + this.ensureInEditableRange = ensureInEditableRange; + this.reason = reason; + } +} + +export class MoveOperations { + + public static left(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, lineNumber: number, column: number): CursorMoveResult { + + if (column > model.getLineMinColumn(lineNumber)) { + if (CursorMove.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 moveLeft(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, cursor: CursorModelState, inSelectionMode: boolean, noOfColumns: number): MoveOperationResult { + let lineNumber: number, + column: number; + + 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 + lineNumber = cursor.selection.startLineNumber; + column = cursor.selection.startColumn; + } else { + let r = MoveOperations.left(config, model, cursor.position.lineNumber, cursor.position.column - (noOfColumns - 1)); + lineNumber = r.lineNumber; + column = r.column; + } + + return new MoveOperationResult(inSelectionMode, lineNumber, column, 0, true, CursorChangeReason.Explicit); + } + + public static right(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, lineNumber: number, column: number): CursorMoveResult { + + if (column < model.getLineMaxColumn(lineNumber)) { + if (CursorMove.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 moveRight(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, cursor: CursorModelState, inSelectionMode: boolean, noOfColumns: number): MoveOperationResult { + let lineNumber: number, + column: number; + + 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 + lineNumber = cursor.selection.endLineNumber; + column = cursor.selection.endColumn; + } else { + let r = MoveOperations.right(config, model, cursor.position.lineNumber, cursor.position.column + (noOfColumns - 1)); + lineNumber = r.lineNumber; + column = r.column; + } + + return new MoveOperationResult(inSelectionMode, lineNumber, column, 0, true, CursorChangeReason.Explicit); + } + + public static down(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnLastLine: boolean): CursorMoveResult { + const currentVisibleColumn = CursorMove.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 (CursorMove.isInsideSurrogatePair(model, lineNumber, column)) { + column = column - 1; + } + } + } else { + column = CursorMove.columnFromVisibleColumn(config, model, lineNumber, currentVisibleColumn); + if (CursorMove.isInsideSurrogatePair(model, lineNumber, column)) { + column = column - 1; + } + } + + leftoverVisibleColumns = currentVisibleColumn - CursorMove.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize); + + return new CursorMoveResult(lineNumber, column, leftoverVisibleColumns); + } + + public static moveDown(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, cursor: CursorModelState, inSelectionMode: boolean, linesCount: number): MoveOperationResult { + let lineNumber: number, + column: number; + + if (cursor.hasSelection() && !inSelectionMode) { + // If we are in selection mode, move down acts relative to the end of selection + lineNumber = cursor.selection.endLineNumber; + column = cursor.selection.endColumn; + } else { + lineNumber = cursor.position.lineNumber; + column = cursor.position.column; + } + + let r = MoveOperations.down(config, model, lineNumber, column, cursor.leftoverVisibleColumns, linesCount, true); + + return new MoveOperationResult(inSelectionMode, r.lineNumber, r.column, r.leftoverVisibleColumns, true, CursorChangeReason.Explicit); + } + + public static up(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnFirstLine: boolean): CursorMoveResult { + const currentVisibleColumn = CursorMove.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 (CursorMove.isInsideSurrogatePair(model, lineNumber, column)) { + column = column - 1; + } + } + } else { + column = CursorMove.columnFromVisibleColumn(config, model, lineNumber, currentVisibleColumn); + if (CursorMove.isInsideSurrogatePair(model, lineNumber, column)) { + column = column - 1; + } + } + + leftoverVisibleColumns = currentVisibleColumn - CursorMove.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize); + + return new CursorMoveResult(lineNumber, column, leftoverVisibleColumns); + } + + public static moveUp(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, cursor: CursorModelState, inSelectionMode: boolean, linesCount: number): MoveOperationResult { + let lineNumber: number, + column: number; + + if (cursor.hasSelection() && !inSelectionMode) { + // If we are in selection mode, move up acts relative to the beginning of selection + lineNumber = cursor.selection.startLineNumber; + column = cursor.selection.startColumn; + } else { + lineNumber = cursor.position.lineNumber; + column = cursor.position.column; + } + + let r = MoveOperations.up(config, model, lineNumber, column, cursor.leftoverVisibleColumns, linesCount, true); + + return new MoveOperationResult(inSelectionMode, r.lineNumber, r.column, r.leftoverVisibleColumns, true, CursorChangeReason.Explicit); + } +} diff --git a/src/vs/editor/common/controller/cursorWordOperations.ts b/src/vs/editor/common/controller/cursorWordOperations.ts new file mode 100644 index 00000000000..0568e63a003 --- /dev/null +++ b/src/vs/editor/common/controller/cursorWordOperations.ts @@ -0,0 +1,261 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { CursorMoveConfiguration, ICursorMoveHelperModel } from 'vs/editor/common/controller/cursorMoveHelper'; +import { Position } from 'vs/editor/common/core/position'; +import { CharCode } from 'vs/base/common/charCode'; +import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier'; +import { MoveOperationResult } from 'vs/editor/common/controller/cursorMoveOperations'; +import { CursorChangeReason } from 'vs/editor/common/editorCommon'; +import { CursorModelState } from 'vs/editor/common/controller/oneCursor'; + +export interface IFindWordResult { + /** + * The index where the word starts. + */ + start: number; + /** + * The index where the word ends. + */ + end: number; + /** + * The word type. + */ + wordType: WordType; +} + +export const enum WordType { + None = 0, + Regular = 1, + Separator = 2 +} + +const enum CharacterClass { + Regular = 0, + Whitespace = 1, + WordSeparator = 2 +} + +export const enum WordNavigationType { + WordStart = 0, + WordEnd = 1 +} + +class WordCharacterClassifier extends CharacterClassifier { + + constructor(wordSeparators: string) { + super(CharacterClass.Regular); + + for (let i = 0, len = wordSeparators.length; i < len; i++) { + this.set(wordSeparators.charCodeAt(i), CharacterClass.WordSeparator); + } + + this.set(CharCode.Space, CharacterClass.Whitespace); + this.set(CharCode.Tab, CharacterClass.Whitespace); + } + +} + +function once(computeFn: (input: string) => R): (input: string) => R { + let cache: { [key: string]: R; } = {}; // TODO@Alex unbounded cache + return (input: string): R => { + if (!cache.hasOwnProperty(input)) { + cache[input] = computeFn(input); + } + return cache[input]; + }; +} + +let getMapForWordSeparators = once( + (input) => new WordCharacterClassifier(input) +); + +export class WordOperations { + + 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 }; + } + + public static findPreviousWordOnLine(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, position: Position): IFindWordResult { + let wordSeparators = getMapForWordSeparators(config.wordSeparators); + let lineContent = model.getLineContent(position.lineNumber); + return this._findPreviousWordOnLine(lineContent, wordSeparators, position); + } + + private static _findPreviousWordOnLine(lineContent: string, wordSeparators: WordCharacterClassifier, position: Position): IFindWordResult { + let wordType = WordType.None; + for (let chIndex = position.column - 2; chIndex >= 0; chIndex--) { + let chCode = lineContent.charCodeAt(chIndex); + let chClass = wordSeparators.get(chCode); + + if (chClass === CharacterClass.Regular) { + if (wordType === WordType.Separator) { + return this._createWord(lineContent, wordType, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1)); + } + wordType = WordType.Regular; + } else if (chClass === CharacterClass.WordSeparator) { + if (wordType === WordType.Regular) { + return this._createWord(lineContent, wordType, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1)); + } + wordType = WordType.Separator; + } else if (chClass === CharacterClass.Whitespace) { + if (wordType !== WordType.None) { + return this._createWord(lineContent, wordType, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1)); + } + } + } + + if (wordType !== WordType.None) { + return this._createWord(lineContent, wordType, 0, this._findEndOfWord(lineContent, wordSeparators, wordType, 0)); + } + + return null; + } + + private static _findEndOfWord(lineContent: string, wordSeparators: WordCharacterClassifier, wordType: WordType, startIndex: number): number { + let len = lineContent.length; + for (let chIndex = startIndex; chIndex < len; chIndex++) { + let chCode = lineContent.charCodeAt(chIndex); + let chClass = wordSeparators.get(chCode); + + if (chClass === CharacterClass.Whitespace) { + return chIndex; + } + if (wordType === WordType.Regular && chClass === CharacterClass.WordSeparator) { + return chIndex; + } + if (wordType === WordType.Separator && chClass === CharacterClass.Regular) { + return chIndex; + } + } + return len; + } + + public static findNextWordOnLine(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, position: Position): IFindWordResult { + let wordSeparators = getMapForWordSeparators(config.wordSeparators); + let lineContent = model.getLineContent(position.lineNumber); + return this._findNextWordOnLine(lineContent, wordSeparators, position); + } + + private static _findNextWordOnLine(lineContent: string, wordSeparators: WordCharacterClassifier, position: Position): IFindWordResult { + let wordType = WordType.None; + let len = lineContent.length; + + for (let chIndex = position.column - 1; chIndex < len; chIndex++) { + let chCode = lineContent.charCodeAt(chIndex); + let chClass = wordSeparators.get(chCode); + + if (chClass === CharacterClass.Regular) { + if (wordType === WordType.Separator) { + return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex); + } + wordType = WordType.Regular; + } else if (chClass === CharacterClass.WordSeparator) { + if (wordType === WordType.Regular) { + return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex); + } + wordType = WordType.Separator; + } else if (chClass === CharacterClass.Whitespace) { + if (wordType !== WordType.None) { + return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex); + } + } + } + + if (wordType !== WordType.None) { + return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordSeparators, wordType, len - 1), len); + } + + return null; + } + + private static _findStartOfWord(lineContent: string, wordSeparators: WordCharacterClassifier, wordType: WordType, startIndex: number): number { + for (let chIndex = startIndex; chIndex >= 0; chIndex--) { + let chCode = lineContent.charCodeAt(chIndex); + let chClass = wordSeparators.get(chCode); + + if (chClass === CharacterClass.Whitespace) { + return chIndex + 1; + } + if (wordType === WordType.Regular && chClass === CharacterClass.WordSeparator) { + return chIndex + 1; + } + if (wordType === WordType.Separator && chClass === CharacterClass.Regular) { + return chIndex + 1; + } + } + return 0; + } + + public static moveWordLeft(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, cursor: CursorModelState, inSelectionMode: boolean, wordNavigationType: WordNavigationType): MoveOperationResult { + let position = cursor.position; + let lineNumber = position.lineNumber; + let column = position.column; + + if (column === 1) { + if (lineNumber > 1) { + lineNumber = lineNumber - 1; + column = model.getLineMaxColumn(lineNumber); + } + } + + let prevWordOnLine = WordOperations.findPreviousWordOnLine(config, model, new Position(lineNumber, column)); + + if (wordNavigationType === WordNavigationType.WordStart) { + if (prevWordOnLine) { + column = prevWordOnLine.start + 1; + } else { + column = 1; + } + } else { + if (prevWordOnLine && column <= prevWordOnLine.end + 1) { + prevWordOnLine = WordOperations.findPreviousWordOnLine(config, model, new Position(lineNumber, prevWordOnLine.start + 1)); + } + if (prevWordOnLine) { + column = prevWordOnLine.end + 1; + } else { + column = 1; + } + } + + return new MoveOperationResult(inSelectionMode, lineNumber, column, 0, true, CursorChangeReason.Explicit); + } + + public static moveWordRight(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, cursor: CursorModelState, inSelectionMode: boolean, wordNavigationType: WordNavigationType): MoveOperationResult { + let position = cursor.position; + let lineNumber = position.lineNumber; + let column = position.column; + + if (column === model.getLineMaxColumn(lineNumber)) { + if (lineNumber < model.getLineCount()) { + lineNumber = lineNumber + 1; + column = 1; + } + } + + let nextWordOnLine = WordOperations.findNextWordOnLine(config, model, new Position(lineNumber, column)); + + if (wordNavigationType === WordNavigationType.WordEnd) { + if (nextWordOnLine) { + column = nextWordOnLine.end + 1; + } else { + column = model.getLineMaxColumn(lineNumber); + } + } else { + if (nextWordOnLine && column >= nextWordOnLine.start + 1) { + nextWordOnLine = WordOperations.findNextWordOnLine(config, model, new Position(lineNumber, nextWordOnLine.end + 1)); + } + if (nextWordOnLine) { + column = nextWordOnLine.start + 1; + } else { + column = model.getLineMaxColumn(lineNumber); + } + } + + return new MoveOperationResult(inSelectionMode, lineNumber, column, 0, true, CursorChangeReason.Explicit); + } +} diff --git a/src/vs/editor/common/controller/oneCursor.ts b/src/vs/editor/common/controller/oneCursor.ts index d2ad9252588..0e1d15dbeed 100644 --- a/src/vs/editor/common/controller/oneCursor.ts +++ b/src/vs/editor/common/controller/oneCursor.ts @@ -17,9 +17,10 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { IndentAction } from 'vs/editor/common/modes/languageConfiguration'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; 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'; +import { MoveOperations, MoveOperationResult } from 'vs/editor/common/controller/cursorMoveOperations'; +import { WordType, WordOperations, WordNavigationType } from 'vs/editor/common/controller/cursorWordOperations'; export interface IPostOperationRunnable { (ctx: IOneCursorOperationContext): void; @@ -76,7 +77,9 @@ export interface IViewModelHelper { convertViewRangeToModelRange(viewRange: Range): Range; validateViewPosition(viewLineNumber: number, viewColumn: number, modelPosition: Position): Position; + validateViewPosition2(viewPosition: Position, modelPosition: Position): Position; validateViewRange(viewStartLineNumber: number, viewStartColumn: number, viewEndLineNumber: number, viewEndColumn: number, modelRange: Range): Range; + validateViewRange2(viewRange: Range, modelRange: Range): Range; } export interface IOneCursorState { @@ -88,38 +91,6 @@ export interface IOneCursorState { selectionStartLeftoverVisibleColumns: number; } -export interface IFindWordResult { - /** - * The index where the word starts. - */ - start: number; - /** - * The index where the word ends. - */ - end: number; - /** - * The word type. - */ - wordType: WordType; -} - -export const enum WordType { - None = 0, - Regular = 1, - Separator = 2 -}; - -const enum CharacterClass { - Regular = 0, - Whitespace = 1, - WordSeparator = 2 -}; - -export const enum WordNavigationType { - WordStart = 0, - WordEnd = 1 -} - /** * Represents the cursor state on either the model or on the view model. */ @@ -146,6 +117,19 @@ export class CursorModelState { this.selection = CursorModelState._computeSelection(this.selectionStart, this.position); } + public equals(other: CursorModelState) { + return ( + this.selectionStartLeftoverVisibleColumns === other.selectionStartLeftoverVisibleColumns + && this.leftoverVisibleColumns === other.leftoverVisibleColumns + && this.position.equals(other.position) + && this.selectionStart.equalsRange(other.selectionStart) + ); + } + + public hasSelection(): boolean { + return (!this.selection.isEmpty() || !this.selectionStart.isEmpty()); + } + public withSelectionStartLeftoverVisibleColumns(selectionStartLeftoverVisibleColumns: number): CursorModelState { return new CursorModelState( this.selectionStart, @@ -273,8 +257,16 @@ export class OneCursor implements IOneCursor { this._modelOptionsListener = model.onDidChangeOptions(() => this._recreateCursorHelper()); this._configChangeListener = this.configuration.onDidChange((e) => { + let shouldRecreate = false; if (e.layoutInfo) { // due to pageSize + shouldRecreate = true; + } + if (e.wordSeparators) { + shouldRecreate = true; + } + + if (shouldRecreate) { this._recreateCursorHelper(); } }); @@ -287,15 +279,40 @@ export class OneCursor implements IOneCursor { ); } + /** + * Sometimes, the line mapping changes and the stored view position is stale. + */ + public ensureValidState(): void { + this._setState(this.modelState, this.viewState); + } + private _recreateCursorHelper(): void { this.config = new CursorMoveConfiguration( this.model.getOptions().tabSize, - this.getPageSize() + this.getPageSize(), + this.configuration.editor.wordSeparators ); - this.helper = new CursorHelper(this.model, this.configuration, this.config); + this.helper = new CursorHelper(this.model, this.config); } private _setState(modelState: CursorModelState, viewState: CursorModelState): void { + // Validate new model state + let selectionStart = this.model.validateRange(modelState.selectionStart); + let selectionStartLeftoverVisibleColumns = modelState.selectionStart.equalsRange(selectionStart) ? modelState.selectionStartLeftoverVisibleColumns : 0; + let position = this.model.validatePosition(modelState.position); + let leftoverVisibleColumns = modelState.position.equals(position) ? modelState.leftoverVisibleColumns : 0; + modelState = new CursorModelState(selectionStart, selectionStartLeftoverVisibleColumns, position, leftoverVisibleColumns); + + // Validate new view state + let viewSelectionStart = this.viewModelHelper.validateViewRange2(viewState.selectionStart, modelState.selectionStart); + let viewPosition = this.viewModelHelper.validateViewPosition2(viewState.position, modelState.position); + viewState = new CursorModelState(viewSelectionStart, selectionStartLeftoverVisibleColumns, viewPosition, leftoverVisibleColumns); + + if (this.modelState && this.viewState && this.modelState.equals(modelState) && this.viewState.equals(viewState)) { + // No-op, early return + return; + } + this.modelState = modelState; this.viewState = viewState; @@ -509,18 +526,11 @@ export class OneCursor implements IOneCursor { // -------------------- START reading API - public getPageSize(): number { + private getPageSize(): number { let c = this.configuration.editor; return Math.floor(c.layoutInfo.height / c.fontInfo.lineHeight) - 2; } - public getValidViewPosition(): Position { - return this.viewModelHelper.validateViewPosition(this.viewState.position.lineNumber, this.viewState.position.column, this.modelState.position); - } - - public hasSelection(): boolean { - return (!this.modelState.selection.isEmpty() || !this.modelState.selectionStart.isEmpty()); - } public getBracketsDecorations(): string[] { return this.bracketDecorations; } @@ -596,21 +606,9 @@ export class OneCursor implements IOneCursor { let visibleLineNumber = visibleRange.endLineNumber - (lineFromBottom - 1); return visibleLineNumber > visibleRange.startLineNumber ? visibleLineNumber : this.getLineFromViewPortTop(); } - public findPreviousWordOnLine(position: Position): IFindWordResult { - return this.helper.findPreviousWordOnLine(position); - } - public findNextWordOnLine(position: Position): IFindWordResult { - return this.helper.findNextWordOnLine(position); - } 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 getViewVisibleColumnFromColumn(viewLineNumber: number, viewColumn: number): number { - return this.helper.visibleColumnFromColumn(this.viewModelHelper.viewModel, viewLineNumber, viewColumn); - } // -- view public isLastLineVisibleInViewPort(): boolean { @@ -742,9 +740,9 @@ export class OneCursorOp { } let inSelectionMode = !!moveParams.select; - let validatedViewPosition = cursor.getValidViewPosition(); + let validatedViewPosition = cursor.viewState.position; let viewLineNumber = validatedViewPosition.lineNumber; - let viewColumn; + let viewColumn: number; switch (moveParams.to) { case editorCommon.CursorMovePosition.Left: return this.moveLeft(cursor, inSelectionMode, editorCommon.CursorMoveByUnit.HalfLine === moveParams.by ? cursor.getViewHalfLineSize(viewLineNumber) : moveParams.value, ctx); @@ -796,7 +794,7 @@ export class OneCursorOp { private static _columnSelectOp(cursor: OneCursor, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult { let viewStartSelection = cursor.viewState.selection; - let fromVisibleColumn = cursor.getViewVisibleColumnFromColumn(viewStartSelection.selectionStartLineNumber, viewStartSelection.selectionStartColumn); + let fromVisibleColumn = CursorMove.visibleColumnFromColumn2(cursor.config, cursor.viewModel, new Position(viewStartSelection.selectionStartLineNumber, viewStartSelection.selectionStartColumn)); return cursor.columnSelect(viewStartSelection.selectionStartLineNumber, fromVisibleColumn, toViewLineNumber, toViewVisualColumn); } @@ -828,7 +826,7 @@ export class OneCursorOp { 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); + let lineMaxVisualViewColumn = CursorMove.visibleColumnFromColumn2(cursor.config, cursor.viewModel, new Position(lineNumber, lineMaxViewColumn)); maxVisualViewColumn = Math.max(maxVisualViewColumn, lineMaxVisualViewColumn); } @@ -840,7 +838,7 @@ export class OneCursorOp { } public static columnSelectUp(isPaged: boolean, cursor: OneCursor, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult { - let linesCount = isPaged ? cursor.getPageSize() : 1; + let linesCount = isPaged ? cursor.config.pageSize : 1; toViewLineNumber -= linesCount; if (toViewLineNumber < 1) { @@ -851,7 +849,7 @@ export class OneCursorOp { } public static columnSelectDown(isPaged: boolean, cursor: OneCursor, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult { - let linesCount = isPaged ? cursor.getPageSize() : 1; + let linesCount = isPaged ? cursor.config.pageSize : 1; toViewLineNumber += linesCount; if (toViewLineNumber > cursor.getViewLineCount()) { @@ -861,124 +859,48 @@ export class OneCursorOp { return this._columnSelectOp(cursor, toViewLineNumber, toViewVisualColumn); } - public static moveLeft(cursor: OneCursor, inSelectionMode: boolean, noOfColumns: number = 1, ctx: IOneCursorOperationContext): boolean { - let viewLineNumber: number, - viewColumn: number; - - 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.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 = CursorMove.left(cursor.config, cursor.viewModel, validatedViewPosition.lineNumber, validatedViewPosition.column - (noOfColumns - 1)); - viewLineNumber = r.lineNumber; - viewColumn = r.column; - } - - ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit; - cursor.moveViewPosition(inSelectionMode, viewLineNumber, viewColumn, 0, true); + private static _applyMoveModelOperation(cursor: OneCursor, ctx: IOneCursorOperationContext, r: MoveOperationResult): boolean { + ctx.cursorPositionChangeReason = r.reason; + cursor.moveModelPosition(r.inSelectionMode, r.lineNumber, r.column, r.leftoverVisibleColumns, r.ensureInEditableRange); return true; } + private static _applyMoveViewOperation(cursor: OneCursor, ctx: IOneCursorOperationContext, r: MoveOperationResult): boolean { + ctx.cursorPositionChangeReason = r.reason; + cursor.moveViewPosition(r.inSelectionMode, r.lineNumber, r.column, r.leftoverVisibleColumns, r.ensureInEditableRange); + return true; + } + + public static moveLeft(cursor: OneCursor, inSelectionMode: boolean, noOfColumns: number = 1, ctx: IOneCursorOperationContext): boolean { + return this._applyMoveViewOperation( + cursor, ctx, + MoveOperations.moveLeft(cursor.config, cursor.viewModel, cursor.viewState, inSelectionMode, noOfColumns) + ); + } + public static moveWordLeft(cursor: OneCursor, inSelectionMode: boolean, wordNavigationType: WordNavigationType, ctx: IOneCursorOperationContext): boolean { - let position = cursor.modelState.position; - let lineNumber = position.lineNumber; - let column = position.column; - - if (column === 1) { - if (lineNumber > 1) { - lineNumber = lineNumber - 1; - column = cursor.model.getLineMaxColumn(lineNumber); - } - } - - let prevWordOnLine = cursor.findPreviousWordOnLine(new Position(lineNumber, column)); - - if (wordNavigationType === WordNavigationType.WordStart) { - if (prevWordOnLine) { - column = prevWordOnLine.start + 1; - } else { - column = 1; - } - } else { - if (prevWordOnLine && column <= prevWordOnLine.end + 1) { - prevWordOnLine = cursor.findPreviousWordOnLine(new Position(lineNumber, prevWordOnLine.start + 1)); - } - if (prevWordOnLine) { - column = prevWordOnLine.end + 1; - } else { - column = 1; - } - } - - ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit; - cursor.moveModelPosition(inSelectionMode, lineNumber, column, 0, true); - return true; + return this._applyMoveModelOperation( + cursor, ctx, + WordOperations.moveWordLeft(cursor.config, cursor.model, cursor.modelState, inSelectionMode, wordNavigationType) + ); } public static moveRight(cursor: OneCursor, inSelectionMode: boolean, noOfColumns: number = 1, ctx: IOneCursorOperationContext): boolean { - let viewLineNumber: number, - viewColumn: number; - - 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.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 = CursorMove.right(cursor.config, cursor.viewModel, validatedViewPosition.lineNumber, validatedViewPosition.column + (noOfColumns - 1)); - viewLineNumber = r.lineNumber; - viewColumn = r.column; - } - - ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit; - cursor.moveViewPosition(inSelectionMode, viewLineNumber, viewColumn, 0, true); - return true; + return this._applyMoveViewOperation( + cursor, ctx, + MoveOperations.moveRight(cursor.config, cursor.viewModel, cursor.viewState, inSelectionMode, noOfColumns) + ); } public static moveWordRight(cursor: OneCursor, inSelectionMode: boolean, wordNavigationType: WordNavigationType, ctx: IOneCursorOperationContext): boolean { - let position = cursor.modelState.position; - let lineNumber = position.lineNumber; - let column = position.column; - - if (column === cursor.model.getLineMaxColumn(lineNumber)) { - if (lineNumber < cursor.model.getLineCount()) { - lineNumber = lineNumber + 1; - column = 1; - } - } - - let nextWordOnLine = cursor.findNextWordOnLine(new Position(lineNumber, column)); - - if (wordNavigationType === WordNavigationType.WordEnd) { - if (nextWordOnLine) { - column = nextWordOnLine.end + 1; - } else { - column = cursor.model.getLineMaxColumn(lineNumber); - } - } else { - if (nextWordOnLine && column >= nextWordOnLine.start + 1) { - nextWordOnLine = cursor.findNextWordOnLine(new Position(lineNumber, nextWordOnLine.end + 1)); - } - if (nextWordOnLine) { - column = nextWordOnLine.start + 1; - } else { - column = cursor.model.getLineMaxColumn(lineNumber); - } - } - - ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit; - cursor.moveModelPosition(inSelectionMode, lineNumber, column, 0, true); - return true; + return this._applyMoveModelOperation( + cursor, ctx, + WordOperations.moveWordRight(cursor.config, cursor.model, cursor.modelState, inSelectionMode, wordNavigationType) + ); } public static moveDown(cursor: OneCursor, moveArguments: CursorMoveArguments, ctx: IOneCursorOperationContext): boolean { - let linesCount = (moveArguments.isPaged ? (moveArguments.pageSize || cursor.getPageSize()) : moveArguments.value) || 1; + let linesCount = (moveArguments.isPaged ? (moveArguments.pageSize || cursor.config.pageSize) : moveArguments.value) || 1; if (editorCommon.CursorMoveByUnit.WrappedLine === moveArguments.by) { return this._moveDownByViewLines(cursor, moveArguments.select, linesCount, ctx); } @@ -986,57 +908,28 @@ export class OneCursorOp { } private static _moveDownByViewLines(cursor: OneCursor, inSelectionMode: boolean, linesCount: number, ctx: IOneCursorOperationContext): boolean { - let viewLineNumber: number, - viewColumn: number; - - if (cursor.hasSelection() && !inSelectionMode) { - // If we are in selection mode, move down acts relative to the end of selection - 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(); - viewLineNumber = validatedViewPosition.lineNumber; - viewColumn = validatedViewPosition.column; - } - - 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; + return this._applyMoveViewOperation( + cursor, ctx, + MoveOperations.moveDown(cursor.config, cursor.viewModel, cursor.viewState, inSelectionMode, linesCount) + ); } private static _moveDownByModelLines(cursor: OneCursor, inSelectionMode: boolean, linesCount: number, ctx: IOneCursorOperationContext): boolean { - let lineNumber: number, - column: number; - - if (cursor.hasSelection() && !inSelectionMode) { - // If we are in selection mode, move down acts relative to the end of selection - let selection = cursor.modelState.selection; - lineNumber = selection.endLineNumber; - column = selection.endColumn; - } else { - let position = cursor.modelState.position; - lineNumber = position.lineNumber; - column = position.column; - } - - 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; + return this._applyMoveModelOperation( + cursor, ctx, + MoveOperations.moveDown(cursor.config, cursor.model, cursor.modelState, inSelectionMode, linesCount) + ); } public static translateDown(cursor: OneCursor, ctx: IOneCursorOperationContext): boolean { let selection = cursor.viewState.selection; - let selectionStart = CursorMove.down(cursor.config, cursor.viewModel, selection.selectionStartLineNumber, selection.selectionStartColumn, cursor.viewState.selectionStartLeftoverVisibleColumns, 1, false); + let selectionStart = MoveOperations.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.viewState.leftoverVisibleColumns, true); - let position = CursorMove.down(cursor.config, cursor.viewModel, selection.positionLineNumber, selection.positionColumn, cursor.viewState.leftoverVisibleColumns, 1, false); + let position = MoveOperations.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); @@ -1046,7 +939,7 @@ export class OneCursorOp { } public static moveUp(cursor: OneCursor, moveArguments: CursorMoveArguments, ctx: IOneCursorOperationContext): boolean { - let linesCount = (moveArguments.isPaged ? (moveArguments.pageSize || cursor.getPageSize()) : moveArguments.value) || 1; + let linesCount = (moveArguments.isPaged ? (moveArguments.pageSize || cursor.config.pageSize) : moveArguments.value) || 1; if (editorCommon.CursorMoveByUnit.WrappedLine === moveArguments.by) { return this._moveUpByViewLines(cursor, moveArguments.select, linesCount, ctx); } @@ -1054,60 +947,28 @@ export class OneCursorOp { } private static _moveUpByViewLines(cursor: OneCursor, inSelectionMode: boolean, linesCount: number, ctx: IOneCursorOperationContext): boolean { - - let viewLineNumber: number, - viewColumn: number; - - if (cursor.hasSelection() && !inSelectionMode) { - // If we are in selection mode, move up acts relative to the beginning of selection - 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(); - viewLineNumber = validatedViewPosition.lineNumber; - viewColumn = validatedViewPosition.column; - } - - 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); - - return true; + return this._applyMoveViewOperation( + cursor, ctx, + MoveOperations.moveUp(cursor.config, cursor.model, cursor.modelState, inSelectionMode, linesCount) + ); } private static _moveUpByModelLines(cursor: OneCursor, inSelectionMode: boolean, linesCount: number, ctx: IOneCursorOperationContext): boolean { - let lineNumber: number, - column: number; - - if (cursor.hasSelection() && !inSelectionMode) { - // If we are in selection mode, move up acts relative to the beginning of selection - let selection = cursor.modelState.selection; - lineNumber = selection.startLineNumber; - column = selection.startColumn; - } else { - let position = cursor.modelState.position; - lineNumber = position.lineNumber; - column = position.column; - } - - 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); - - return true; + return this._applyMoveModelOperation( + cursor, ctx, + MoveOperations.moveUp(cursor.config, cursor.model, cursor.modelState, inSelectionMode, linesCount) + ); } public static translateUp(cursor: OneCursor, ctx: IOneCursorOperationContext): boolean { let selection = cursor.viewState.selection; - let selectionStart = CursorMove.up(cursor.config, cursor.viewModel, selection.selectionStartLineNumber, selection.selectionStartColumn, cursor.viewState.selectionStartLeftoverVisibleColumns, 1, false); + let selectionStart = MoveOperations.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.viewState.leftoverVisibleColumns, true); - let position = CursorMove.up(cursor.config, cursor.viewModel, selection.positionLineNumber, selection.positionColumn, cursor.viewState.leftoverVisibleColumns, 1, false); + let position = MoveOperations.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); @@ -1117,7 +978,7 @@ export class OneCursorOp { } public static moveToBeginningOfLine(cursor: OneCursor, inSelectionMode: boolean, ctx: IOneCursorOperationContext): boolean { - let validatedViewPosition = cursor.getValidViewPosition(); + let validatedViewPosition = cursor.viewState.position; let viewLineNumber = validatedViewPosition.lineNumber; let viewColumn = validatedViewPosition.column; @@ -1128,7 +989,7 @@ export class OneCursorOp { } public static moveToEndOfLine(cursor: OneCursor, inSelectionMode: boolean, ctx: IOneCursorOperationContext): boolean { - let validatedViewPosition = cursor.getValidViewPosition(); + let validatedViewPosition = cursor.viewState.position; let viewLineNumber = validatedViewPosition.lineNumber; let viewColumn = validatedViewPosition.column; @@ -1153,7 +1014,7 @@ export class OneCursorOp { viewEndColumn = viewEndMaxColumn; } else { // Expand selection with one more line down - let moveResult = CursorMove.down(cursor.config, cursor.viewModel, viewEndLineNumber, viewEndColumn, 0, 1, true); + let moveResult = MoveOperations.down(cursor.config, cursor.viewModel, viewEndLineNumber, viewEndColumn, 0, 1, true); viewEndLineNumber = moveResult.lineNumber; viewEndColumn = cursor.getViewLineMaxColumn(viewEndLineNumber); } @@ -1230,7 +1091,7 @@ export class OneCursorOp { ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit; ctx.shouldRevealHorizontal = false; - if (!inSelectionMode || !cursor.hasSelection()) { + if (!inSelectionMode || !cursor.modelState.hasSelection()) { // Entering line selection for the first time let selectToLineNumber = position.lineNumber + 1; @@ -1242,7 +1103,7 @@ export class OneCursorOp { let selectionStartRange = new Range(position.lineNumber, 1, selectToLineNumber, selectToColumn); cursor.setSelectionStart(selectionStartRange); - cursor.moveModelPosition(cursor.hasSelection(), selectionStartRange.endLineNumber, selectionStartRange.endColumn, 0, false); + cursor.moveModelPosition(cursor.modelState.hasSelection(), selectionStartRange.endLineNumber, selectionStartRange.endColumn, 0, false); return true; } else { @@ -1251,7 +1112,7 @@ export class OneCursorOp { if (position.lineNumber < enteringLineNumber) { - cursor.moveViewPosition(cursor.hasSelection(), viewPosition.lineNumber, 1, 0, false); + cursor.moveViewPosition(cursor.modelState.hasSelection(), viewPosition.lineNumber, 1, 0, false); } else if (position.lineNumber > enteringLineNumber) { @@ -1261,12 +1122,12 @@ export class OneCursorOp { selectToViewLineNumber = cursor.getViewLineCount(); selectToViewColumn = cursor.getViewLineMaxColumn(selectToViewLineNumber); } - cursor.moveViewPosition(cursor.hasSelection(), selectToViewLineNumber, selectToViewColumn, 0, false); + cursor.moveViewPosition(cursor.modelState.hasSelection(), selectToViewLineNumber, selectToViewColumn, 0, false); } else { let endPositionOfSelectionStart = cursor.modelState.selectionStart.getEndPosition(); - cursor.moveModelPosition(cursor.hasSelection(), endPositionOfSelectionStart.lineNumber, endPositionOfSelectionStart.column, 0, false); + cursor.moveModelPosition(cursor.modelState.hasSelection(), endPositionOfSelectionStart.lineNumber, endPositionOfSelectionStart.column, 0, false); } @@ -1280,14 +1141,14 @@ export class OneCursorOp { // TODO@Alex -> select in editable range let validatedPosition = cursor.validatePosition(position); - let prevWord = cursor.findPreviousWordOnLine(validatedPosition); + let prevWord = WordOperations.findPreviousWordOnLine(cursor.config, cursor.model, validatedPosition); let isInPrevWord = (prevWord && prevWord.wordType === WordType.Regular && prevWord.start < validatedPosition.column - 1 && validatedPosition.column - 1 <= prevWord.end); - let nextWord = cursor.findNextWordOnLine(validatedPosition); + let nextWord = WordOperations.findNextWordOnLine(cursor.config, cursor.model, validatedPosition); let isInNextWord = (nextWord && nextWord.wordType === WordType.Regular && nextWord.start < validatedPosition.column - 1 && validatedPosition.column - 1 <= nextWord.end); let lineNumber: number; let column: number; - if (!inSelectionMode || !cursor.hasSelection()) { + if (!inSelectionMode || !cursor.modelState.hasSelection()) { let startColumn: number; let endColumn: number; @@ -1348,12 +1209,12 @@ export class OneCursorOp { } ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit; - cursor.moveModelPosition(cursor.hasSelection(), lineNumber, column, 0, false); + cursor.moveModelPosition(cursor.modelState.hasSelection(), lineNumber, column, 0, false); return true; } public static cancelSelection(cursor: OneCursor, ctx: IOneCursorOperationContext): boolean { - if (!cursor.hasSelection()) { + if (!cursor.modelState.hasSelection()) { return false; } @@ -1706,7 +1567,7 @@ export class OneCursorOp { let position = selection.getStartPosition(); let modelOpts = cursor.model.getOptions(); if (modelOpts.insertSpaces) { - let visibleColumnFromColumn = cursor.getVisibleColumnFromColumn(position.lineNumber, position.column); + let visibleColumnFromColumn = CursorMove.visibleColumnFromColumn2(cursor.config, cursor.model, position); let tabSize = modelOpts.tabSize; let spacesCnt = tabSize - (visibleColumnFromColumn % tabSize); for (let i = 0; i < spacesCnt; i++) { @@ -1873,7 +1734,7 @@ export class OneCursorOp { ); if (position.column <= lastIndentationColumn) { - let fromVisibleColumn = cursor.getVisibleColumnFromColumn(position.lineNumber, position.column); + let fromVisibleColumn = CursorMove.visibleColumnFromColumn2(cursor.config, cursor.model, position); let toVisibleColumn = CursorMoveHelper.prevTabColumn(fromVisibleColumn, cursor.model.getOptions().tabSize); let toColumn = CursorMove.columnFromVisibleColumn(cursor.config, cursor.model, position.lineNumber, toVisibleColumn); deleteSelection = new Range(position.lineNumber, toColumn, position.lineNumber, position.column); @@ -1881,7 +1742,7 @@ export class OneCursorOp { deleteSelection = new Range(position.lineNumber, position.column - 1, position.lineNumber, position.column); } } else { - let leftOfPosition = CursorMove.left(cursor.config, cursor.model, position.lineNumber, position.column); + let leftOfPosition = MoveOperations.left(cursor.config, cursor.model, position.lineNumber, position.column); deleteSelection = new Range( leftOfPosition.lineNumber, leftOfPosition.column, @@ -1940,7 +1801,7 @@ export class OneCursorOp { return true; } - let prevWordOnLine = cursor.findPreviousWordOnLine(position); + let prevWordOnLine = WordOperations.findPreviousWordOnLine(cursor.config, cursor.model, position); if (wordNavigationType === WordNavigationType.WordStart) { if (prevWordOnLine) { @@ -1950,7 +1811,7 @@ export class OneCursorOp { } } else { if (prevWordOnLine && column <= prevWordOnLine.end + 1) { - prevWordOnLine = cursor.findPreviousWordOnLine(new Position(lineNumber, prevWordOnLine.start + 1)); + prevWordOnLine = WordOperations.findPreviousWordOnLine(cursor.config, cursor.model, new Position(lineNumber, prevWordOnLine.start + 1)); } if (prevWordOnLine) { column = prevWordOnLine.end + 1; @@ -1975,7 +1836,7 @@ export class OneCursorOp { if (deleteSelection.isEmpty()) { let position = cursor.modelState.position; - let rightOfPosition = CursorMove.right(cursor.config, cursor.model, position.lineNumber, position.column); + let rightOfPosition = MoveOperations.right(cursor.config, cursor.model, position.lineNumber, position.column); deleteSelection = new Range( rightOfPosition.lineNumber, rightOfPosition.column, @@ -2042,7 +1903,7 @@ export class OneCursorOp { return true; } - let nextWordOnLine = cursor.findNextWordOnLine(position); + let nextWordOnLine = WordOperations.findNextWordOnLine(cursor.config, cursor.model, position); if (wordNavigationType === WordNavigationType.WordEnd) { if (nextWordOnLine) { @@ -2052,7 +1913,7 @@ export class OneCursorOp { column = maxColumn; } else { lineNumber++; - nextWordOnLine = cursor.findNextWordOnLine(new Position(lineNumber, 1)); + nextWordOnLine = WordOperations.findNextWordOnLine(cursor.config, cursor.model, new Position(lineNumber, 1)); if (nextWordOnLine) { column = nextWordOnLine.start + 1; } else { @@ -2062,7 +1923,7 @@ export class OneCursorOp { } } else { if (nextWordOnLine && column >= nextWordOnLine.start + 1) { - nextWordOnLine = cursor.findNextWordOnLine(new Position(lineNumber, nextWordOnLine.end + 1)); + nextWordOnLine = WordOperations.findNextWordOnLine(cursor.config, cursor.model, new Position(lineNumber, nextWordOnLine.end + 1)); } if (nextWordOnLine) { column = nextWordOnLine.start + 1; @@ -2071,7 +1932,7 @@ export class OneCursorOp { column = maxColumn; } else { lineNumber++; - nextWordOnLine = cursor.findNextWordOnLine(new Position(lineNumber, 1)); + nextWordOnLine = WordOperations.findNextWordOnLine(cursor.config, cursor.model, new Position(lineNumber, 1)); if (nextWordOnLine) { column = nextWordOnLine.start + 1; } else { @@ -2203,179 +2064,27 @@ export class OneCursorOp { class CursorHelper { private model: editorCommon.IModel; - private configuration: editorCommon.IConfiguration; - private moveHelper: CursorMoveHelper; + private config: CursorMoveConfiguration; - constructor(model: editorCommon.IModel, configuration: editorCommon.IConfiguration, config: CursorMoveConfiguration) { + constructor(model: editorCommon.IModel, config: CursorMoveConfiguration) { this.model = model; - this.configuration = configuration; - this.moveHelper = new CursorMoveHelper(config); + this.config = config; } public getColumnAtBeginningOfLine(model: ICursorMoveHelperModel, lineNumber: number, column: number): number { - return this.moveHelper.getColumnAtBeginningOfLine(model, lineNumber, column); + return CursorMoveHelper.getColumnAtBeginningOfLine(model, lineNumber, column); } public getColumnAtEndOfLine(model: ICursorMoveHelperModel, lineNumber: number, column: number): number { - return this.moveHelper.getColumnAtEndOfLine(model, lineNumber, column); + return CursorMoveHelper.getColumnAtEndOfLine(model, lineNumber, column); } public columnSelect(model: ICursorMoveHelperModel, fromLineNumber: number, fromVisibleColumn: number, toLineNumber: number, toVisibleColumn: number): IViewColumnSelectResult { - return this.moveHelper.columnSelect(model, fromLineNumber, fromVisibleColumn, toLineNumber, toVisibleColumn); - } - - public visibleColumnFromColumn(model: ICursorMoveHelperModel, lineNumber: number, column: number): number { - return this.moveHelper.visibleColumnFromColumn(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 }; - } - - public findPreviousWordOnLine(_position: Position): IFindWordResult { - let position = this.model.validatePosition(_position); - let wordSeparators = getMapForWordSeparators(this.configuration.editor.wordSeparators); - let lineContent = this.model.getLineContent(position.lineNumber); - return CursorHelper._findPreviousWordOnLine(lineContent, wordSeparators, position); - } - - private static _findPreviousWordOnLine(lineContent: string, wordSeparators: WordCharacterClassifier, position: Position): IFindWordResult { - let wordType = WordType.None; - for (let chIndex = position.column - 2; chIndex >= 0; chIndex--) { - let chCode = lineContent.charCodeAt(chIndex); - let chClass = wordSeparators.get(chCode); - - if (chClass === CharacterClass.Regular) { - if (wordType === WordType.Separator) { - return CursorHelper._createWord(lineContent, wordType, chIndex + 1, CursorHelper._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1)); - } - wordType = WordType.Regular; - } else if (chClass === CharacterClass.WordSeparator) { - if (wordType === WordType.Regular) { - return CursorHelper._createWord(lineContent, wordType, chIndex + 1, CursorHelper._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1)); - } - wordType = WordType.Separator; - } else if (chClass === CharacterClass.Whitespace) { - if (wordType !== WordType.None) { - return CursorHelper._createWord(lineContent, wordType, chIndex + 1, CursorHelper._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1)); - } - } - } - - if (wordType !== WordType.None) { - return CursorHelper._createWord(lineContent, wordType, 0, CursorHelper._findEndOfWord(lineContent, wordSeparators, wordType, 0)); - } - - return null; - } - - private static _findEndOfWord(lineContent: string, wordSeparators: WordCharacterClassifier, wordType: WordType, startIndex: number): number { - let len = lineContent.length; - for (let chIndex = startIndex; chIndex < len; chIndex++) { - let chCode = lineContent.charCodeAt(chIndex); - let chClass = wordSeparators.get(chCode); - - if (chClass === CharacterClass.Whitespace) { - return chIndex; - } - if (wordType === WordType.Regular && chClass === CharacterClass.WordSeparator) { - return chIndex; - } - if (wordType === WordType.Separator && chClass === CharacterClass.Regular) { - return chIndex; - } - } - return len; - } - - public findNextWordOnLine(_position: Position): IFindWordResult { - let position = this.model.validatePosition(_position); - let wordSeparators = getMapForWordSeparators(this.configuration.editor.wordSeparators); - let lineContent = this.model.getLineContent(position.lineNumber); - return CursorHelper._findNextWordOnLine(lineContent, wordSeparators, position); - } - - private static _findNextWordOnLine(lineContent: string, wordSeparators: WordCharacterClassifier, position: Position): IFindWordResult { - let wordType = WordType.None; - let len = lineContent.length; - - for (let chIndex = position.column - 1; chIndex < len; chIndex++) { - let chCode = lineContent.charCodeAt(chIndex); - let chClass = wordSeparators.get(chCode); - - if (chClass === CharacterClass.Regular) { - if (wordType === WordType.Separator) { - return CursorHelper._createWord(lineContent, wordType, CursorHelper._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex); - } - wordType = WordType.Regular; - } else if (chClass === CharacterClass.WordSeparator) { - if (wordType === WordType.Regular) { - return CursorHelper._createWord(lineContent, wordType, CursorHelper._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex); - } - wordType = WordType.Separator; - } else if (chClass === CharacterClass.Whitespace) { - if (wordType !== WordType.None) { - return CursorHelper._createWord(lineContent, wordType, CursorHelper._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex); - } - } - } - - if (wordType !== WordType.None) { - return CursorHelper._createWord(lineContent, wordType, CursorHelper._findStartOfWord(lineContent, wordSeparators, wordType, len - 1), len); - } - - return null; - } - - private static _findStartOfWord(lineContent: string, wordSeparators: WordCharacterClassifier, wordType: WordType, startIndex: number): number { - for (let chIndex = startIndex; chIndex >= 0; chIndex--) { - let chCode = lineContent.charCodeAt(chIndex); - let chClass = wordSeparators.get(chCode); - - if (chClass === CharacterClass.Whitespace) { - return chIndex + 1; - } - if (wordType === WordType.Regular && chClass === CharacterClass.WordSeparator) { - return chIndex + 1; - } - if (wordType === WordType.Separator && chClass === CharacterClass.Regular) { - return chIndex + 1; - } - } - return 0; - } -} - -class WordCharacterClassifier extends CharacterClassifier { - - constructor(wordSeparators: string) { - super(CharacterClass.Regular); - - for (let i = 0, len = wordSeparators.length; i < len; i++) { - this.set(wordSeparators.charCodeAt(i), CharacterClass.WordSeparator); - } - - this.set(CharCode.Space, CharacterClass.Whitespace); - this.set(CharCode.Tab, CharacterClass.Whitespace); + return CursorMoveHelper.columnSelect(this.config, model, fromLineNumber, fromVisibleColumn, toLineNumber, toVisibleColumn); } } -function once(computeFn: (input: string) => R): (input: string) => R { - let cache: { [key: string]: R; } = {}; // TODO@Alex unbounded cache - return (input: string): R => { - if (!cache.hasOwnProperty(input)) { - cache[input] = computeFn(input); - } - return cache[input]; - }; -} - -let getMapForWordSeparators = once( - (input) => new WordCharacterClassifier(input) -); - class Utils { /** diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index a6b0a628115..da4c5f80205 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -336,7 +336,7 @@ export class ViewModel extends EventEmitter implements IViewModel { } public validateViewRange(viewStartLineNumber: number, viewStartColumn: number, viewEndLineNumber: number, viewEndColumn: number, modelRange: Range): Range { - var validViewStart = this.validateViewPosition(viewStartColumn, viewStartColumn, modelRange.getStartPosition()); + var validViewStart = this.validateViewPosition(viewStartLineNumber, viewStartColumn, modelRange.getStartPosition()); var validViewEnd = this.validateViewPosition(viewEndLineNumber, viewEndColumn, modelRange.getEndPosition()); return new Range(validViewStart.lineNumber, validViewStart.column, validViewEnd.lineNumber, validViewEnd.column); } diff --git a/src/vs/editor/contrib/suggest/test/common/completionModel.test.ts b/src/vs/editor/contrib/suggest/test/common/completionModel.test.ts index 58e189f5408..0aeb47180cf 100644 --- a/src/vs/editor/contrib/suggest/test/common/completionModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/common/completionModel.test.ts @@ -143,5 +143,8 @@ suite('CompletionModel', function () { assertTopScore('Editor.r', 0, 'diffEditor.renderSideBySide', 'editor.overviewRulerlanes', 'editor.renderControlCharacter', 'editor.renderWhitespace'); assertTopScore('-mo', 1, '-ms-ime-mode', '-moz-columns'); + + // issue #14861 + assertTopScore('convertModelPosition', 0, 'convertModelPositionToViewPosition', 'convertViewToModelPosition'); }); }); diff --git a/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts b/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts index 8ff1e854911..d092d78c2d5 100644 --- a/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts +++ b/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as strings from 'vs/base/common/strings'; -import { CursorMoveHelper, ICursorMoveHelperModel, CursorMoveConfiguration } from 'vs/editor/common/controller/cursorMoveHelper'; +import { CursorMoveHelper, CursorMove, ICursorMoveHelperModel, CursorMoveConfiguration } from 'vs/editor/common/controller/cursorMoveHelper'; suite('CursorMove', () => { @@ -85,10 +85,9 @@ suite('CursorMove', () => { test('visibleColumnFromColumn', () => { - function testVisibleColumnFromColumn(text:string, tabSize:number, column:number, expected:number): void { - let helper = new CursorMoveHelper(new CursorMoveConfiguration(tabSize, 13)); + function testVisibleColumnFromColumn(text: string, tabSize: number, column: number, expected: number): void { let model = new OneLineModel(text); - assert.equal(helper.visibleColumnFromColumn(model, 1, column), expected); + assert.equal(CursorMoveHelper.visibleColumnFromColumn(model, 1, column, tabSize), expected); } testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 1, 0); @@ -146,10 +145,10 @@ suite('CursorMove', () => { test('columnFromVisibleColumn', () => { - function testColumnFromVisibleColumn(text:string, tabSize:number, visibleColumn:number, expected:number): void { - let helper = new CursorMoveHelper(new CursorMoveConfiguration(tabSize, 13)); + function testColumnFromVisibleColumn(text: string, tabSize: number, visibleColumn: number, expected: number): void { + let config = new CursorMoveConfiguration(tabSize, 13, null); let model = new OneLineModel(text); - assert.equal(helper.columnFromVisibleColumn(model, 1, visibleColumn), expected); + assert.equal(CursorMove.columnFromVisibleColumn(config, model, 1, visibleColumn), expected); } // testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 0, 1); diff --git a/src/vs/editor/test/common/editorTestUtils.ts b/src/vs/editor/test/common/editorTestUtils.ts index a1debaa84cc..38de6b727a2 100644 --- a/src/vs/editor/test/common/editorTestUtils.ts +++ b/src/vs/editor/test/common/editorTestUtils.ts @@ -39,8 +39,14 @@ export function viewModelHelper(model): IViewModelHelper { validateViewPosition: (viewLineNumber: number, viewColumn: number, modelPosition: Position) => { return modelPosition; }, + validateViewPosition2: (viewPosition: Position, modelPosition: Position): Position => { + return modelPosition; + }, validateViewRange: (viewStartLineNumber: number, viewStartColumn: number, viewEndLineNumber: number, viewEndColumn: number, modelRange: Range) => { return modelRange; + }, + validateViewRange2: (viewRange:Range, modelRange: Range): Range => { + return modelRange; } }; } diff --git a/src/vs/platform/environment/common/http.ts b/src/vs/platform/environment/common/http.ts new file mode 100644 index 00000000000..d1bf7af0181 --- /dev/null +++ b/src/vs/platform/environment/common/http.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TPromise } from 'vs/base/common/winjs.base'; +import { getMachineId } from 'vs/base/node/id'; +import pkg from 'vs/platform/package'; + +export function getCommonHTTPHeaders(): TPromise<{ [key: string]: string; }> { + return getMachineId().then(machineId => ({ + 'X-Market-Client-Id': `VSCode ${pkg.version}`, + 'User-Agent': `VSCode ${pkg.version}`, + 'X-Market-User-Id': machineId + })); +} \ No newline at end of file diff --git a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts index c5969b330cb..521c8af70a5 100644 --- a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts @@ -11,7 +11,6 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace'; 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'; @@ -29,7 +28,6 @@ export class ExtensionEnablementService implements IExtensionEnablementService { constructor( @IStorageService private storageService: IStorageService, @IWorkspaceContextService contextService: IWorkspaceContextService, - @IMessageService private messageService: IMessageService, @IEnvironmentService private environmentService: IEnvironmentService, @IExtensionManagementService private extensionManagementService: IExtensionManagementService ) { @@ -58,25 +56,19 @@ export class ExtensionEnablementService implements IExtensionEnablementService { return TPromise.wrap(false); } - if (this.isDisabled(identifier) === !enable) { - return TPromise.wrap(false); - } - if (enable) { if (workspace) { - this.enableExtension(identifier, StorageScope.WORKSPACE); + return this.enableExtension(identifier, StorageScope.WORKSPACE); } else { - this.enableExtension(identifier, StorageScope.GLOBAL); + return this.enableExtension(identifier, StorageScope.GLOBAL); } } else { if (workspace) { - this.disableExtension(identifier, StorageScope.WORKSPACE); + return this.disableExtension(identifier, StorageScope.WORKSPACE); } else { - this.disableExtension(identifier, StorageScope.GLOBAL); + return this.disableExtension(identifier, StorageScope.GLOBAL); } } - - return TPromise.wrap(true); } private getDisabledExtensions(): string[] { @@ -101,9 +93,13 @@ export class ExtensionEnablementService implements IExtensionEnablementService { private disableExtension(identifier: string, scope: StorageScope): TPromise { let disabledExtensions = this._getDisabledExtensions(scope); - disabledExtensions.push(identifier); - this._setDisabledExtensions(disabledExtensions, scope, identifier); - return TPromise.wrap(true); + const index = disabledExtensions.indexOf(identifier); + if (index === -1) { + disabledExtensions.push(identifier); + this._setDisabledExtensions(disabledExtensions, scope, identifier); + return TPromise.wrap(true); + } + return TPromise.wrap(false); } private enableExtension(identifier: string, scope: StorageScope): TPromise { @@ -112,8 +108,9 @@ export class ExtensionEnablementService implements IExtensionEnablementService { if (index !== -1) { disabledExtensions.splice(index, 1); this._setDisabledExtensions(disabledExtensions, scope, identifier); + return TPromise.wrap(true); } - return TPromise.wrap(true); + return TPromise.wrap(false); } private _getDisabledExtensions(scope: StorageScope): string[] { diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index b31ffe53d0d..a3bc9eab7de 100644 --- a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts @@ -23,7 +23,7 @@ import pkg from 'vs/platform/package'; import product from 'vs/platform/product'; import { isVersionValid } from 'vs/platform/extensions/node/extensionValidator'; import * as url from 'url'; -import { getMachineId } from 'vs/base/node/id'; +import { getCommonHTTPHeaders } from 'vs/platform/environment/common/http'; interface IRawGalleryExtensionFile { assetType: string; @@ -262,12 +262,8 @@ export class ExtensionGalleryService implements IExtensionGalleryService { private extensionsGalleryUrl: string; @memoize - private get commonHeaders(): TPromise<{ [key: string]: string; }> { - return getMachineId().then(machineId => ({ - 'X-Market-Client-Id': `VSCode ${pkg.version}`, - 'User-Agent': `VSCode ${pkg.version}`, - 'X-Market-User-Id': machineId - })); + private get commonHTTPHeaders(): TPromise<{ [key: string]: string; }> { + return getCommonHTTPHeaders(); } constructor( @@ -288,7 +284,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } getRequestHeaders(): TPromise<{ [key: string]: string; }> { - return this.commonHeaders; + return this.commonHTTPHeaders; } query(options: IQueryOptions = {}): TPromise> { @@ -338,7 +334,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } private queryGallery(query: Query): TPromise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> { - return this.commonHeaders + return this.commonHTTPHeaders .then(headers => { const data = JSON.stringify(query.raw); @@ -478,7 +474,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { parsedUrl.search = undefined; parsedUrl.query['redirect'] = 'true'; - return this.commonHeaders.then(headers => { + return this.commonHTTPHeaders.then(headers => { headers = assign({}, headers, options.headers || {}); options = assign({}, options, { headers }); diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 9e81bbfecca..200f7303251 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -18,6 +18,27 @@ export interface IWindowsService { openFilePicker(windowId: number, forceNewWindow?: boolean, path?: string): TPromise; openFolderPicker(windowId: number, forceNewWindow?: boolean): TPromise; reloadWindow(windowId: number): TPromise; + openDevTools(windowId: number): TPromise; + toggleDevTools(windowId: number): TPromise; + // TODO@joao: rename, shouldn't this be closeWindow? + closeFolder(windowId: number): TPromise; + toggleFullScreen(windowId: number): TPromise; + setRepresentedFilename(windowId: number, fileName: string): TPromise; + getRecentlyOpen(windowId: number): TPromise<{ files: string[]; folders: string[]; }>; + focusWindow(windowId: number): TPromise; + setDocumentEdited(windowId: number, flag: boolean): TPromise; + toggleMenuBar(windowId: number): TPromise; + + // Global methods + // TODO@joao: rename, shouldn't this be openWindow? + windowOpen(paths: string[], forceNewWindow?: boolean): TPromise; + openNewWindow(): TPromise; + showWindow(windowId: number): TPromise; + getWindows(): TPromise<{ id: number; path: string; title: string; }[]>; + log(severity: string, ...messages: string[]): TPromise; + // TODO@joao: what? + closeExtensionHostWindow(extensionDevelopmentPath: string): TPromise; + showItemInFolder(path: string): TPromise; } export const IWindowService = createDecorator('windowService'); @@ -26,8 +47,18 @@ export interface IWindowService { _serviceBrand: any; + getCurrentWindowId(): number; openFileFolderPicker(forceNewWindow?: boolean): TPromise; openFilePicker(forceNewWindow?: boolean, path?: string): TPromise; openFolderPicker(forceNewWindow?: boolean): TPromise; reloadWindow(): TPromise; + openDevTools(): TPromise; + toggleDevTools(): TPromise; + closeFolder(): TPromise; + toggleFullScreen(): TPromise; + setRepresentedFilename(fileName: string): TPromise; + getRecentlyOpen(): TPromise<{ files: string[]; folders: string[]; }>; + focusWindow(): TPromise; + setDocumentEdited(flag: boolean): TPromise; + toggleMenuBar(): 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 index a83ac7f6783..3f73ca7f9f2 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -14,6 +14,21 @@ export interface IWindowsChannel extends IChannel { call(command: 'openFilePicker', args: [number, boolean, string]): TPromise; call(command: 'openFolderPicker', args: [number, boolean]): TPromise; call(command: 'reloadWindow', arg: number): TPromise; + call(command: 'toggleDevTools', arg: number): TPromise; + call(command: 'closeFolder', arg: number): TPromise; + call(command: 'toggleFullScreen', arg: number): TPromise; + call(command: 'setRepresentedFilename', arg: [number, string]): TPromise; + call(command: 'getRecentlyOpen', arg: number): TPromise<{ files: string[]; folders: string[]; }>; + call(command: 'focusWindow', arg: number): TPromise; + call(command: 'setDocumentEdited', args: [number, boolean]): TPromise; + call(command: 'toggleMenuBar', args: number): TPromise; + call(command: 'windowOpen', arg: [string[], boolean]): TPromise; + call(command: 'openNewWindow'): TPromise; + call(command: 'showWindow', arg: number): TPromise; + call(command: 'getWindows'): TPromise<{ id: number; path: string; title: string; }[]>; + call(command: 'log', args: [string, string[]]): TPromise; + call(command: 'closeExtensionHostWindow', args: string): TPromise; + call(command: 'showItemInFolder', args: string): TPromise; call(command: string, arg?: any): TPromise; } @@ -27,6 +42,22 @@ export class WindowsChannel implements IWindowsChannel { 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); + case 'openDevTools': return this.service.openDevTools(arg); + case 'toggleDevTools': return this.service.toggleDevTools(arg); + case 'closeFolder': return this.service.closeFolder(arg); + case 'toggleFullScreen': return this.service.toggleFullScreen(arg); + case 'setRepresentedFilename': return this.service.setRepresentedFilename(arg[0], arg[1]); + case 'getRecentlyOpen': return this.service.getRecentlyOpen(arg); + case 'focusWindow': return this.service.focusWindow(arg); + case 'setDocumentEdited': return this.service.setDocumentEdited(arg[0], arg[1]); + case 'toggleMenuBar': return this.service.toggleMenuBar(arg); + case 'windowOpen': return this.service.windowOpen(arg[0], arg[1]); + case 'openNewWindow': return this.service.openNewWindow(); + case 'showWindow': return this.service.showWindow(arg); + case 'getWindows': return this.service.getWindows(); + case 'log': return this.service.log(arg[0], arg[1]); + case 'closeExtensionHostWindow': return this.service.closeExtensionHostWindow(arg); + case 'showItemInFolder': return this.service.showItemInFolder(arg); } } } @@ -52,4 +83,68 @@ export class WindowsChannelClient implements IWindowsService { reloadWindow(windowId: number): TPromise { return this.channel.call('reloadWindow', windowId); } + + openDevTools(windowId: number): TPromise { + return this.channel.call('openDevTools', windowId); + } + + toggleDevTools(windowId: number): TPromise { + return this.channel.call('toggleDevTools', windowId); + } + + closeFolder(windowId: number): TPromise { + return this.channel.call('closeFolder', windowId); + } + + toggleFullScreen(windowId: number): TPromise { + return this.channel.call('toggleFullScreen', windowId); + } + + setRepresentedFilename(windowId: number, fileName: string): TPromise { + return this.channel.call('setRepresentedFilename', [windowId, fileName]); + } + + getRecentlyOpen(windowId: number): TPromise<{ files: string[]; folders: string[]; }> { + return this.channel.call('getRecentlyOpen', windowId); + } + + focusWindow(windowId: number): TPromise { + return this.channel.call('focusWindow', windowId); + } + + setDocumentEdited(windowId: number, flag: boolean): TPromise { + return this.channel.call('setDocumentEdited', [windowId, flag]); + } + + toggleMenuBar(windowId: number): TPromise { + return this.channel.call('toggleMenuBar', windowId); + } + + windowOpen(paths: string[], forceNewWindow?: boolean): TPromise { + return this.channel.call('windowOpen', [paths, forceNewWindow]); + } + + openNewWindow(): TPromise { + return this.channel.call('openNewWindow'); + } + + showWindow(windowId: number): TPromise { + return this.channel.call('showWindow', windowId); + } + + getWindows(): TPromise<{ id: number; path: string; title: string; }[]> { + return this.channel.call('getWindows'); + } + + log(severity: string, ...messages: string[]): TPromise { + return this.channel.call('log', [severity, messages]); + } + + closeExtensionHostWindow(extensionDevelopmentPath: string): TPromise { + return this.channel.call('closeExtensionHostWindow', extensionDevelopmentPath); + } + + showItemInFolder(path: string): TPromise { + return this.channel.call('showItemInFolder', path); + } } \ 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 index aee56786b0e..88b852206f3 100644 --- a/src/vs/platform/windows/electron-browser/windowService.ts +++ b/src/vs/platform/windows/electron-browser/windowService.ts @@ -17,6 +17,10 @@ export class WindowService implements IWindowService { @IWindowsService private windowsService: IWindowsService ) { } + getCurrentWindowId(): number { + return this.windowId; + } + openFileFolderPicker(forceNewWindow?: boolean): TPromise { return this.windowsService.openFileFolderPicker(this.windowId, forceNewWindow); } @@ -32,4 +36,40 @@ export class WindowService implements IWindowService { reloadWindow(): TPromise { return this.windowsService.reloadWindow(this.windowId); } + + openDevTools(): TPromise { + return this.windowsService.openDevTools(this.windowId); + } + + toggleDevTools(): TPromise { + return this.windowsService.toggleDevTools(this.windowId); + } + + closeFolder(): TPromise { + return this.windowsService.closeFolder(this.windowId); + } + + toggleFullScreen(): TPromise { + return this.windowsService.toggleFullScreen(this.windowId); + } + + setRepresentedFilename(fileName: string): TPromise { + return this.windowsService.setRepresentedFilename(this.windowId, fileName); + } + + getRecentlyOpen(): TPromise<{ files: string[]; folders: string[]; }> { + return this.windowsService.getRecentlyOpen(this.windowId); + } + + focusWindow(): TPromise { + return this.windowsService.focusWindow(this.windowId); + } + + setDocumentEdited(flag: boolean): TPromise { + return this.windowsService.setDocumentEdited(this.windowId, flag); + } + + toggleMenuBar(): TPromise { + return this.windowsService.toggleMenuBar(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 index 72407812187..4094cef4839 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -7,6 +7,8 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { shell } from 'electron'; // TODO@Joao: remove this dependency, move all implementation to this class import { IWindowsMainService } from 'vs/code/electron-main/windows'; @@ -16,7 +18,8 @@ export class WindowsService implements IWindowsService { _serviceBrand: any; constructor( - @IWindowsMainService private windowsMainService: IWindowsMainService + @IWindowsMainService private windowsMainService: IWindowsMainService, + @IEnvironmentService private environmentService: IEnvironmentService ) { } openFileFolderPicker(windowId: number, forceNewWindow?: boolean): TPromise { @@ -43,4 +46,140 @@ export class WindowsService implements IWindowsService { return TPromise.as(null); } + + openDevTools(windowId: number): TPromise { + const vscodeWindow = this.windowsMainService.getWindowById(windowId); + + if (vscodeWindow) { + vscodeWindow.win.webContents.openDevTools(); + } + + return TPromise.as(null); + } + + toggleDevTools(windowId: number): TPromise { + const vscodeWindow = this.windowsMainService.getWindowById(windowId); + + if (vscodeWindow) { + vscodeWindow.win.webContents.toggleDevTools(); + } + + return TPromise.as(null); + } + + closeFolder(windowId: number): TPromise { + const vscodeWindow = this.windowsMainService.getWindowById(windowId); + + if (vscodeWindow) { + this.windowsMainService.open({ cli: this.environmentService.args, forceEmpty: true, windowToUse: vscodeWindow }); + } + + return TPromise.as(null); + } + + toggleFullScreen(windowId: number): TPromise { + const vscodeWindow = this.windowsMainService.getWindowById(windowId); + + if (vscodeWindow) { + vscodeWindow.toggleFullScreen(); + } + + return TPromise.as(null); + } + + setRepresentedFilename(windowId: number, fileName: string): TPromise { + const vscodeWindow = this.windowsMainService.getWindowById(windowId); + + if (vscodeWindow) { + vscodeWindow.win.setRepresentedFilename(fileName); + } + + return TPromise.as(null); + } + + getRecentlyOpen(windowId: number): TPromise<{ files: string[]; folders: string[]; }> { + const vscodeWindow = this.windowsMainService.getWindowById(windowId); + + if (vscodeWindow) { + const { files, folders } = this.windowsMainService.getRecentPathsList(vscodeWindow.config.workspacePath, vscodeWindow.config.filesToOpen); + return TPromise.as({ files, folders }); + } + + return TPromise.as({ files: [], folders: [] }); + } + + focusWindow(windowId: number): TPromise { + const vscodeWindow = this.windowsMainService.getWindowById(windowId); + + if (vscodeWindow) { + vscodeWindow.win.focus(); + } + + return TPromise.as(null); + } + + setDocumentEdited(windowId: number, flag: boolean): TPromise { + const vscodeWindow = this.windowsMainService.getWindowById(windowId); + + if (vscodeWindow && vscodeWindow.win.isDocumentEdited() !== flag) { + vscodeWindow.win.setDocumentEdited(flag); + } + + return TPromise.as(null); + } + + toggleMenuBar(windowId: number): TPromise { + this.windowsMainService.toggleMenuBar(windowId); + return TPromise.as(null); + } + + windowOpen(paths: string[], forceNewWindow?: boolean): TPromise { + if (!paths || !paths.length) { + return TPromise.as(null); + } + + this.windowsMainService.open({ cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: forceNewWindow }); + return TPromise.as(null); + } + + openNewWindow(): TPromise { + this.windowsMainService.openNewWindow(); + return TPromise.as(null); + } + + showWindow(windowId: number): TPromise { + const vscodeWindow = this.windowsMainService.getWindowById(windowId); + + if (vscodeWindow) { + vscodeWindow.win.show(); + } + + return TPromise.as(null); + } + + getWindows(): TPromise<{ id: number; path: string; title: string; }[]> { + const windows = this.windowsMainService.getWindows(); + const result = windows.map(w => ({ path: w.openedWorkspacePath, title: w.win.getTitle(), id: w.id })); + return TPromise.as(result); + } + + log(severity: string, ...messages: string[]): TPromise { + console[severity].apply(console, ...messages); + return TPromise.as(null); + } + + closeExtensionHostWindow(extensionDevelopmentPath: string): TPromise { + const windowOnExtension = this.windowsMainService.findWindow(null, null, extensionDevelopmentPath); + + if (windowOnExtension) { + windowOnExtension.win.close(); + } + + return TPromise.as(null); + } + + showItemInFolder(path: string): TPromise { + shell.showItemInFolder(path); + return TPromise.as(null); + } } \ No newline at end of file diff --git a/src/vs/test/utils/instantiationTestUtils.ts b/src/vs/test/utils/instantiationTestUtils.ts index 509eec6e6ae..0e34d008c41 100644 --- a/src/vs/test/utils/instantiationTestUtils.ts +++ b/src/vs/test/utils/instantiationTestUtils.ts @@ -54,32 +54,44 @@ export class TestInstantiationService extends InstantiationService { this._servciesMap.set(IKeybindingService, WorkbenchKeybindingService); } + public get(service: ServiceIdentifier): T { + return this._serviceCollection.get(service); + } + + public set(service: ServiceIdentifier, instance: T): T { + return this._serviceCollection.set(service, instance); + } + public mock(service: ServiceIdentifier): T | sinon.SinonMock { return this._create(service, { mock: true }); } public stub(service?: ServiceIdentifier, ctor?: any): T public stub(service?: ServiceIdentifier, obj?: any): T - public stub(service?: ServiceIdentifier, ctor?: any, fnProperty?: string, value?: any): sinon.SinonStub - public stub(service?: ServiceIdentifier, obj?: any, fnProperty?: string, value?: any): sinon.SinonStub - public stub(service?: ServiceIdentifier, fnProperty?: string, value?: any): sinon.SinonStub + public stub(service?: ServiceIdentifier, ctor?: any, property?: string, value?: any): sinon.SinonStub + public stub(service?: ServiceIdentifier, obj?: any, property?: string, value?: any): sinon.SinonStub + public stub(service?: ServiceIdentifier, property?: string, value?: any): sinon.SinonStub public stub(serviceIdentifier?: ServiceIdentifier, arg2?: any, arg3?: string, arg4?: any): sinon.SinonStub { let service = typeof arg2 !== 'string' ? arg2 : void 0; let serviceMock: IServiceMock = { id: serviceIdentifier, service: service }; - let fnProperty = typeof arg2 === 'string' ? arg2 : arg3; + let property = typeof arg2 === 'string' ? arg2 : arg3; let value = typeof arg2 === 'string' ? arg3 : arg4; let stubObject = this._create(serviceMock, { stub: true }); - if (fnProperty) { - if (stubObject[fnProperty].hasOwnProperty('restore')) { - stubObject[fnProperty].restore(); - } - if (typeof value === 'function') { - stubObject[fnProperty] = value; + if (property) { + if (stubObject[property]) { + if (stubObject[property].hasOwnProperty('restore')) { + stubObject[property].restore(); + } + if (typeof value === 'function') { + stubObject[property] = value; + } else { + let stub = value ? sinon.stub().returns(value) : sinon.stub(); + stubObject[property] = stub; + return stub; + } } else { - let stub = value ? sinon.stub().returns(value) : sinon.stub(); - stubObject[fnProperty] = stub; - return stub; + stubObject[property] = value; } } return stubObject; diff --git a/src/vs/workbench/api/node/extHostApiCommands.ts b/src/vs/workbench/api/node/extHostApiCommands.ts index c270f87afd0..5fca8b0a243 100644 --- a/src/vs/workbench/api/node/extHostApiCommands.ts +++ b/src/vs/workbench/api/node/extHostApiCommands.ts @@ -188,7 +188,7 @@ export class ExtHostApiCommands { return this._commands.executeCommand('_files.openFolderPicker', forceNewWindow); } - return this._commands.executeCommand('_workbench.ipc', 'vscode:windowOpen', [[uri.fsPath], forceNewWindow]); + return this._commands.executeCommand('_files.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/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index d6b42f0e142..7e3451be8c7 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -467,49 +467,6 @@ export function toEditorQuickOpenEntry(element: any): IEditorQuickOpenEntry { return null; } -export class SaveEditorAction extends Action { - - public static ID = 'workbench.action.files.save'; - public static LABEL = nls.localize('saveEditor', "Save Editor"); - - constructor( - id: string, - label: string, - @IEditorGroupService private editorGroupService: IEditorGroupService, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService - ) { - super(id, label, 'save-editor-action'); - } - - public run(context?: IEditorContext): TPromise { - const position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null; - - // Save Active Editor - if (typeof position !== 'number') { - const activeEditor = this.editorService.getActiveEditorInput(); - if (activeEditor instanceof EditorInput) { - return activeEditor.save(); - } - } - - let input = context ? context.editor : null; - if (!input) { - - // Get Editor at Position - const visibleEditors = this.editorService.getVisibleEditors(); - if (visibleEditors[position]) { - input = visibleEditors[position].input; - } - } - - if (input instanceof EditorInput) { - return input.save(); - } - - return TPromise.as(false); - } -} - export class CloseEditorAction extends Action { public static ID = 'workbench.action.closeActiveEditor'; diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitle.css b/src/vs/workbench/browser/parts/editor/media/tabstitle.css index a066a6b1029..b18efa9585a 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitle.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitle.css @@ -72,14 +72,6 @@ padding-left: 10px; } -.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button { - padding-right: 28px; /* make room for dirty indication when we are running without close button */ -} - -.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button.dirty { - padding-right: 0; /* dirty tabs always show the dirty indicator, so we can clear the padding now */ -} - .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active { z-index: 2; /* on top of the horizontal border of the title */ } @@ -183,10 +175,6 @@ display: none; /* hide the close action bar when we are configured to hide it */ } -.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button.dirty > .tab-close { - display: block; /* show the close action bar when the tab gets dirty */ -} - .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab.active > .tab-close .action-label, /* always show it for active tab */ .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab > .tab-close .action-label:focus, /* always show it on focus */ .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab:hover > .tab-close .action-label, /* always show it on hover */ @@ -215,15 +203,12 @@ margin-right: 0.5em; } -.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action, -.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .save-editor-action { +.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action { background: url('close-dirty.svg') center center no-repeat; } .vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action, -.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action, -.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .save-editor-action, -.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .save-editor-action { +.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action { background: url('close-dirty-inverse.svg') center center no-repeat; } @@ -236,6 +221,27 @@ background: url('close-inverse.svg') center center no-repeat; } +/* No Tab Close Button */ + +.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button { + padding-right: 28px; /* make room for dirty indication when we are running without close button */ +} + +.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button.dirty { + background-repeat: no-repeat; + background-position-y: center; + background-position-x: calc(100% - 6px); /* to the right of the tab label */ +} + +.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button.dirty { + background-image: url('close-dirty.svg'); +} + +.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button.dirty, +.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button.dirty { + background-image: url('close-dirty-inverse.svg'); +} + /* Editor Actions */ .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions { diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditorControl.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditorControl.ts index fc51439a6f2..c92369e61a8 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditorControl.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditorControl.ts @@ -36,6 +36,7 @@ import { NoTabsTitleControl } from 'vs/workbench/browser/parts/editor/noTabsTitl import { IEditorStacksModel, IStacksModelChangeEvent, IWorkbenchEditorConfiguration, IEditorGroup, EditorOptions, TextEditorOptions, IEditorIdentifier } from 'vs/workbench/common/editor'; import { ITitleAreaControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { extractResources } from 'vs/base/browser/dnd'; +import { IWindowService } from 'vs/platform/windows/common/windows'; export enum Rochade { NONE, @@ -147,7 +148,8 @@ export class SideBySideEditorControl implements ISideBySideEditorControl, IVerti @IConfigurationService private configurationService: IConfigurationService, @IContextKeyService private contextKeyService: IContextKeyService, @IExtensionService private extensionService: IExtensionService, - @IInstantiationService private instantiationService: IInstantiationService + @IInstantiationService private instantiationService: IInstantiationService, + @IWindowService private windowService: IWindowService ) { this.stacks = editorGroupService.getStacksModel(); this.toDispose = []; @@ -1003,10 +1005,8 @@ export class SideBySideEditorControl implements ISideBySideEditorControl, IVerti // Check for URI transfer else { if (droppedResources.length) { - window.focus(); // make sure this window has focus so that the open call reaches the right window! - - // Open all - editorService.openEditors(droppedResources.map(resource => { return { input: { resource, options: { pinned: true } }, position: splitEditor ? freeGroup : position }; })) + $this.windowService.focusWindow() + .then(() => editorService.openEditors(droppedResources.map(resource => { return { input: { resource, options: { pinned: true } }, position: splitEditor ? freeGroup : position }; }))) .then(() => { if (splitEditor && splitTo !== freeGroup) { groupService.moveGroup(freeGroup, splitTo); diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 07d66a8d951..b59abdf4bda 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -28,6 +28,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService } from 'vs/platform/actions/common/actions'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { IQuickOpenService } from 'vs/workbench/services/quickopen/common/quickOpenService'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -65,7 +66,8 @@ export class TabsTitleControl extends TitleControl { @ITelemetryService telemetryService: ITelemetryService, @IMessageService messageService: IMessageService, @IMenuService menuService: IMenuService, - @IQuickOpenService quickOpenService: IQuickOpenService + @IQuickOpenService quickOpenService: IQuickOpenService, + @IWindowService private windowService: IWindowService ) { super(contextMenuService, instantiationService, configurationService, editorService, editorGroupService, contextKeyService, keybindingService, telemetryService, messageService, menuService, quickOpenService); @@ -353,8 +355,7 @@ export class TabsTitleControl extends TitleControl { tabContainer.appendChild(tabCloseContainer); const bar = new ActionBar(tabCloseContainer, { context: { editor, group }, ariaLabel: nls.localize('araLabelTabActions', "Tab actions") }); - const action = this.showTabCloseButton ? this.closeEditorAction : this.saveEditorAction; - bar.push(action, { icon: true, label: false, keybinding: this.getKeybindingLabel(action) }); + bar.push(this.closeEditorAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(this.closeEditorAction) }); this.tabDisposeables.push(bar); @@ -583,10 +584,10 @@ export class TabsTitleControl extends TitleControl { input: { resource, options: { pinned: true, index: targetIndex } }, position: targetPosition }; - })).done(() => { + })).then(() => { this.editorGroupService.focusGroup(targetPosition); - window.focus(); - }, errors.onUnexpectedError); + return this.windowService.focusWindow(); + }).done(null, errors.onUnexpectedError); } } diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index e9a069932dd..b757f02912a 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -33,7 +33,7 @@ import { IQuickOpenService } from 'vs/workbench/services/quickopen/common/quickO import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Keybinding } from 'vs/base/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { CloseEditorsInGroupAction, SplitEditorAction, SaveEditorAction, CloseEditorAction, KeepEditorAction, CloseOtherEditorsInGroupAction, CloseRightEditorsInGroupAction, ShowEditorsInGroupAction } from 'vs/workbench/browser/parts/editor/editorActions'; +import { CloseEditorsInGroupAction, SplitEditorAction, CloseEditorAction, KeepEditorAction, CloseOtherEditorsInGroupAction, CloseRightEditorsInGroupAction, ShowEditorsInGroupAction } from 'vs/workbench/browser/parts/editor/editorActions'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { createActionItem, fillInActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; @@ -68,7 +68,6 @@ export abstract class TitleControl implements ITitleAreaControl { protected dragged: boolean; protected closeEditorAction: CloseEditorAction; - protected saveEditorAction: SaveEditorAction; protected pinEditorAction: KeepEditorAction; protected closeOtherEditorsAction: CloseOtherEditorsInGroupAction; protected closeRightEditorsAction: CloseRightEditorsInGroupAction; @@ -232,7 +231,6 @@ export abstract class TitleControl implements ITitleAreaControl { private initActions(): void { this.closeEditorAction = this.instantiationService.createInstance(CloseEditorAction, CloseEditorAction.ID, nls.localize('close', "Close")); - this.saveEditorAction = this.instantiationService.createInstance(SaveEditorAction, SaveEditorAction.ID, nls.localize('save', "Save")); this.closeOtherEditorsAction = this.instantiationService.createInstance(CloseOtherEditorsInGroupAction, CloseOtherEditorsInGroupAction.ID, nls.localize('closeOthers', "Close Others")); this.closeRightEditorsAction = this.instantiationService.createInstance(CloseRightEditorsInGroupAction, CloseRightEditorsInGroupAction.ID, nls.localize('closeRight', "Close to the Right")); this.closeEditorsInGroupAction = this.instantiationService.createInstance(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, nls.localize('closeAll', "Close All")); @@ -495,7 +493,6 @@ export abstract class TitleControl implements ITitleAreaControl { this.splitEditorAction, this.showEditorsInGroupAction, this.closeEditorAction, - this.saveEditorAction, this.closeRightEditorsAction, this.closeOtherEditorsAction, this.closeEditorsInGroupAction, diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index c37f2a181ea..b5196dbe37a 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -10,7 +10,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import timer = require('vs/base/common/timer'); import { Action } from 'vs/base/common/actions'; import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IWindowService, IWindowsService } 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'; @@ -28,7 +28,7 @@ import { IWorkspaceConfigurationService } from 'vs/workbench/services/configurat import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import paths = require('vs/base/common/paths'); import { isMacintosh } from 'vs/base/common/platform'; -import { IQuickOpenService, IPickOpenEntry, IFilePickOpenEntry, ISeparator } from 'vs/workbench/services/quickopen/common/quickOpenService'; +import { IQuickOpenService, IFilePickOpenEntry, ISeparator } from 'vs/workbench/services/quickopen/common/quickOpenService'; import { KeyMod } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import * as browser from 'vs/base/browser/browser'; @@ -80,97 +80,89 @@ export class CloseWindowAction extends Action { export class SwitchWindow extends Action { - public static ID = 'workbench.action.switchWindow'; - public static LABEL = nls.localize('switchWindow', "Switch Window"); + static ID = 'workbench.action.switchWindow'; + static LABEL = nls.localize('switchWindow', "Switch Window"); constructor( id: string, label: string, - @IWindowIPCService private windowService: IWindowIPCService, + @IWindowsService private windowsService: IWindowsService, + @IWindowService private windowService: IWindowService, @IQuickOpenService private quickOpenService: IQuickOpenService ) { super(id, label); } - public run(): TPromise { - const id = this.windowService.getWindowId(); - ipc.send('vscode:switchWindow', id); - ipc.once('vscode:switchWindow', (event, workspaces) => { - const picks: IPickOpenEntry[] = workspaces.map(w => { - return { - label: w.title, - description: (id === w.id) ? nls.localize('current', "Current Window") : void 0, - run: () => { - ipc.send('vscode:showWindow', w.id); - } - }; - }); - this.quickOpenService.pick(picks, { placeHolder: nls.localize('switchWindowPlaceHolder', "Select a window") }); - }); + run(): TPromise { + const currentWindowId = this.windowService.getCurrentWindowId(); - return TPromise.as(true); + return this.windowsService.getWindows().then(workspaces => { + const placeHolder = nls.localize('switchWindowPlaceHolder', "Select a window"); + const picks = workspaces.map(w => ({ + label: w.title, + description: (currentWindowId === w.id) ? nls.localize('current', "Current Window") : void 0, + run: () => this.windowsService.showWindow(w.id) + })); + + this.quickOpenService.pick(picks, { placeHolder }); + }); } } export class CloseFolderAction extends Action { - public static ID = 'workbench.action.closeFolder'; - public static LABEL = nls.localize('closeFolder', "Close Folder"); + static ID = 'workbench.action.closeFolder'; + static LABEL = nls.localize('closeFolder', "Close Folder"); constructor( id: string, label: string, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IMessageService private messageService: IMessageService, - @IWindowIPCService private windowService: IWindowIPCService + @IWindowService private windowService: IWindowService ) { super(id, label); } - public run(): TPromise { - if (this.contextService.getWorkspace()) { - ipc.send('vscode:closeFolder', this.windowService.getWindowId()); // handled from browser process - } else { + run(): TPromise { + if (!this.contextService.getWorkspace()) { this.messageService.show(Severity.Info, nls.localize('noFolderOpened', "There is currently no folder opened in this instance to close.")); + return TPromise.as(null); } - return TPromise.as(true); + return this.windowService.closeFolder(); } } export class NewWindowAction extends Action { - public static ID = 'workbench.action.newWindow'; - public static LABEL = nls.localize('newWindow', "New Window"); + static ID = 'workbench.action.newWindow'; + static LABEL = nls.localize('newWindow', "New Window"); constructor( id: string, label: string, - @IWindowIPCService private windowService: IWindowIPCService + @IWindowsService private windowsService: IWindowsService ) { super(id, label); } - public run(): TPromise { - this.windowService.getWindow().openNew(); - - return TPromise.as(true); + run(): TPromise { + return this.windowsService.openNewWindow(); } } export class ToggleFullScreenAction extends Action { - public static ID = 'workbench.action.toggleFullScreen'; - public static LABEL = nls.localize('toggleFullScreen', "Toggle Full Screen"); + static ID = 'workbench.action.toggleFullScreen'; + static LABEL = nls.localize('toggleFullScreen', "Toggle Full Screen"); - constructor(id: string, label: string, @IWindowIPCService private windowService: IWindowIPCService) { + constructor(id: string, label: string, @IWindowService private windowService: IWindowService) { super(id, label); } - public run(): TPromise { - ipc.send('vscode:toggleFullScreen', this.windowService.getWindowId()); - - return TPromise.as(true); + run(): TPromise { + return this.windowService.toggleFullScreen(); } } @@ -192,17 +184,15 @@ export class ToggleMenuBarAction extends Action { export class ToggleDevToolsAction extends Action { - public static ID = 'workbench.action.toggleDevTools'; - public static LABEL = nls.localize('toggleDevTools', "Toggle Developer Tools"); + static ID = 'workbench.action.toggleDevTools'; + static LABEL = nls.localize('toggleDevTools', "Toggle Developer Tools"); - constructor(id: string, label: string, @IWindowIPCService private windowService: IWindowIPCService) { + constructor(id: string, label: string, @IWindowService private windowsService: IWindowService) { super(id, label); } - public run(): TPromise { - ipc.send('vscode:toggleDevTools', this.windowService.getWindowId()); - - return TPromise.as(true); + run(): TPromise { + return this.windowsService.toggleDevTools(); } } @@ -330,7 +320,7 @@ export class ShowStartupPerformance extends Action { constructor( id: string, label: string, - @IWindowIPCService private windowService: IWindowIPCService, + @IWindowService private windowService: IWindowService, @IEnvironmentService environmentService: IEnvironmentService ) { super(id, label); @@ -405,9 +395,8 @@ export class ShowStartupPerformance extends Action { sum['Took (ms)'] = lastEvent.stopTime.getTime() - start; table.push(sum); - // Show dev tools - this.windowService.getWindow().openDevTools(); + this.windowService.openDevTools(); // Print to console setTimeout(() => { @@ -447,23 +436,17 @@ export class OpenRecentAction extends Action { constructor( id: string, label: string, - @IWindowIPCService private windowService: IWindowIPCService, + @IWindowsService private windowsService: IWindowsService, + @IWindowService private windowService: IWindowService, @IQuickOpenService private quickOpenService: IQuickOpenService, @IWorkspaceContextService private contextService: IWorkspaceContextService ) { super(id, label); } - public run(): TPromise { - ipc.send('vscode:openRecent', this.windowService.getWindowId()); - - return new TPromise((c, e, p) => { - ipc.once('vscode:openRecent', (event, files: string[], folders: string[]) => { - this.openRecent(files, folders); - - c(true); - }); - }); + public run(): TPromise { + return this.windowService.getRecentlyOpen() + .then(({ files, folders }) => this.openRecent(files, folders)); } private openRecent(recentFiles: string[], recentFolders: string[]): void { @@ -478,11 +461,10 @@ export class OpenRecentAction extends Action { }; } - function runPick(path: string, context): void { + const runPick = (path: string, context) => { const newWindow = context.keymods.indexOf(KeyMod.CtrlCmd) >= 0; - - ipc.send('vscode:windowOpen', [path], newWindow); - } + this.windowsService.windowOpen([path], newWindow); + }; const folderPicks: IFilePickOpenEntry[] = recentFolders.map((p, index) => toPick(p, index === 0 ? { label: nls.localize('folders', "folders") } : void 0, true)); const filePicks: IFilePickOpenEntry[] = recentFiles.map((p, index) => toPick(p, index === 0 ? { label: nls.localize('files', "files"), border: true } : void 0, false)); @@ -589,14 +571,6 @@ Steps to Reproduce: // --- commands -CommandsRegistry.registerCommand('_workbench.ipc', function (accessor: ServicesAccessor, ipcMessage: string, ipcArgs: any[]) { - if (ipcMessage && Array.isArray(ipcArgs)) { - ipc.send(ipcMessage, ...ipcArgs); - } else { - ipc.send(ipcMessage); - } -}); - CommandsRegistry.registerCommand('_workbench.diff', function (accessor: ServicesAccessor, args: [URI, URI, string]) { const editorService = accessor.get(IWorkbenchEditorService); let [left, right, label] = args; diff --git a/src/vs/workbench/electron-browser/bootstrap/index.js b/src/vs/workbench/electron-browser/bootstrap/index.js index 7c66ad35cf6..d61e362bb9d 100644 --- a/src/vs/workbench/electron-browser/bootstrap/index.js +++ b/src/vs/workbench/electron-browser/bootstrap/index.js @@ -13,11 +13,10 @@ const path = require('path'); const electron = require('electron'); const remote = electron.remote; const ipc = electron.ipcRenderer; -const windowId = remote.getCurrentWindow().id; function onError(error, enableDeveloperTools) { if (enableDeveloperTools) { - ipc.send('vscode:openDevTools', windowId); + remote.getCurrentWebContents().openDevTools(); } console.error('[uncaught exception]: ' + error); @@ -80,8 +79,7 @@ function registerListeners(enableDeveloperTools) { window.addEventListener('keydown', function (e) { const key = extractKey(e); if (key === TOGGLE_DEV_TOOLS_KB) { - ipc.send('vscode:toggleDevTools', windowId); - // remote.getCurrentWebContents().toggleDevTools(); + remote.getCurrentWebContents().toggleDevTools(); } else if (key === RELOAD_KB) { remote.getCurrentWindow().reload(); } diff --git a/src/vs/workbench/electron-browser/extensionHost.ts b/src/vs/workbench/electron-browser/extensionHost.ts index d48a8687bc8..86d58254061 100644 --- a/src/vs/workbench/electron-browser/extensionHost.ts +++ b/src/vs/workbench/electron-browser/extensionHost.ts @@ -15,6 +15,7 @@ import { isWindows } from 'vs/base/common/platform'; import { findFreePort } from 'vs/base/node/ports'; import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { ILifecycleService, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; +import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; @@ -68,6 +69,7 @@ export class ExtensionHostProcessWorker { constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService, @IMessageService private messageService: IMessageService, + @IWindowsService private windowsService: IWindowsService, @IWindowIPCService private windowService: IWindowIPCService, @ILifecycleService lifecycleService: ILifecycleService, @IInstantiationService private instantiationService: IInstantiationService, @@ -289,7 +291,7 @@ export class ExtensionHostProcessWorker { // Log on main side if running tests from cli if (this.isExtensionDevelopmentTestFromCli) { - ipc.send('vscode:log', logEntry); + this.windowsService.log(logEntry.severity, ...args); } // Broadcast to other windows if we are in development mode diff --git a/src/vs/workbench/electron-browser/integration.ts b/src/vs/workbench/electron-browser/integration.ts index ed76f6de9de..763fa81bdb4 100644 --- a/src/vs/workbench/electron-browser/integration.ts +++ b/src/vs/workbench/electron-browser/integration.ts @@ -34,7 +34,6 @@ import { IPath, IOpenFileRequest, IWindowConfiguration } from 'vs/workbench/elec import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import URI from 'vs/base/common/uri'; import { ipcRenderer as ipc, webFrame, remote } from 'electron'; @@ -69,7 +68,6 @@ export class ElectronIntegration { @IMessageService private messageService: IMessageService, @IContextMenuService private contextMenuService: IContextMenuService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService ) { } @@ -194,13 +192,6 @@ export class ElectronIntegration { } } }); - - // Extra request headers - this.extensionGalleryService.getRequestHeaders().done(headers => { - const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; - - ipc.send('vscode:setHeaders', this.windowService.getWindowId(), urls, headers); - }); } private resolveKeybindings(actionIds: string[]): TPromise<{ id: string; binding: number; }[]> { diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 13d0d826f5d..65bf61daf8c 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -16,6 +16,7 @@ import { IPartService } from 'vs/workbench/services/part/common/partService'; import { asFileEditorInput } from 'vs/workbench/common/editor'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; +import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; import { ipcRenderer as ipc, remote } from 'electron'; @@ -30,7 +31,9 @@ export class ElectronWindow { shellContainer: HTMLElement, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IEditorGroupService private editorGroupService: IEditorGroupService, - @IPartService private partService: IPartService + @IPartService private partService: IPartService, + @IWindowsService private windowsService: IWindowsService, + @IWindowService private windowService: IWindowService ) { this.win = win; this.windowId = win.id; @@ -43,12 +46,9 @@ export class ElectronWindow { if (platform.platform === platform.Platform.Mac) { this.editorGroupService.onEditorsChanged(() => { const fileInput = asFileEditorInput(this.editorService.getActiveEditorInput(), true); - let representedFilename = ''; - if (fileInput) { - representedFilename = fileInput.getResource().fsPath; - } + const fileName = fileInput ? fileInput.getResource().fsPath : ''; - ipc.send('vscode:setRepresentedFilename', this.windowId, representedFilename); + this.windowService.setRepresentedFilename(fileName); }); } @@ -80,7 +80,7 @@ export class ElectronWindow { DOM.EventHelper.stop(e, true); this.focus(); // make sure this window has focus so that the open call reaches the right window! - ipc.send('vscode:windowOpen', draggedExternalResources.map(r => r.fsPath)); // handled from browser process + this.windowsService.windowOpen(draggedExternalResources.map(r => r.fsPath)); cleanUp(); }) @@ -119,19 +119,12 @@ export class ElectronWindow { }); // Handle window.open() calls + const $this = this; (window).open = function (url: string, target: string, features: string, replace: boolean) { $this.openExternal(url); return null; }; - - // Patch focus to also focus the entire window - const originalFocus = window.focus; - const $this = this; - window.focus = function () { - originalFocus.call(this, arguments); - $this.focus(); - }; } private includesFolder(resources: URI[]): TPromise { @@ -140,10 +133,6 @@ export class ElectronWindow { })).then(res => res.some(res => !!res)); } - public openNew(): void { - ipc.send('vscode:openNewWindow'); // handled from browser process - } - public close(): void { this.win.close(); } @@ -165,28 +154,8 @@ export class ElectronWindow { return dialog.showSaveDialog(this.win, options); // https://github.com/electron/electron/issues/4936 } - public setFullScreen(fullscreen: boolean): void { - ipc.send('vscode:setFullScreen', this.windowId, fullscreen); // handled from browser process - } - - public openDevTools(): void { - ipc.send('vscode:openDevTools', this.windowId); // handled from browser process - } - - public setMenuBarVisibility(visible: boolean): void { - ipc.send('vscode:setMenuBarVisibility', this.windowId, visible); // handled from browser process - } - - public focus(): void { - ipc.send('vscode:focusWindow', this.windowId); // handled from browser process - } - - public flashFrame(): void { - ipc.send('vscode:flashFrame', this.windowId); // handled from browser process - } - - public showItemInFolder(path: string): void { - ipc.send('vscode:showItemInFolder', path); // handled from browser process to prevent foreground ordering issues on Windows + focus(): TPromise { + return this.windowService.focusWindow(); } public openExternal(url: string): void { diff --git a/src/vs/workbench/parts/debug/browser/debugActionItems.ts b/src/vs/workbench/parts/debug/browser/debugActionItems.ts index 1dacaa28e1a..daf49cc9268 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionItems.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionItems.ts @@ -8,7 +8,7 @@ import errors = require('vs/base/common/errors'); import { IAction } from 'vs/base/common/actions'; import { SelectActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IDebugService, State, IGlobalConfig } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, IGlobalConfig } from 'vs/workbench/parts/debug/common/debug'; export class DebugSelectActionItem extends SelectActionItem { @@ -25,15 +25,11 @@ export class DebugSelectActionItem extends SelectActionItem { this.toDispose.push(this.debugService.getViewModel().onDidSelectConfigurationName(name => { this.updateOptions(false); })); - this.toDispose.push(this.debugService.onDidChangeState(() => { - this.enabled = this.debugService.state === State.Inactive; - })); } public render(container: HTMLElement): void { super.render(container); this.updateOptions(true); - this.enabled = this.debugService.state === State.Inactive; } private updateOptions(changeDebugConfiguration: boolean): void { diff --git a/src/vs/workbench/parts/debug/browser/debugActions.ts b/src/vs/workbench/parts/debug/browser/debugActions.ts index 986c2aad011..101eeeb5270 100644 --- a/src/vs/workbench/parts/debug/browser/debugActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugActions.ts @@ -104,10 +104,6 @@ export class SelectConfigAction extends AbstractDebugAction { this.debugService.getViewModel().setSelectedConfigurationName(configName); return TPromise.as(null); } - - protected isEnabled(state: debug.State): boolean { - return super.isEnabled(state) && state === debug.State.Inactive; - } } export class StartAction extends AbstractDebugAction { @@ -116,14 +112,19 @@ export class StartAction extends AbstractDebugAction { constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService, @ICommandService private commandService: ICommandService) { super(id, label, 'debug-action start', debugService, keybindingService); + this.debugService.getViewModel().onDidSelectConfigurationName(() => { + this.updateEnablement(); + }); } public run(): TPromise { return this.commandService.executeCommand('_workbench.startDebug', this.debugService.getViewModel().selectedConfigurationName); } + // Disabled if the launch drop down shows the launch config that is already running. protected isEnabled(state: debug.State): boolean { - return super.isEnabled(state) && state === debug.State.Inactive; + const process = this.debugService.getModel().getProcesses(); + return super.isEnabled(state) && process.every(p => p.name !== this.debugService.getViewModel().selectedConfigurationName); } } diff --git a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts b/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts index b8e621b6569..654c37f5100 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts +++ b/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts @@ -295,7 +295,12 @@ export class DebugEditorModelManager implements IWorkbenchContribution { const mode = modelData ? modelData.model.getMode() : null; const modeId = mode ? mode.getId() : ''; - const condition = breakpoint.condition ? breakpoint.condition : breakpoint.hitCondition; + let condition: string; + if (breakpoint.condition && breakpoint.hitCondition) { + condition = `Expression: ${breakpoint.condition}\nHitCount: ${breakpoint.hitCondition}`; + } else { + condition = breakpoint.condition ? breakpoint.condition : breakpoint.hitCondition; + } const glyphMarginHoverMessage = `\`\`\`${modeId}\n${condition}\`\`\``; return { diff --git a/src/vs/workbench/parts/debug/common/debugProtocol.d.ts b/src/vs/workbench/parts/debug/common/debugProtocol.d.ts index 585f70172b8..3ff4c1d795f 100644 --- a/src/vs/workbench/parts/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/parts/debug/common/debugProtocol.d.ts @@ -654,7 +654,7 @@ declare module DebugProtocol { /** Response to 'setVariable' request. */ export interface SetVariableResponse extends Response { body: { - /** The new value of the variable. */ + /** The new value of the variable. See capability 'supportsValueEscaping' for details about how to treat newlines in multi-line strings. */ value: string; /** The type of the new value. Typically shown in the UI when hovering over the value. */ type?: string; @@ -756,7 +756,7 @@ declare module DebugProtocol { /** Response to 'evaluate' request. */ export interface EvaluateResponse extends Response { body: { - /** The result of the evaluate request. */ + /** The result of the evaluate request. See capability 'supportsValueEscaping' for details about how to treat newlines in multi-lines strings. */ result: string; /** The optional type of the evaluate result. */ type?: string; @@ -886,6 +886,8 @@ declare module DebugProtocol { additionalModuleColumns?: ColumnDescriptor[]; /** Checksum algorithms supported by the debug adapter. */ supportedChecksumAlgorithms?: ChecksumAlgorithm[]; + /** The debug adapter will be responsible for escaping newlines in variable values and evaluation results, and the client will display them as-is. If missing or false the client will escape newlines as needed. */ + supportsValueEscaping?: boolean; } /** An ExceptionBreakpointsFilter is shown in the UI as an option for configuring how exceptions are dealt with. */ @@ -1059,7 +1061,7 @@ declare module DebugProtocol { export interface Variable { /** The variable's name. */ name: string; - /** The variable's value. For structured objects this can be a multi line text, e.g. for a function the body of a function. */ + /** The variable's value. This can be a multi-line text, e.g. for a function the body of a function. See capability 'supportsValueEscaping' for details about how to treat newlines in multi-line strings. */ value: string; /** The type of the variable's value. Typically shown in the UI when hovering over the value. */ type?: string; diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index be06e7a9d4d..9392758d62d 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -28,6 +28,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IFileService, FileChangesEvent, FileChangeType, EventType } from 'vs/platform/files/common/files'; import { IEventService } from 'vs/platform/event/common/event'; import { IMessageService, CloseAction } from 'vs/platform/message/common/message'; +import { IWindowsService } from 'vs/platform/windows/common/windows'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc'; @@ -54,7 +55,6 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; 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'; const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; const DEBUG_BREAKPOINTS_ACTIVATED_KEY = 'debug.breakpointactivated'; @@ -87,6 +87,7 @@ export class DebugService implements debug.IDebugService { @IFileService private fileService: IFileService, @IMessageService private messageService: IMessageService, @IPartService private partService: IPartService, + @IWindowsService private windowsService: IWindowsService, @IWindowIPCService private windowService: IWindowIPCService, @ITelemetryService private telemetryService: ITelemetryService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @@ -352,7 +353,7 @@ export class DebugService implements debug.IDebugService { this.toDisposeOnSessionEnd[session.getId()].push(session.onDidExitAdapter(event => { // 'Run without debugging' mode VSCode must terminate the extension host. More details: #3905 if (session && session.configuration.type === 'extensionHost' && this.sessionStates[session.getId()] === debug.State.RunningNoDebug) { - ipc.send('vscode:closeExtensionHostWindow', this.contextService.getWorkspace().resource.fsPath); + this.windowsService.closeExtensionHostWindow(this.contextService.getWorkspace().resource.fsPath); } if (session && session.getId() === event.body.sessionId) { this.onSessionEnd(session); diff --git a/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts b/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts index 020f469a65c..e5698ef2249 100644 --- a/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts +++ b/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts @@ -307,7 +307,7 @@ export class RawDebugSession extends v8.V8Protocol implements debug.ISession { // Cancel all sent promises on disconnect so debug trees are not left in a broken state #3666. // Give a 1s timeout to give a chance for some promises to complete. setTimeout(() => { - this.sentPromises.forEach(p => p.cancel()); + this.sentPromises.forEach(p => p && p.cancel()); this.sentPromises = []; }, 1000); diff --git a/src/vs/workbench/parts/extensions/browser/dependenciesViewer.ts b/src/vs/workbench/parts/extensions/browser/dependenciesViewer.ts index e6f7aef8bf8..d0cbd34242c 100644 --- a/src/vs/workbench/parts/extensions/browser/dependenciesViewer.ts +++ b/src/vs/workbench/parts/extensions/browser/dependenciesViewer.ts @@ -42,7 +42,7 @@ export class DataSource implements IDataSource { } public hasChildren(tree: ITree, element: IExtensionDependencies): boolean { - return element.hasDependencies && !this.isSelfAncestor(element); + return element.hasDependencies; } public getChildren(tree: ITree, element: IExtensionDependencies): Promise { @@ -52,17 +52,6 @@ export class DataSource implements IDataSource { public getParent(tree: ITree, element: IExtensionDependencies): Promise { return TPromise.as(element.dependent); } - - private isSelfAncestor(element: IExtensionDependencies): boolean { - let ancestor = element.dependent; - while (ancestor !== null) { - if (ancestor.identifier === element.identifier) { - return true; - } - ancestor = ancestor.dependent; - } - return false; - } } export class Renderer implements IRenderer { diff --git a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts index 506f02d3007..1d04decb65a 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts @@ -668,8 +668,6 @@ export class DisableActionItem extends DropDownMenuActionItem { } } - - export class UpdateAllAction extends Action { static ID = 'workbench.extensions.action.updateAllExtensions'; @@ -716,7 +714,7 @@ export class ReloadAction extends Action { get extension(): IExtension { return this._extension; } set extension(extension: IExtension) { this._extension = extension; this.update(); } - private reloadMessaage: string = ''; + reloadMessaage: string = ''; private throttler: Throttler; constructor( diff --git a/src/vs/workbench/parts/extensions/common/extensions.ts b/src/vs/workbench/parts/extensions/common/extensions.ts index d756390fcfc..756bf88db54 100644 --- a/src/vs/workbench/parts/extensions/common/extensions.ts +++ b/src/vs/workbench/parts/extensions/common/extensions.ts @@ -47,7 +47,6 @@ export interface IExtension { telemetryData: any; getManifest(): TPromise; getReadme(): TPromise; - hasChangelog: boolean; getChangelog(): TPromise; } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index d763f2c2a37..9ec4af10195 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -14,7 +14,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IMessageService } from 'vs/platform/message/common/message'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { remote } from 'electron'; -import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; +import { IWindowsService } from 'vs/platform/windows/common/windows'; const dialog = remote.dialog; @@ -26,17 +26,15 @@ export class OpenExtensionsFolderAction extends Action { constructor( id: string, label: string, - @IWindowIPCService private windowService: IWindowIPCService, + @IWindowsService private windowsService: IWindowsService, @IEnvironmentService private environmentService: IEnvironmentService ) { super(id, label, null, true); } - run(): TPromise { + run(): TPromise { const extensionsHome = this.environmentService.extensionsPath; - this.windowService.getWindow().showItemInFolder(paths.normalize(extensionsHome, true)); - - return TPromise.as(true); + return this.windowsService.showItemInFolder(paths.normalize(extensionsHome, true)); } protected isEnabled(): boolean { diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 29739425a0f..e47c20280a5 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -162,7 +162,7 @@ class Extension implements IExtension { } get outdated(): boolean { - return this.gallery && this.type === LocalExtensionType.User && semver.gt(this.latestVersion, this.version); + return !!this.gallery && this.type === LocalExtensionType.User && semver.gt(this.latestVersion, this.version); } get telemetryData(): any { @@ -201,10 +201,6 @@ class Extension implements IExtension { return this.galleryService.getAsset(readmeUrl).then(asText); } - get hasChangelog(): boolean { - return !!(this.changelogUrl); - } - getChangelog(): TPromise { const changelogUrl = this.changelogUrl; @@ -223,22 +219,27 @@ class Extension implements IExtension { get dependencies(): string[] { const { local, gallery } = this; - if (gallery) { - return gallery.properties.dependencies; - } if (local && local.manifest.extensionDependencies) { return local.manifest.extensionDependencies; } + if (gallery) { + return gallery.properties.dependencies; + } return []; } } class ExtensionDependencies implements IExtensionDependencies { + private _hasDependencies: boolean = null; + constructor(private _extension: IExtension, private _identifier: string, private _map: Map, private _dependent: IExtensionDependencies = null) { } get hasDependencies(): boolean { - return this._extension ? this._extension.dependencies.length > 0 : false; + if (this._hasDependencies === null) { + this._hasDependencies = this.computeHasDependencies(); + } + return this._hasDependencies; } get extension(): IExtension { @@ -254,8 +255,25 @@ class ExtensionDependencies implements IExtensionDependencies { } get dependencies(): IExtensionDependencies[] { + if (!this.hasDependencies) { + return []; + } return this._extension.dependencies.map(d => new ExtensionDependencies(this._map.get(d), d, this._map, this)); } + + private computeHasDependencies(): boolean { + if (this._extension && this._extension.dependencies.length > 0) { + let dependent = this._dependent; + while (dependent !== null) { + if (dependent.identifier === this.identifier) { + return false; + } + dependent = dependent.dependent; + } + return true; + } + return false; + } } function stripVersion(id: string): string { @@ -486,7 +504,6 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { return this.doSetEnablement(extension, enable, workspace).then(reload => { this.telemetryService.publicLog(enable ? 'extension:enable' : 'extension:disable', extension.telemetryData); - this._onChange.fire(); }); } @@ -503,6 +520,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { } return this.extensionService.uninstall(local); + } private doSetEnablement(extension: IExtension, enable: boolean, workspace: boolean): TPromise { @@ -511,11 +529,11 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { } const globalElablement = this.extensionEnablementService.setEnablement(extension.identifier, enable, false); - if (!this.workspaceContextService.getWorkspace()) { - return globalElablement; + if (enable && this.workspaceContextService.getWorkspace()) { + const workspaceEnablement = this.extensionEnablementService.setEnablement(extension.identifier, enable, true); + return TPromise.join([globalElablement, workspaceEnablement]).then(values => values[0] || values[1]); } - return TPromise.join([globalElablement, this.extensionEnablementService.setEnablement(extension.identifier, enable, true)]) - .then(values => values[0] || values[1]); + return globalElablement; } private onInstallExtension(event: InstallExtensionEvent): void { @@ -612,6 +630,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { const workspaceDisabledExtensions = this.extensionEnablementService.getWorkspaceDisabledExtensions(); extension.disabledGlobally = globallyDisabledExtensions.indexOf(extension.identifier) !== -1; extension.disabledForWorkspace = workspaceDisabledExtensions.indexOf(extension.identifier) !== -1; + this._onChange.fire(); } } diff --git a/src/vs/workbench/parts/extensions/test/browser/extensionsActions.test.ts b/src/vs/workbench/parts/extensions/test/browser/extensionsActions.test.ts new file mode 100644 index 00000000000..b897478b3c8 --- /dev/null +++ b/src/vs/workbench/parts/extensions/test/browser/extensionsActions.test.ts @@ -0,0 +1,1135 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { assign } from 'vs/base/common/objects'; +import { generateUuid } from 'vs/base/common/uuid'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; +import * as ExtensionsActions from 'vs/workbench/parts/extensions/browser/extensionsActions'; +import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; +import { + IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, ILocalExtension, LocalExtensionType, IGalleryExtension, + DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent +} from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; +import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; +import { IURLService } from 'vs/platform/url/common/url'; +import { TestInstantiationService } from 'vs/test/utils/instantiationTestUtils'; +import { TestWorkspace } from 'vs/test/utils/servicesTestUtils'; +import { Emitter } from 'vs/base/common/event'; +import { IPager } from 'vs/base/common/paging'; +import { ITelemetryService, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { StorageService, InMemoryLocalStorage } from 'vs/workbench/services/storage/common/storageService'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IWorkspaceContextService, WorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IExtensionService } from 'vs/platform/extensions/common/extensions'; + +suite('ExtensionsActions Test => ', () => { + + let instantiationService: TestInstantiationService; + + const installEvent: Emitter = new Emitter(), + didInstallEvent: Emitter = new Emitter(), + uninstallEvent: Emitter = new Emitter(), + didUninstallEvent: Emitter = new Emitter(); + + setup(() => { + instantiationService = new TestInstantiationService(); + instantiationService.stub(IURLService, { onOpenURL: new Emitter().event }); + instantiationService.stub(ITelemetryService, NullTelemetryService); + + instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); + + instantiationService.stub(IExtensionManagementService, ExtensionManagementService); + instantiationService.stub(IExtensionManagementService, 'onInstallExtension', installEvent.event); + instantiationService.stub(IExtensionManagementService, 'onDidInstallExtension', didInstallEvent.event); + instantiationService.stub(IExtensionManagementService, 'onUninstallExtension', uninstallEvent.event); + instantiationService.stub(IExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); + + instantiationService.stub(IWorkspaceContextService, WorkspaceContextService); + instantiationService.stub(IWorkspaceContextService, 'getWorkspace', TestWorkspace); + instantiationService.stub(IEnvironmentService, { disableExtensions: false }); + instantiationService.stub(IStorageService, instantiationService.createInstance(StorageService, new InMemoryLocalStorage(), new InMemoryLocalStorage())); + instantiationService.stub(IExtensionEnablementService, instantiationService.createInstance(ExtensionEnablementService)); + + instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService)); + + instantiationService.stub(IExtensionService, { getExtensions: () => TPromise.wrap([]) }); + }); + + teardown(() => { + (instantiationService.get(IExtensionsWorkbenchService)).dispose(); + }); + + test('Install action is disabled when there is no extension', () => { + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction); + + assert.ok(!testObject.enabled); + }); + + test('Test Install action when state is installed', (done) => { + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + workbenchService.queryLocal().done(() => { + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { id: local.id }))); + workbenchService.queryGallery().done((paged) => { + testObject.extension = paged.firstPage[0]; + assert.ok(!testObject.enabled); + assert.equal('Install', testObject.label); + assert.equal('extension-action install', testObject.class); + done(); + }); + }); + }); + + test('Test Install action when state is installing', (done) => { + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction); + const gallery = aGalleryExtension('a'); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + workbenchService.queryGallery().done((paged) => { + testObject.extension = paged.firstPage[0]; + installEvent.fire({ id: gallery.id, gallery }); + + assert.ok(!testObject.enabled); + assert.equal('Installing', testObject.label); + assert.equal('extension-action install installing', testObject.class); + done(); + }); + }); + + test('Test Install action when state is uninstalled', (done) => { + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction); + const gallery = aGalleryExtension('a'); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + workbenchService.queryGallery().done((paged) => { + testObject.extension = paged.firstPage[0]; + assert.ok(testObject.enabled); + assert.equal('Install', testObject.label); + done(); + }); + }); + + test('Test Install action when extension is system action', (done) => { + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction); + const local = aLocalExtension('a', {}, { type: LocalExtensionType.System }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + uninstallEvent.fire(local.id); + didUninstallEvent.fire({ id: local.id }); + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test Install action when extension doesnot has gallery', (done) => { + const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + uninstallEvent.fire(local.id); + didUninstallEvent.fire({ id: local.id }); + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Uninstall action is disabled when there is no extension', () => { + const testObject: ExtensionsActions.UninstallAction = instantiationService.createInstance(ExtensionsActions.UninstallAction); + + assert.ok(!testObject.enabled); + }); + + test('Test Uninstall action when state is uninstalling', (done) => { + const testObject: ExtensionsActions.UninstallAction = instantiationService.createInstance(ExtensionsActions.UninstallAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + uninstallEvent.fire(local.id); + assert.ok(!testObject.enabled); + assert.equal('Uninstalling', testObject.label); + assert.equal('extension-action uninstall uninstalling', testObject.class); + done(); + }); + }); + + test('Test Uninstall action when state is installed and is user extension', (done) => { + const testObject: ExtensionsActions.UninstallAction = instantiationService.createInstance(ExtensionsActions.UninstallAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Uninstall', testObject.label); + assert.equal('extension-action uninstall', testObject.class); + done(); + }); + }); + + test('Test Uninstall action when state is installed and is system extension', (done) => { + const testObject: ExtensionsActions.UninstallAction = instantiationService.createInstance(ExtensionsActions.UninstallAction); + const local = aLocalExtension('a', {}, { type: LocalExtensionType.System }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + assert.equal('Uninstall', testObject.label); + assert.equal('extension-action uninstall', testObject.class); + done(); + }); + }); + + test('Test Uninstall action after extension is installed', (done) => { + const testObject: ExtensionsActions.UninstallAction = instantiationService.createInstance(ExtensionsActions.UninstallAction); + const gallery = aGalleryExtension('a'); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + instantiationService.get(IExtensionsWorkbenchService).queryGallery().done(paged => { + testObject.extension = paged.firstPage[0]; + + installEvent.fire({ id: gallery.id, gallery }); + didInstallEvent.fire({ id: gallery.id, gallery, local: aLocalExtension('a', gallery, gallery) }); + + assert.ok(testObject.enabled); + assert.equal('Uninstall', testObject.label); + assert.equal('extension-action uninstall', testObject.class); + done(); + }); + }); + + test('Test CombinedInstallAction when there is no extension', () => { + const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction); + + assert.ok(!testObject.enabled); + assert.equal('extension-action install no-extension', testObject.class); + }); + + test('Test CombinedInstallAction when extension is system extension', (done) => { + const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction); + const local = aLocalExtension('a', {}, { type: LocalExtensionType.System }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + assert.equal('extension-action install no-extension', testObject.class); + done(); + }); + }); + + test('Test CombinedInstallAction when installAction is enabled', (done) => { + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction); + const gallery = aGalleryExtension('a'); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + workbenchService.queryGallery().done((paged) => { + testObject.extension = paged.firstPage[0]; + assert.ok(testObject.enabled); + assert.equal('Install', testObject.label); + assert.equal('extension-action install', testObject.class); + done(); + }); + }); + + test('Test CombinedInstallAction when unInstallAction is enabled', (done) => { + const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('Uninstall', testObject.label); + assert.equal('extension-action uninstall', testObject.class); + done(); + }); + }); + + test('Test CombinedInstallAction when state is installing', (done) => { + const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction); + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + const gallery = aGalleryExtension('a'); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + workbenchService.queryGallery().done((paged) => { + testObject.extension = paged.firstPage[0]; + installEvent.fire({ id: gallery.id, gallery }); + + assert.ok(!testObject.enabled); + assert.equal('Installing', testObject.label); + assert.equal('extension-action install installing', testObject.class); + done(); + }); + }); + + test('Test CombinedInstallAction when state is uninstalling', (done) => { + const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + uninstallEvent.fire(local.id); + assert.ok(!testObject.enabled); + assert.equal('Uninstalling', testObject.label); + assert.equal('extension-action uninstall uninstalling', testObject.class); + done(); + }); + }); + + test('Test UpdateAction when there is no extension', () => { + const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction); + + assert.ok(!testObject.enabled); + }); + + test('Test UpdateAction when extension is uninstalled', (done) => { + const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction); + const gallery = aGalleryExtension('a', { version: '1.0.0' }); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + instantiationService.get(IExtensionsWorkbenchService).queryGallery().done((paged) => { + testObject.extension = paged.firstPage[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test UpdateAction when extension is installed and not outdated', (done) => { + const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction); + const local = aLocalExtension('a', { version: '1.0.0' }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {id: local.id, version: local.manifest.version}))); + instantiationService.get(IExtensionsWorkbenchService).queryGallery().done(extensions => { + assert.ok(!testObject.enabled); + done(); + }); + }); + }); + + test('Test UpdateAction when extension is installed outdated and system extension', (done) => { + const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction); + const local = aLocalExtension('a', { version: '1.0.0' }, { type: LocalExtensionType.System }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { id: local.id, version: '1.0.1' }))); + instantiationService.get(IExtensionsWorkbenchService).queryGallery().done(extensions => { + assert.ok(!testObject.enabled); + done(); + }); + }); + }); + + test('Test UpdateAction when extension is installed outdated and user extension', (done) => { + const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction); + const local = aLocalExtension('a', { version: '1.0.0' }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { id: local.id, version: '1.0.1' }))); + instantiationService.get(IExtensionsWorkbenchService).queryGallery().done(extensions => { + assert.ok(testObject.enabled); + done(); + }); + }); + }); + + test('Test UpdateAction when extension is installing and outdated and user extension', (done) => { + const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction); + const local = aLocalExtension('a', { version: '1.0.0' }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + const gallery = aGalleryExtension('a', { id: local.id, version: '1.0.1' }); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + instantiationService.get(IExtensionsWorkbenchService).queryGallery().done(extensions => { + installEvent.fire({ id: local.id, gallery }); + assert.ok(!testObject.enabled); + done(); + }); + }); + }); + + test('Test ManageExtensionAction when there is no extension', () => { + const testObject: ExtensionsActions.ManageExtensionAction = instantiationService.createInstance(ExtensionsActions.ManageExtensionAction); + + assert.ok(!testObject.enabled); + }); + + test('Test ManageExtensionAction when extension is installed', (done) => { + const testObject: ExtensionsActions.ManageExtensionAction = instantiationService.createInstance(ExtensionsActions.ManageExtensionAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + assert.equal('extension-action manage', testObject.class); + assert.equal('', testObject.tooltip); + + done(); + }); + }); + + test('Test ManageExtensionAction when extension is uninstalled', (done) => { + const testObject: ExtensionsActions.ManageExtensionAction = instantiationService.createInstance(ExtensionsActions.ManageExtensionAction); + const gallery = aGalleryExtension('a'); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + instantiationService.get(IExtensionsWorkbenchService).queryGallery().done(page => { + testObject.extension = page.firstPage[0]; + assert.ok(!testObject.enabled); + assert.equal('extension-action manage no-extension', testObject.class); + assert.equal('', testObject.tooltip); + + done(); + }); + }); + + test('Test ManageExtensionAction when extension is installing', (done) => { + const testObject: ExtensionsActions.ManageExtensionAction = instantiationService.createInstance(ExtensionsActions.ManageExtensionAction); + const gallery = aGalleryExtension('a'); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + instantiationService.get(IExtensionsWorkbenchService).queryGallery().done(page => { + testObject.extension = page.firstPage[0]; + + installEvent.fire({ id: gallery.id, gallery }); + assert.ok(!testObject.enabled); + assert.equal('extension-action manage no-extension', testObject.class); + assert.equal('', testObject.tooltip); + + done(); + }); + }); + + test('Test ManageExtensionAction when extension is uninstalling', (done) => { + const testObject: ExtensionsActions.ManageExtensionAction = instantiationService.createInstance(ExtensionsActions.ManageExtensionAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + uninstallEvent.fire(local.id); + + assert.ok(!testObject.enabled); + assert.equal('extension-action manage', testObject.class); + assert.equal('Uninstalling', testObject.tooltip); + + done(); + }); + }); + + test('Test EnableForWorkspaceAction when there is no extension', () => { + const testObject: ExtensionsActions.EnableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.EnableForWorkspaceAction, 'id'); + + assert.ok(!testObject.enabled); + }); + + test('Test EnableForWorkspaceAction when there extension is not disabled', (done) => { + const testObject: ExtensionsActions.EnableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.EnableForWorkspaceAction, 'id'); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test EnableForWorkspaceAction when there extension is disabled globally', (done) => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + const testObject: ExtensionsActions.EnableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.EnableForWorkspaceAction, 'id'); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test EnableForWorkspaceAction when extension is disabled for workspace', (done) => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false, true); + const testObject: ExtensionsActions.EnableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.EnableForWorkspaceAction, 'id'); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + done(); + }); + }); + + test('Test EnableForWorkspaceAction when the extension is disabled in both', (done) => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false, true); + const testObject: ExtensionsActions.EnableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.EnableForWorkspaceAction, 'id'); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test EnableGloballyAction when there is no extension', () => { + const testObject: ExtensionsActions.EnableGloballyAction = instantiationService.createInstance(ExtensionsActions.EnableGloballyAction, 'id'); + + assert.ok(!testObject.enabled); + }); + + test('Test EnableGloballyAction when the extension is not disabled', (done) => { + const testObject: ExtensionsActions.EnableGloballyAction = instantiationService.createInstance(ExtensionsActions.EnableGloballyAction, 'id'); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test EnableGloballyAction when the extension is disabled for workspace', (done) => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false, true); + const testObject: ExtensionsActions.EnableGloballyAction = instantiationService.createInstance(ExtensionsActions.EnableGloballyAction, 'id'); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test EnableGloballyAction when the extension is disabled globally', (done) => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + const testObject: ExtensionsActions.EnableGloballyAction = instantiationService.createInstance(ExtensionsActions.EnableGloballyAction, 'id'); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + done(); + }); + }); + + test('Test EnableGloballyAction when the extension is disabled in both', (done) => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false, true); + const testObject: ExtensionsActions.EnableGloballyAction = instantiationService.createInstance(ExtensionsActions.EnableGloballyAction, 'id'); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + done(); + }); + }); + + test('Test EnableAction when there is no extension', () => { + const testObject: ExtensionsActions.EnableAction = instantiationService.createInstance(ExtensionsActions.EnableAction); + + assert.ok(!testObject.enabled); + }); + + test('Test EnableAction when extension is installed and enabled', (done) => { + const testObject: ExtensionsActions.EnableAction = instantiationService.createInstance(ExtensionsActions.EnableAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test EnableAction when extension is installed and disabled globally', (done) => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + const testObject: ExtensionsActions.EnableAction = instantiationService.createInstance(ExtensionsActions.EnableAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + done(); + }); + }); + + test('Test EnableAction when extension is installed and disabled for workspace', (done) => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false, true); + const testObject: ExtensionsActions.EnableAction = instantiationService.createInstance(ExtensionsActions.EnableAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + done(); + }); + }); + + test('Test EnableAction when extension is uninstalled', (done) => { + const testObject: ExtensionsActions.EnableAction = instantiationService.createInstance(ExtensionsActions.EnableAction); + const gallery = aGalleryExtension('a'); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + instantiationService.get(IExtensionsWorkbenchService).queryGallery().done(page => { + testObject.extension = page.firstPage[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test EnableAction when extension is installing', (done) => { + const testObject: ExtensionsActions.EnableAction = instantiationService.createInstance(ExtensionsActions.EnableAction); + const gallery = aGalleryExtension('a'); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + instantiationService.get(IExtensionsWorkbenchService).queryGallery().done(page => { + testObject.extension = page.firstPage[0]; + + installEvent.fire({ id: gallery.id, gallery }); + assert.ok(!testObject.enabled); + + done(); + }); + }); + + test('Test EnableAction when extension is uninstalling', (done) => { + const testObject: ExtensionsActions.EnableAction = instantiationService.createInstance(ExtensionsActions.EnableAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + uninstallEvent.fire(local.id); + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test DisableForWorkspaceAction when there is no extension', () => { + const testObject: ExtensionsActions.DisableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.DisableForWorkspaceAction, 'id'); + + assert.ok(!testObject.enabled); + }); + + test('Test DisableForWorkspaceAction when the extension is disabled globally', (done) => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + const testObject: ExtensionsActions.DisableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.DisableForWorkspaceAction, 'id'); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test DisableForWorkspaceAction when the extension is disabled workspace', (done) => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + const testObject: ExtensionsActions.DisableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.DisableForWorkspaceAction, 'id'); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test DisableForWorkspaceAction when extension is enabled', (done) => { + const testObject: ExtensionsActions.DisableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.DisableForWorkspaceAction, 'id'); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + done(); + }); + }); + + test('Test DisableGloballyAction when there is no extension', () => { + const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction, 'id'); + + assert.ok(!testObject.enabled); + }); + + test('Test DisableGloballyAction when the extension is disabled globally', (done) => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction, 'id'); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test DisableGloballyAction when the extension is disabled for workspace', (done) => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false, true); + const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction, 'id'); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test DisableGloballyAction when the extension is enabled', (done) => { + const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction, 'id'); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + done(); + }); + }); + + test('Test DisableAction when there is no extension', () => { + const testObject: ExtensionsActions.DisableAction = instantiationService.createInstance(ExtensionsActions.DisableAction); + + assert.ok(!testObject.enabled); + }); + + test('Test DisableAction when extension is installed and enabled', (done) => { + const testObject: ExtensionsActions.DisableAction = instantiationService.createInstance(ExtensionsActions.DisableAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(testObject.enabled); + done(); + }); + }); + + test('Test DisableAction when extension is installed and disabled globally', (done) => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + const testObject: ExtensionsActions.DisableAction = instantiationService.createInstance(ExtensionsActions.DisableAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test DisableAction when extension is installed and disabled for workspace', (done) => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false, true); + const testObject: ExtensionsActions.DisableAction = instantiationService.createInstance(ExtensionsActions.DisableAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test DisableAction when extension is uninstalled', (done) => { + const testObject: ExtensionsActions.DisableAction = instantiationService.createInstance(ExtensionsActions.DisableAction); + const gallery = aGalleryExtension('a'); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + instantiationService.get(IExtensionsWorkbenchService).queryGallery().done(page => { + testObject.extension = page.firstPage[0]; + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test DisableAction when extension is installing', (done) => { + const testObject: ExtensionsActions.DisableAction = instantiationService.createInstance(ExtensionsActions.DisableAction); + const gallery = aGalleryExtension('a'); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + instantiationService.get(IExtensionsWorkbenchService).queryGallery().done(page => { + testObject.extension = page.firstPage[0]; + + installEvent.fire({ id: gallery.id, gallery }); + assert.ok(!testObject.enabled); + + done(); + }); + }); + + test('Test DisableAction when extension is uninstalling', (done) => { + const testObject: ExtensionsActions.DisableAction = instantiationService.createInstance(ExtensionsActions.DisableAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + uninstallEvent.fire(local.id); + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test UpdateAllAction when no installed extensions', () => { + const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label'); + + assert.ok(!testObject.enabled); + }); + + test('Test UpdateAllAction when installed extensions are not outdated', (done) => { + const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a'), aLocalExtension('b')]); + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test UpdateAllAction when some installed extensions are outdated', (done) => { + const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label'); + const local = [aLocalExtension('a', { version: '1.0.1' }), aLocalExtension('b', { version: '1.0.1' }), aLocalExtension('c', { version: '1.0.1' })]; + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', local); + workbenchService.queryLocal().done(() => { + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { id: local[0].id, version: '1.0.2' }), aGalleryExtension('b', { id: local[1].id, version: '1.0.2' }), aGalleryExtension('c', local))); + workbenchService.queryGallery().done(() => { + assert.ok(testObject.enabled); + done(); + }); + }); + }); + + test('Test UpdateAllAction when some installed extensions are outdated and some outdated are being installed', (done) => { + const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label'); + const local = [aLocalExtension('a', { version: '1.0.1' }), aLocalExtension('b', { version: '1.0.1' }), aLocalExtension('c', { version: '1.0.1' })]; + const gallery = [aGalleryExtension('a', { id: local[0].id, version: '1.0.2' }), aGalleryExtension('b', { id: local[1].id, version: '1.0.2' }), aGalleryExtension('c', local)]; + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', local); + workbenchService.queryLocal().done(() => { + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...gallery)); + workbenchService.queryGallery().done(() => { + installEvent.fire({ id: local[0].id, gallery: gallery[0] }); + assert.ok(testObject.enabled); + done(); + }); + }); + }); + + test('Test UpdateAllAction when some installed extensions are outdated and all outdated are being installed', (done) => { + const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label'); + const local = [aLocalExtension('a', { version: '1.0.1' }), aLocalExtension('b', { version: '1.0.1' }), aLocalExtension('c', { version: '1.0.1' })]; + const gallery = [aGalleryExtension('a', { id: local[0].id, version: '1.0.2' }), aGalleryExtension('b', { id: local[1].id, version: '1.0.2' }), aGalleryExtension('c', local)]; + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', local); + workbenchService.queryLocal().done(() => { + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...gallery)); + workbenchService.queryGallery().done(() => { + installEvent.fire({ id: local[0].id, gallery: gallery[0] }); + installEvent.fire({ id: local[1].id, gallery: gallery[1] }); + assert.ok(!testObject.enabled); + done(); + }); + }); + }); + + test('Test ReloadAction when there is no extension', () => { + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + + assert.ok(!testObject.enabled); + }); + + test('Test ReloadAction when extension state is installing', (done) => { + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + const gallery = aGalleryExtension('a'); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + workbenchService.queryGallery().done((paged) => { + testObject.extension = paged.firstPage[0]; + installEvent.fire({ id: gallery.id, gallery }); + + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test ReloadAction when extension state is uninstalling', (done) => { + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + uninstallEvent.fire(local.id); + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test ReloadAction when extension is newly installed', (done) => { + instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ id: 'pub.b' }]); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + const gallery = aGalleryExtension('a'); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + instantiationService.get(IExtensionsWorkbenchService).queryGallery().done((paged) => { + testObject.extension = paged.firstPage[0]; + installEvent.fire({ id: gallery.id, gallery }); + didInstallEvent.fire({ id: gallery.id, gallery, local: aLocalExtension('a', gallery, gallery) }); + + assert.ok(testObject.enabled); + assert.equal('Reload to activate', testObject.tooltip); + assert.equal(`Reload this window to activate the extension 'a'?`, testObject.reloadMessaage); + done(); + }); + }); + + test('Test ReloadAction when extension is installed and uninstalled', (done) => { + instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ id: 'pub.b' }]); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + const gallery = aGalleryExtension('a'); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + instantiationService.get(IExtensionsWorkbenchService).queryGallery().done((paged) => { + testObject.extension = paged.firstPage[0]; + installEvent.fire({ id: gallery.id, gallery }); + didInstallEvent.fire({ id: gallery.id, gallery, local: aLocalExtension('a', gallery, gallery) }); + uninstallEvent.fire(gallery.id); + didUninstallEvent.fire({ id: gallery.id }); + + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test ReloadAction when extension is uninstalled', (done) => { + instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ id: 'pub.a' }]); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + uninstallEvent.fire(local.id); + didUninstallEvent.fire({ id: local.id }); + + assert.ok(testObject.enabled); + assert.equal('Reload to deactivate', testObject.tooltip); + assert.equal(`Reload this window to deactivate the uninstalled extension 'a'?`, testObject.reloadMessaage); + done(); + }); + }); + + test('Test ReloadAction when extension is uninstalled and installed', (done) => { + instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ id: 'pub.a' }]); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + const local = aLocalExtension('a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + instantiationService.get(IExtensionsWorkbenchService).queryLocal().done(extensions => { + testObject.extension = extensions[0]; + uninstallEvent.fire(local.id); + didUninstallEvent.fire({ id: local.id }); + const gallery = aGalleryExtension('a', { id: local.id }); + installEvent.fire({ id: local.id, gallery }); + didInstallEvent.fire({ id: gallery.id, gallery, local }); + + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test ReloadAction when extension is updated while running', (done) => { + instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ id: 'pub.a', version: '1.0.1' }]); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + const local = aLocalExtension('a', { version: '1.0.1' }); + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + workbenchService.queryLocal().done(extensions => { + testObject.extension = extensions[0]; + + const gallery = aGalleryExtension('a', { id: local.id, version: '1.0.2' }); + installEvent.fire({ id: gallery.id, gallery }); + didInstallEvent.fire({ id: gallery.id, gallery, local: aLocalExtension('a', gallery, gallery) }); + + assert.ok(testObject.enabled); + assert.equal('Reload to update', testObject.tooltip); + assert.equal(`Reload this window to activate the updated extension 'a'?`, testObject.reloadMessaage); + done(); + + }); + }); + + test('Test ReloadAction when extension is updated when not running', (done) => { + instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ id: 'pub.b' }]); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + const local = aLocalExtension('a', { version: '1.0.1' }); + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + workbenchService.queryLocal().done(extensions => { + testObject.extension = extensions[0]; + + const gallery = aGalleryExtension('a', { id: local.id, version: '1.0.2' }); + installEvent.fire({ id: gallery.id, gallery }); + didInstallEvent.fire({ id: gallery.id, gallery, local: aLocalExtension('a', gallery, gallery) }); + + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test ReloadAction when extension is disabled when running', (done) => { + instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ id: 'pub.a' }]); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + const local = aLocalExtension('a'); + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + workbenchService.queryLocal().done(extensions => { + testObject.extension = extensions[0]; + workbenchService.setEnablement(extensions[0], false); + + assert.ok(testObject.enabled); + assert.equal('Reload to deactivate', testObject.tooltip); + assert.equal(`Reload this window to deactivate the extension 'a'?`, testObject.reloadMessaage); + done(); + }); + }); + + test('Test ReloadAction when extension enablement is toggled when running', (done) => { + instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ id: 'pub.a' }]); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + const local = aLocalExtension('a'); + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + workbenchService.queryLocal().done(extensions => { + testObject.extension = extensions[0]; + workbenchService.setEnablement(extensions[0], false); + workbenchService.setEnablement(extensions[0], true); + + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test ReloadAction when extension is enabled when not running', (done) => { + instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ id: 'pub.b' }]); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + const local = aLocalExtension('a'); + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + workbenchService.queryLocal().done(extensions => { + testObject.extension = extensions[0]; + workbenchService.setEnablement(extensions[0], true); + + assert.ok(testObject.enabled); + assert.equal('Reload to activate', testObject.tooltip); + assert.equal(`Reload this window to activate the extension 'a'?`, testObject.reloadMessaage); + done(); + }); + }); + + test('Test ReloadAction when extension enablement is toggled when not running', (done) => { + instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ id: 'pub.b' }]); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + const local = aLocalExtension('a'); + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + workbenchService.queryLocal().done(extensions => { + testObject.extension = extensions[0]; + workbenchService.setEnablement(extensions[0], true); + workbenchService.setEnablement(extensions[0], false); + + assert.ok(!testObject.enabled); + done(); + }); + }); + + test('Test ReloadAction when extension is updated when not running and enabled', (done) => { + instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ id: 'pub.b' }]); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + const local = aLocalExtension('a', { version: '1.0.1' }); + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + workbenchService.queryLocal().done(extensions => { + testObject.extension = extensions[0]; + + const gallery = aGalleryExtension('a', { id: local.id, version: '1.0.2' }); + installEvent.fire({ id: gallery.id, gallery }); + didInstallEvent.fire({ id: gallery.id, gallery, local: aLocalExtension('a', gallery, gallery) }); + workbenchService.setEnablement(extensions[0], true); + + assert.ok(testObject.enabled); + assert.equal('Reload to activate', testObject.tooltip); + assert.equal(`Reload this window to activate the extension 'a'?`, testObject.reloadMessaage); + done(); + }); + }); + + function aLocalExtension(name: string = 'someext', manifest: any = {}, properties: any = {}): ILocalExtension { + const localExtension = Object.create({ manifest: {} }); + assign(localExtension, { type: LocalExtensionType.User, id: generateUuid() }, properties); + assign(localExtension.manifest, { name, publisher: 'pub' }, manifest); + localExtension.metadata = { id: localExtension.id, publisherId: localExtension.manifest.publisher, publisherDisplayName: 'somename' }; + return localExtension; + } + + function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: any = {}): IGalleryExtension { + const galleryExtension = Object.create({}); + assign(galleryExtension, { name, publisher: 'pub', id: generateUuid(), properties: {}, assets: {} }, properties); + assign(galleryExtension.properties, { dependencies: [] }, galleryExtensionProperties); + assign(galleryExtension.assets, assets); + return galleryExtension; + } + + function aPage(...objects: T[]): IPager { + return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null }; + } + +}); \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/test/common/extensionQuery.test.ts b/src/vs/workbench/parts/extensions/test/common/extensionQuery.test.ts index 63a8d724082..865a5173018 100644 --- a/src/vs/workbench/parts/extensions/test/common/extensionQuery.test.ts +++ b/src/vs/workbench/parts/extensions/test/common/extensionQuery.test.ts @@ -6,7 +6,7 @@ 'use strict'; import * as assert from 'assert'; -import { Query } from '../../common/extensionQuery'; +import { Query } from 'vs/workbench/parts/extensions/common/extensionQuery'; suite('Extension query', () => { test('parse', () => { diff --git a/src/vs/workbench/parts/extensions/test/node/extensionsWorkbenchService.test.ts b/src/vs/workbench/parts/extensions/test/node/extensionsWorkbenchService.test.ts new file mode 100644 index 00000000000..d377b57bdfe --- /dev/null +++ b/src/vs/workbench/parts/extensions/test/node/extensionsWorkbenchService.test.ts @@ -0,0 +1,855 @@ +/*--------------------------------------------------------------------------------------------- + * 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 sinon from 'sinon'; +import * as assert from 'assert'; +import * as fs from 'fs'; +import { assign } from 'vs/base/common/objects'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IExtensionsWorkbenchService, ExtensionState } from 'vs/workbench/parts/extensions/common/extensions'; +import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; +import { + IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, ILocalExtension, LocalExtensionType, IGalleryExtension, + DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent +} from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; +import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; +import { IURLService } from 'vs/platform/url/common/url'; +import { TestInstantiationService } from 'vs/test/utils/instantiationTestUtils'; +import { TestEnvironmentService, TestWorkspace } from 'vs/test/utils/servicesTestUtils'; +import Event, { Emitter } from 'vs/base/common/event'; +import { IPager } from 'vs/base/common/paging'; +import { ITelemetryService, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { StorageService, InMemoryLocalStorage } from 'vs/workbench/services/storage/common/storageService'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IWorkspaceContextService, WorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; + +suite('ExtensionsWorkbenchService Test', () => { + + let instantiationService: TestInstantiationService; + let testObject: IExtensionsWorkbenchService; + + const installEvent: Emitter = new Emitter(), + didInstallEvent: Emitter = new Emitter(), + uninstallEvent: Emitter = new Emitter(), + didUninstallEvent: Emitter = new Emitter(); + + setup(() => { + instantiationService = new TestInstantiationService(); + instantiationService.stub(IURLService, { onOpenURL: new Emitter().event }); + instantiationService.stub(ITelemetryService, NullTelemetryService); + + instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); + + instantiationService.stub(IExtensionManagementService, ExtensionManagementService); + instantiationService.stub(IExtensionManagementService, 'onInstallExtension', installEvent.event); + instantiationService.stub(IExtensionManagementService, 'onDidInstallExtension', didInstallEvent.event); + instantiationService.stub(IExtensionManagementService, 'onUninstallExtension', uninstallEvent.event); + instantiationService.stub(IExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); + + instantiationService.stub(IWorkspaceContextService, WorkspaceContextService); + instantiationService.stub(IWorkspaceContextService, 'getWorkspace', TestWorkspace); + instantiationService.stub(IEnvironmentService, TestEnvironmentService); + instantiationService.stub(IStorageService, instantiationService.createInstance(StorageService, new InMemoryLocalStorage(), new InMemoryLocalStorage())); + instantiationService.stub(IExtensionEnablementService, instantiationService.createInstance(ExtensionEnablementService)); + }); + + teardown(() => { + (testObject).dispose(); + }); + + test('test gallery extension', (done) => { + const expected = aGalleryExtension('expectedName', { + displayName: 'expectedDisplayName', + version: '1.5', + publisherId: 'expectedPublisherId', + publisher: 'expectedPublisher', + publisherDisplayName: 'expectedPublisherDisplayName', + description: 'expectedDescription', + installCount: 1000, + rating: 4, + ratingCount: 100 + }, { + dependencies: ['pub.1', 'pub.2'], + }, { + manifest: 'expectedMainfest', + readme: 'expectedReadme', + changeLog: 'expectedChangelog', + download: 'expectedDownload', + icon: 'expectedIcon', + iconFallback: 'expectedIconFallback', + license: 'expectedLicense' + }); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(expected)); + testObject.queryGallery().done(pagedResponse => { + assert.equal(1, pagedResponse.firstPage.length); + const actual = pagedResponse.firstPage[0]; + + assert.equal(null, actual.type); + assert.equal('expectedName', actual.name); + assert.equal('expectedDisplayName', actual.displayName); + assert.equal('expectedPublisher.expectedName', actual.identifier); + assert.equal('expectedPublisher', actual.publisher); + assert.equal('expectedPublisherDisplayName', actual.publisherDisplayName); + assert.equal('1.5', actual.version); + assert.equal('1.5', actual.latestVersion); + assert.equal('expectedDescription', actual.description); + assert.equal('expectedIcon', actual.iconUrl); + assert.equal('expectedIconFallback', actual.iconUrlFallback); + assert.equal('expectedLicense', actual.licenseUrl); + assert.equal(ExtensionState.Uninstalled, actual.state); + assert.equal(1000, actual.installCount); + assert.equal(4, actual.rating); + assert.equal(100, actual.ratingCount); + assert.equal(false, actual.outdated); + assert.deepEqual(['pub.1', 'pub.2'], actual.dependencies); + done(); + }); + }); + + test('test for empty installed extensions', () => { + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + assert.deepEqual([], testObject.local); + }); + + test('test for installed extensions', () => { + const expected1 = aLocalExtension('local1', { + publisher: 'localPublisher1', + version: '1.1', + displayName: 'localDisplayName1', + description: 'localDescription1', + icon: 'localIcon1', + extensionDependencies: ['pub.1', 'pub.2'], + }, { + type: LocalExtensionType.User, + readmeUrl: 'localReadmeUrl1', + changelogUrl: 'localChangelogUrl1', + path: 'localPath1' + }); + const expected2 = aLocalExtension('local2', { + publisher: 'localPublisher2', + version: '1.2', + displayName: 'localDisplayName2', + description: 'localDescription2', + }, { + type: LocalExtensionType.System, + readmeUrl: 'localReadmeUrl2', + changelogUrl: 'localChangelogUrl2', + }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [expected1, expected2]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + const actuals = testObject.local; + assert.equal(2, actuals.length); + + let actual = actuals[0]; + assert.equal(LocalExtensionType.User, actual.type); + assert.equal('local1', actual.name); + assert.equal('localDisplayName1', actual.displayName); + assert.equal('localPublisher1.local1', actual.identifier); + assert.equal('localPublisher1', actual.publisher); + assert.equal('1.1', actual.version); + assert.equal('1.1', actual.latestVersion); + assert.equal('localDescription1', actual.description); + assert.equal('file:///localPath1/localIcon1', actual.iconUrl); + assert.equal('file:///localPath1/localIcon1', actual.iconUrlFallback); + assert.equal(null, actual.licenseUrl); + assert.equal(ExtensionState.Installed, actual.state); + assert.equal(null, actual.installCount); + assert.equal(null, actual.rating); + assert.equal(null, actual.ratingCount); + assert.equal(false, actual.outdated); + assert.deepEqual(['pub.1', 'pub.2'], actual.dependencies); + + actual = actuals[1]; + assert.equal(LocalExtensionType.System, actual.type); + assert.equal('local2', actual.name); + assert.equal('localDisplayName2', actual.displayName); + assert.equal('localPublisher2.local2', actual.identifier); + assert.equal('localPublisher2', actual.publisher); + assert.equal('1.2', actual.version); + assert.equal('1.2', actual.latestVersion); + assert.equal('localDescription2', actual.description); + assert.ok(fs.existsSync(actual.iconUrl)); + assert.equal(null, actual.licenseUrl); + assert.equal(ExtensionState.Installed, actual.state); + assert.equal(null, actual.installCount); + assert.equal(null, actual.rating); + assert.equal(null, actual.ratingCount); + assert.equal(false, actual.outdated); + assert.deepEqual([], actual.dependencies); + }); + + test('test installed extensions get syncs with gallery', (done) => { + const local1 = aLocalExtension('local1', { + publisher: 'localPublisher1', + version: '1.1.0', + displayName: 'localDisplayName1', + description: 'localDescription1', + icon: 'localIcon1', + extensionDependencies: ['pub.1', 'pub.2'], + }, { + type: LocalExtensionType.User, + readmeUrl: 'localReadmeUrl1', + changelogUrl: 'localChangelogUrl1', + path: 'localPath1' + }); + const local2 = aLocalExtension('local2', { + publisher: 'localPublisher2', + version: '1.2.0', + displayName: 'localDisplayName2', + description: 'localDescription2', + }, { + type: LocalExtensionType.System, + readmeUrl: 'localReadmeUrl2', + changelogUrl: 'localChangelogUrl2', + }); + const gallery1 = aGalleryExtension('expectedName', { + id: local1.id, + displayName: 'expectedDisplayName', + version: '1.5.0', + publisherId: 'expectedPublisherId', + publisher: 'expectedPublisher', + publisherDisplayName: 'expectedPublisherDisplayName', + description: 'expectedDescription', + installCount: 1000, + rating: 4, + ratingCount: 100 + }, { + dependencies: ['pub.1'], + }, { + manifest: 'expectedMainfest', + readme: 'expectedReadme', + changeLog: 'expectedChangelog', + download: 'expectedDownload', + icon: 'expectedIcon', + iconFallback: 'expectedIconFallback', + license: 'expectedLicense' + }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local1, local2]); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery1)); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + listenAfter(testObject.onChange)(() => { + const actuals = testObject.local; + assert.equal(2, actuals.length); + + let actual = actuals[0]; + assert.equal(LocalExtensionType.User, actual.type); + assert.equal('local1', actual.name); + assert.equal('localDisplayName1', actual.displayName); + assert.equal('localPublisher1.local1', actual.identifier); + assert.equal('localPublisher1', actual.publisher); + assert.equal('1.1.0', actual.version); + assert.equal('1.5.0', actual.latestVersion); + assert.equal('localDescription1', actual.description); + assert.equal('file:///localPath1/localIcon1', actual.iconUrl); + assert.equal('file:///localPath1/localIcon1', actual.iconUrlFallback); + assert.equal(ExtensionState.Installed, actual.state); + assert.equal('expectedLicense', actual.licenseUrl); + assert.equal(1000, actual.installCount); + assert.equal(4, actual.rating); + assert.equal(100, actual.ratingCount); + assert.equal(true, actual.outdated); + assert.deepEqual(['pub.1', 'pub.2'], actual.dependencies); + + actual = actuals[1]; + assert.equal(LocalExtensionType.System, actual.type); + assert.equal('local2', actual.name); + assert.equal('localDisplayName2', actual.displayName); + assert.equal('localPublisher2.local2', actual.identifier); + assert.equal('localPublisher2', actual.publisher); + assert.equal('1.2.0', actual.version); + assert.equal('1.2.0', actual.latestVersion); + assert.equal('localDescription2', actual.description); + assert.ok(fs.existsSync(actual.iconUrl)); + assert.equal(null, actual.licenseUrl); + assert.equal(ExtensionState.Installed, actual.state); + assert.equal(null, actual.installCount); + assert.equal(null, actual.rating); + assert.equal(null, actual.ratingCount); + assert.equal(false, actual.outdated); + assert.deepEqual([], actual.dependencies); + done(); + }); + }); + + test('test extension state computation', (done) => { + const gallery = aGalleryExtension('gallery1'); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + testObject.queryGallery().done(page => { + const extension = page.firstPage[0]; + assert.equal(ExtensionState.Uninstalled, extension.state); + + testObject.install(extension); + + // Installing + installEvent.fire({ id: gallery.id, gallery }); + let local = testObject.local; + assert.equal(1, local.length); + const actual = local[0]; + assert.equal(`${gallery.publisher}.${gallery.name}`, actual.identifier); + assert.equal(ExtensionState.Installing, actual.state); + + // Installed + didInstallEvent.fire({ id: gallery.id, gallery, local: aLocalExtension(gallery.name, gallery, gallery) }); + assert.equal(ExtensionState.Installed, actual.state); + assert.equal(1, testObject.local.length); + + testObject.uninstall(actual); + + // Uninstalling + uninstallEvent.fire(gallery.id); + assert.equal(ExtensionState.Uninstalling, actual.state); + + // Uninstalled + didUninstallEvent.fire({ id: gallery.id }); + assert.equal(ExtensionState.Uninstalled, actual.state); + + assert.equal(0, testObject.local.length); + done(); + }); + }); + + test('test extension doesnot show outdated for system extensions', () => { + const local = aLocalExtension('a', { version: '1.0.1' }, { type: LocalExtensionType.System }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension(local.manifest.name, { id: local.id, version: '1.0.2' }))); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + assert.ok(!testObject.local[0].outdated); + }); + + test('test canInstall returns false for extensions with out gallery', () => { + const local = aLocalExtension('a', { version: '1.0.1' }, { type: LocalExtensionType.System }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + const target = testObject.local[0]; + testObject.uninstall(target); + uninstallEvent.fire(local.id); + didUninstallEvent.fire({ id: local.id }); + + assert.ok(!testObject.canInstall(target)); + }); + + test('test canInstall returns true for extensions with gallery', (done) => { + const local = aLocalExtension('a', { version: '1.0.1' }, { type: LocalExtensionType.System }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension(local.manifest.name, { id: local.id }))); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + const target = testObject.local[0]; + + listenAfter(testObject.onChange)(() => { + assert.ok(testObject.canInstall(target)); + done(); + }); + }); + + test('test onchange event is triggered while installing', (done) => { + const gallery = aGalleryExtension('gallery1'); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + const target = sinon.spy(); + + testObject.queryGallery().done(page => { + const extension = page.firstPage[0]; + assert.equal(ExtensionState.Uninstalled, extension.state); + + testObject.install(extension); + installEvent.fire({ id: gallery.id, gallery }); + testObject.onChange(target); + + // Installed + didInstallEvent.fire({ id: gallery.id, gallery, local: aLocalExtension(gallery.name, gallery, gallery) }); + + assert.ok(target.calledOnce); + done(); + }); + }); + + test('test onchange event is triggered when installation is finished', (done) => { + const gallery = aGalleryExtension('gallery1'); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + const target = sinon.spy(); + + testObject.queryGallery().done(page => { + const extension = page.firstPage[0]; + assert.equal(ExtensionState.Uninstalled, extension.state); + + testObject.install(extension); + testObject.onChange(target); + + // Installing + installEvent.fire({ id: gallery.id, gallery }); + + assert.ok(target.calledOnce); + done(); + }); + }); + + test('test onchange event is triggered while uninstalling', () => { + const local = aLocalExtension('a', {}, { type: LocalExtensionType.System }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + const target = sinon.spy(); + + testObject.uninstall(testObject.local[0]); + testObject.onChange(target); + uninstallEvent.fire(local.id); + + assert.ok(target.calledOnce); + }); + + test('test onchange event is triggered when uninstalling is finished', () => { + const local = aLocalExtension('a', {}, { type: LocalExtensionType.System }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + const target = sinon.spy(); + + testObject.uninstall(testObject.local[0]); + uninstallEvent.fire(local.id); + testObject.onChange(target); + didUninstallEvent.fire({ id: local.id }); + + assert.ok(target.calledOnce); + }); + + test('test extension dependencies when empty', (done) => { + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a'))); + + testObject.queryGallery().done(page => { + testObject.loadDependencies(page.firstPage[0]).done(dependencies => { + assert.equal(null, dependencies); + done(); + }); + }); + }); + + test('test one level extension dependencies without cycle', (done) => { + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.c', 'pub.d'] }))); + instantiationService.stubPromise(IExtensionGalleryService, 'getAllDependencies', [aGalleryExtension('b'), aGalleryExtension('c'), aGalleryExtension('d')]); + + testObject.queryGallery().done(page => { + const extension = page.firstPage[0]; + testObject.loadDependencies(extension).done(actual => { + assert.ok(actual.hasDependencies); + assert.equal(extension, actual.extension); + assert.equal(null, actual.dependent); + assert.equal(3, actual.dependencies.length); + assert.equal('pub.a', actual.identifier); + let dependent = actual; + + actual = dependent.dependencies[0]; + assert.ok(!actual.hasDependencies); + assert.equal('pub.b', actual.extension.identifier); + assert.equal('pub.b', actual.identifier); + assert.equal(dependent, actual.dependent); + assert.equal(0, actual.dependencies.length); + + actual = dependent.dependencies[1]; + assert.ok(!actual.hasDependencies); + assert.equal('pub.c', actual.extension.identifier); + assert.equal('pub.c', actual.identifier); + assert.equal(dependent, actual.dependent); + assert.equal(0, actual.dependencies.length); + + actual = dependent.dependencies[2]; + assert.ok(!actual.hasDependencies); + assert.equal('pub.d', actual.extension.identifier); + assert.equal('pub.d', actual.identifier); + assert.equal(dependent, actual.dependent); + assert.equal(0, actual.dependencies.length); + done(); + }); + }); + }); + + test('test one level extension dependencies with cycle', (done) => { + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.a'] }))); + instantiationService.stubPromise(IExtensionGalleryService, 'getAllDependencies', [aGalleryExtension('b'), aGalleryExtension('a')]); + + testObject.queryGallery().done(page => { + const extension = page.firstPage[0]; + testObject.loadDependencies(extension).done(actual => { + assert.ok(actual.hasDependencies); + assert.equal(extension, actual.extension); + assert.equal(null, actual.dependent); + assert.equal(2, actual.dependencies.length); + assert.equal('pub.a', actual.identifier); + let dependent = actual; + + actual = dependent.dependencies[0]; + assert.ok(!actual.hasDependencies); + assert.equal('pub.b', actual.extension.identifier); + assert.equal('pub.b', actual.identifier); + assert.equal(dependent, actual.dependent); + assert.equal(0, actual.dependencies.length); + + actual = dependent.dependencies[1]; + assert.ok(!actual.hasDependencies); + assert.equal('pub.a', actual.extension.identifier); + assert.equal('pub.a', actual.identifier); + assert.equal(dependent, actual.dependent); + assert.equal(0, actual.dependencies.length); + + done(); + }); + }); + }); + + test('test one level extension dependencies with missing dependencies', (done) => { + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.a'] }))); + instantiationService.stubPromise(IExtensionGalleryService, 'getAllDependencies', [aGalleryExtension('a')]); + + testObject.queryGallery().done(page => { + const extension = page.firstPage[0]; + testObject.loadDependencies(extension).done(actual => { + assert.ok(actual.hasDependencies); + assert.equal(extension, actual.extension); + assert.equal(null, actual.dependent); + assert.equal(2, actual.dependencies.length); + assert.equal('pub.a', actual.identifier); + let dependent = actual; + + actual = dependent.dependencies[0]; + assert.ok(!actual.hasDependencies); + assert.equal(null, actual.extension); + assert.equal('pub.b', actual.identifier); + assert.equal(dependent, actual.dependent); + assert.equal(0, actual.dependencies.length); + + actual = dependent.dependencies[1]; + assert.ok(!actual.hasDependencies); + assert.equal('pub.a', actual.extension.identifier); + assert.equal('pub.a', actual.identifier); + assert.equal(dependent, actual.dependent); + assert.equal(0, actual.dependencies.length); + + done(); + }); + }); + }); + + test('test one level extension dependencies with in built dependencies', (done) => { + const local = aLocalExtension('inbuilt', {}, { type: LocalExtensionType.System }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.inbuilt', 'pub.a'] }))); + instantiationService.stubPromise(IExtensionGalleryService, 'getAllDependencies', [aGalleryExtension('a')]); + + testObject.queryGallery().done(page => { + const extension = page.firstPage[0]; + testObject.loadDependencies(extension).done(actual => { + assert.ok(actual.hasDependencies); + assert.equal(extension, actual.extension); + assert.equal(null, actual.dependent); + assert.equal(2, actual.dependencies.length); + assert.equal('pub.a', actual.identifier); + let dependent = actual; + + actual = dependent.dependencies[0]; + assert.ok(!actual.hasDependencies); + assert.equal('pub.inbuilt', actual.extension.identifier); + assert.equal('pub.inbuilt', actual.identifier); + assert.equal(dependent, actual.dependent); + assert.equal(0, actual.dependencies.length); + + + actual = dependent.dependencies[1]; + assert.ok(!actual.hasDependencies); + assert.equal('pub.a', actual.extension.identifier); + assert.equal('pub.a', actual.identifier); + assert.equal(dependent, actual.dependent); + assert.equal(0, actual.dependencies.length); + + done(); + }); + }); + }); + + test('test more than one level of extension dependencies', (done) => { + const local = aLocalExtension('c', { extensionDependencies: ['pub.d'] }, { type: LocalExtensionType.System }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.c'] }))); + instantiationService.stubPromise(IExtensionGalleryService, 'getAllDependencies', [ + aGalleryExtension('b', {}, { dependencies: ['pub.d', 'pub.e'] }), + aGalleryExtension('d', {}, { dependencies: ['pub.f', 'pub.c'] }), + aGalleryExtension('e')]); + + testObject.queryGallery().done(page => { + const extension = page.firstPage[0]; + testObject.loadDependencies(extension).done(a => { + assert.ok(a.hasDependencies); + assert.equal(extension, a.extension); + assert.equal(null, a.dependent); + assert.equal(2, a.dependencies.length); + assert.equal('pub.a', a.identifier); + + let b = a.dependencies[0]; + assert.ok(b.hasDependencies); + assert.equal('pub.b', b.extension.identifier); + assert.equal('pub.b', b.identifier); + assert.equal(a, b.dependent); + assert.equal(2, b.dependencies.length); + + let c = a.dependencies[1]; + assert.ok(c.hasDependencies); + assert.equal('pub.c', c.extension.identifier); + assert.equal('pub.c', c.identifier); + assert.equal(a, c.dependent); + assert.equal(1, c.dependencies.length); + + let d = b.dependencies[0]; + assert.ok(d.hasDependencies); + assert.equal('pub.d', d.extension.identifier); + assert.equal('pub.d', d.identifier); + assert.equal(b, d.dependent); + assert.equal(2, d.dependencies.length); + + let e = b.dependencies[1]; + assert.ok(!e.hasDependencies); + assert.equal('pub.e', e.extension.identifier); + assert.equal('pub.e', e.identifier); + assert.equal(b, e.dependent); + assert.equal(0, e.dependencies.length); + + let f = d.dependencies[0]; + assert.ok(!f.hasDependencies); + assert.equal(null, f.extension); + assert.equal('pub.f', f.identifier); + assert.equal(d, f.dependent); + assert.equal(0, f.dependencies.length); + + c = d.dependencies[1]; + assert.ok(c.hasDependencies); + assert.equal('pub.c', c.extension.identifier); + assert.equal('pub.c', c.identifier); + assert.equal(d, c.dependent); + assert.equal(1, c.dependencies.length); + + d = c.dependencies[0]; + assert.ok(!d.hasDependencies); + assert.equal('pub.d', d.extension.identifier); + assert.equal('pub.d', d.identifier); + assert.equal(c, d.dependent); + assert.equal(0, d.dependencies.length); + + c = a.dependencies[1]; + d = c.dependencies[0]; + assert.ok(d.hasDependencies); + assert.equal('pub.d', d.extension.identifier); + assert.equal('pub.d', d.identifier); + assert.equal(c, d.dependent); + assert.equal(2, d.dependencies.length); + + f = d.dependencies[0]; + assert.ok(!f.hasDependencies); + assert.equal(null, f.extension); + assert.equal('pub.f', f.identifier); + assert.equal(d, f.dependent); + assert.equal(0, f.dependencies.length); + + c = d.dependencies[1]; + assert.ok(!c.hasDependencies); + assert.equal('pub.c', c.extension.identifier); + assert.equal('pub.c', c.identifier); + assert.equal(d, c.dependent); + assert.equal(0, c.dependencies.length); + + done(); + }); + }); + }); + + test('test disabled flags are false for uninstalled extension', (done) => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', false, true); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a'))); + testObject.queryGallery().done(pagedResponse => { + const actual = pagedResponse.firstPage[0]; + + assert.ok(!actual.disabledForWorkspace); + assert.ok(!actual.disabledGlobally); + done(); + }); + + }); + + test('test disabled flags are false for installed enabled extension', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', false, true); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + const actual = testObject.local[0]; + + assert.ok(!actual.disabledForWorkspace); + assert.ok(!actual.disabledGlobally); + }); + + test('test disabled for workspace is set', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.d', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false, true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.e', false, true); + + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + const actual = testObject.local[0]; + + assert.ok(actual.disabledForWorkspace); + assert.ok(!actual.disabledGlobally); + }); + + test('test disabled globally is set', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.d', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', false, true); + + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + const actual = testObject.local[0]; + + assert.ok(!actual.disabledForWorkspace); + assert.ok(actual.disabledGlobally); + }); + + test('test disable flags are updated for user extensions', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', false, true); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + testObject.setEnablement(testObject.local[0], false, true); + const actual = testObject.local[0]; + + assert.ok(actual.disabledForWorkspace); + assert.ok(!actual.disabledGlobally); + }); + + test('test enable extension globally when extension is disabled for workspace', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false, true); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + testObject.setEnablement(testObject.local[0], true); + const actual = testObject.local[0]; + + assert.ok(!actual.disabledForWorkspace); + assert.ok(!actual.disabledGlobally); + }); + + test('test disable extension globally should not disable for workspace', () => { + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + testObject.setEnablement(testObject.local[0], false); + const actual = testObject.local[0]; + + assert.ok(!actual.disabledForWorkspace); + assert.ok(actual.disabledGlobally); + }); + + test('test disabled flags are not updated for system extensions', () => { + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', {}, { type: LocalExtensionType.System })]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + testObject.setEnablement(testObject.local[0], false); + const actual = testObject.local[0]; + + assert.ok(!actual.disabledForWorkspace); + assert.ok(!actual.disabledGlobally); + }); + + test('test disabled flags are updated on change from outside', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', false, true); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + const actual = testObject.local[0]; + + assert.ok(!actual.disabledForWorkspace); + assert.ok(actual.disabledGlobally); + }); + + test('test change event is fired when disablement flags are changed', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', false, true); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + const target = sinon.spy(); + testObject.onChange(target); + + testObject.setEnablement(testObject.local[0], false); + + assert.ok(target.calledOnce); + }); + + test('test change event is fired when disablement flags are changed from outside', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', false, true); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + const target = sinon.spy(); + testObject.onChange(target); + + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + + assert.ok(target.calledOnce); + }); + + function aLocalExtension(name: string = 'someext', manifest: any = {}, properties: any = {}): ILocalExtension { + const localExtension = Object.create({ manifest: {} }); + assign(localExtension, { type: LocalExtensionType.User, id: generateUuid() }, properties); + assign(localExtension.manifest, { name, publisher: 'pub' }, manifest); + localExtension.metadata = { id: localExtension.id, publisherId: localExtension.manifest.publisher, publisherDisplayName: 'somename' }; + return localExtension; + } + + function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: any = {}): IGalleryExtension { + const galleryExtension = Object.create({}); + assign(galleryExtension, { name, publisher: 'pub', id: generateUuid(), properties: {}, assets: {} }, properties); + assign(galleryExtension.properties, { dependencies: [] }, galleryExtensionProperties); + assign(galleryExtension.assets, assets); + return galleryExtension; + } + + function aPage(...objects: T[]): IPager { + return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null }; + } + + function listenAfter(event: Event, count: number = 1): Event { + let counter = 0; + const emitter = new Emitter(); + event(() => { + if (++counter === count) { + emitter.fire(); + emitter.dispose(); + } + }); + return emitter.event; + } +}); \ No newline at end of file diff --git a/src/vs/workbench/parts/files/electron-browser/dirtyFilesTracker.ts b/src/vs/workbench/parts/files/electron-browser/dirtyFilesTracker.ts index 88443df9bde..8b17bef1205 100644 --- a/src/vs/workbench/parts/files/electron-browser/dirtyFilesTracker.ts +++ b/src/vs/workbench/parts/files/electron-browser/dirtyFilesTracker.ts @@ -11,8 +11,8 @@ 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 { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; import { Position } from 'vs/platform/editor/common/editor'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { IEditorStacksModel } from 'vs/workbench/common/editor'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -23,8 +23,6 @@ import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/co import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import arrays = require('vs/base/common/arrays'); -import { ipcRenderer as ipc } from 'electron'; - export class DirtyFilesTracker implements IWorkbenchContribution { private isDocumentedEdited: boolean; private toUnbind: IDisposable[]; @@ -41,7 +39,7 @@ export class DirtyFilesTracker implements IWorkbenchContribution { @IEditorGroupService editorGroupService: IEditorGroupService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IActivityService private activityService: IActivityService, - @IWindowIPCService private windowService: IWindowIPCService, + @IWindowService private windowService: IWindowService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService ) { this.toUnbind = []; @@ -161,7 +159,7 @@ export class DirtyFilesTracker implements IWorkbenchContribution { const hasDirtyFiles = this.textFileService.isDirty(); this.isDocumentedEdited = hasDirtyFiles; - ipc.send('vscode:setDocumentEdited', this.windowService.getWindowId(), hasDirtyFiles); // handled from browser process + this.windowService.setDocumentEdited(hasDirtyFiles); } } diff --git a/src/vs/workbench/parts/files/electron-browser/electronFileActions.ts b/src/vs/workbench/parts/files/electron-browser/electronFileActions.ts index d973c906835..444981345a3 100644 --- a/src/vs/workbench/parts/files/electron-browser/electronFileActions.ts +++ b/src/vs/workbench/parts/files/electron-browser/electronFileActions.ts @@ -17,17 +17,15 @@ 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 { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService'; -import { IWindowService } from 'vs/platform/windows/common/windows'; - -import { ipcRenderer as ipc, clipboard } from 'electron'; +import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; +import { clipboard } from 'electron'; export class RevealInOSAction extends Action { private resource: uri; constructor( resource: uri, - @IWindowIPCService private windowService: IWindowIPCService + @IWindowsService private windowsService: IWindowsService ) { 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"))); @@ -37,7 +35,7 @@ export class RevealInOSAction extends Action { } public run(): TPromise { - this.windowService.getWindow().showItemInFolder(paths.normalize(this.resource.fsPath, true)); + this.windowsService.showItemInFolder(paths.normalize(this.resource.fsPath, true)); return TPromise.as(true); } @@ -52,7 +50,7 @@ export class GlobalRevealInOSAction extends Action { id: string, label: string, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IWindowIPCService private windowService: IWindowIPCService, + @IWindowsService private windowsService: IWindowsService, @IMessageService private messageService: IMessageService ) { super(id, label); @@ -61,7 +59,7 @@ export class GlobalRevealInOSAction extends Action { public run(): TPromise { const fileInput = asFileEditorInput(this.editorService.getActiveEditorInput(), true); if (fileInput) { - this.windowService.getWindow().showItemInFolder(paths.normalize(fileInput.getResource().fsPath, true)); + this.windowsService.showItemInFolder(paths.normalize(fileInput.getResource().fsPath, true)); } else { this.messageService.show(severity.Info, nls.localize('openFileToReveal', "Open a file first to reveal")); } @@ -187,6 +185,7 @@ export class ShowOpenedFileInNewWindow extends Action { constructor( id: string, label: string, + @IWindowsService private windowsService: IWindowsService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IMessageService private messageService: IMessageService ) { @@ -196,7 +195,7 @@ export class ShowOpenedFileInNewWindow extends Action { public run(): TPromise { const fileInput = asFileEditorInput(this.editorService.getActiveEditorInput(), true); if (fileInput) { - ipc.send('vscode:windowOpen', [fileInput.getResource().fsPath], true /* force new window */); // handled from browser process + this.windowsService.windowOpen([fileInput.getResource().fsPath], true); } else { this.messageService.show(severity.Info, nls.localize('openFileToShow', "Open a file first to open in new window")); } 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 334f527af2b..b08311c24cc 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 @@ -21,7 +21,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti 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'; +import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; class FileViewerActionContributor extends ActionBarContributor { @@ -85,4 +85,9 @@ actionsRegistry.registerActionBarContributor(Scope.VIEWER, FileViewerActionContr CommandsRegistry.registerCommand('_files.openFolderPicker', (accessor: ServicesAccessor, forceNewWindow: boolean) => { const windowService = accessor.get(IWindowService); windowService.openFolderPicker(forceNewWindow); +}); + +CommandsRegistry.registerCommand('_files.windowOpen', (accessor: ServicesAccessor, paths: string[], forceNewWindow: boolean) => { + const windowsService = accessor.get(IWindowsService); + windowsService.windowOpen(paths, forceNewWindow); }); \ No newline at end of file diff --git a/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts b/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts index 7d1d3cedd35..1aa08b96c35 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts +++ b/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts @@ -19,8 +19,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { IModeService } from 'vs/editor/common/services/modeService'; - -import { ipcRenderer as ipc } from 'electron'; +import { IWindowsService } from 'vs/platform/windows/common/windows'; import fs = require('fs'); class OpenSnippetsAction extends actions.Action { @@ -33,13 +32,14 @@ class OpenSnippetsAction extends actions.Action { label: string, @IEnvironmentService private environmentService: IEnvironmentService, @IQuickOpenService private quickOpenService: IQuickOpenService, - @IModeService private modeService: IModeService + @IModeService private modeService: IModeService, + @IWindowsService private windowsService: IWindowsService ) { super(id, label); } - private openFile(filePath: string): void { - ipc.send('vscode:windowOpen', [filePath]); // handled from browser process + private openFile(filePath: string): winjs.TPromise { + return this.windowsService.windowOpen([filePath]); } public run(): winjs.Promise { @@ -60,8 +60,7 @@ class OpenSnippetsAction extends actions.Action { var snippetPath = paths.join(this.environmentService.appSettingsHome, 'snippets', language.id + '.json'); return fileExists(snippetPath).then((success) => { if (success) { - this.openFile(snippetPath); - return winjs.TPromise.as(null); + return this.openFile(snippetPath); } var defaultContent = [ '{', @@ -82,7 +81,7 @@ class OpenSnippetsAction extends actions.Action { '}' ].join('\n'); return createFile(snippetPath, defaultContent).then(() => { - this.openFile(snippetPath); + return this.openFile(snippetPath); }, (err) => { errors.onUnexpectedError(nls.localize('openSnippet.errorOnCreate', 'Unable to create {0}', snippetPath)); });