diff --git a/README.md b/README.md index 91566a493db6..5d5ee55c63af 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ a code editor with what developers need for their core edit-build-debug cycle. Code provides comprehensive editing and debugging support, an extensibility model, and lightweight integration with existing tools. +VS Code is updated monthly with new features and bug fixes. You can download it for Windows, Mac and Linux on [VS Code's website](https://code.visualstudio.com/Download). To get the latest releases everyday, you can install the [Insiders version of VS Code](https://code.visualstudio.com/insiders). This builds from the master branch and is updated at least daily. +

VS Code in action

diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index e0359c5d0ebb..5ecb9a1f315d 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -39,7 +39,7 @@ const nodeModules = ['electron', 'original-fs'] // Build const builtInExtensions = [ - { name: 'ms-vscode.node-debug', version: '1.9.0' }, + { name: 'ms-vscode.node-debug', version: '1.9.1' }, { name: 'ms-vscode.node-debug2', version: '1.9.1' } ]; @@ -110,7 +110,7 @@ const config = { version: packageJson.electronVersion, productAppName: product.nameLong, companyName: 'Microsoft Corporation', - copyright: 'Copyright (C) 2016 Microsoft. All rights reserved', + copyright: 'Copyright (C) 2017 Microsoft. All rights reserved', darwinIcon: 'resources/darwin/code.icns', darwinBundleIdentifier: product.darwinBundleIdentifier, darwinApplicationCategoryType: 'public.app-category.developer-tools', diff --git a/build/monaco/api.js b/build/monaco/api.js index 4cab80a45a9f..469b19bd79d5 100644 --- a/build/monaco/api.js +++ b/build/monaco/api.js @@ -188,21 +188,21 @@ function format(text) { } function getDefaultOptions() { return { - IndentSize: 4, - TabSize: 4, - NewLineCharacter: '\r\n', - ConvertTabsToSpaces: true, - IndentStyle: ts.IndentStyle.Block, - InsertSpaceAfterCommaDelimiter: true, - InsertSpaceAfterSemicolonInForStatements: true, - InsertSpaceBeforeAndAfterBinaryOperators: true, - InsertSpaceAfterKeywordsInControlFlowStatements: true, - InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false, - InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, - InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, - InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: true, - PlaceOpenBraceOnNewLineForFunctions: false, - PlaceOpenBraceOnNewLineForControlBlocks: false, + indentSize: 4, + tabSize: 4, + newLineCharacter: '\r\n', + convertTabsToSpaces: true, + indentStyle: ts.IndentStyle.Block, + insertSpaceAfterCommaDelimiter: true, + insertSpaceAfterSemicolonInForStatements: true, + insertSpaceBeforeAndAfterBinaryOperators: true, + insertSpaceAfterKeywordsInControlFlowStatements: true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: true, + placeOpenBraceOnNewLineForFunctions: false, + placeOpenBraceOnNewLineForControlBlocks: false, }; } } diff --git a/build/monaco/api.ts b/build/monaco/api.ts index 2ca2968c6c30..b399d1b1d81d 100644 --- a/build/monaco/api.ts +++ b/build/monaco/api.ts @@ -217,22 +217,22 @@ function format(text:string): string { function getDefaultOptions(): ts.FormatCodeOptions { return { - IndentSize: 4, - TabSize: 4, - NewLineCharacter: '\r\n', - ConvertTabsToSpaces: true, - IndentStyle: ts.IndentStyle.Block, + indentSize: 4, + tabSize: 4, + newLineCharacter: '\r\n', + convertTabsToSpaces: true, + indentStyle: ts.IndentStyle.Block, - InsertSpaceAfterCommaDelimiter: true, - InsertSpaceAfterSemicolonInForStatements: true, - InsertSpaceBeforeAndAfterBinaryOperators: true, - InsertSpaceAfterKeywordsInControlFlowStatements: true, - InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false, - InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, - InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, - InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: true, - PlaceOpenBraceOnNewLineForFunctions: false, - PlaceOpenBraceOnNewLineForControlBlocks: false, + insertSpaceAfterCommaDelimiter: true, + insertSpaceAfterSemicolonInForStatements: true, + insertSpaceBeforeAndAfterBinaryOperators: true, + insertSpaceAfterKeywordsInControlFlowStatements: true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: true, + placeOpenBraceOnNewLineForFunctions: false, + placeOpenBraceOnNewLineForControlBlocks: false, }; } } diff --git a/extensions/markdown/syntaxes/markdown.tmLanguage b/extensions/markdown/syntaxes/markdown.tmLanguage index 031589c965ac..238afa870ec4 100644 --- a/extensions/markdown/syntaxes/markdown.tmLanguage +++ b/extensions/markdown/syntaxes/markdown.tmLanguage @@ -320,7 +320,29 @@ begin - (^|\G)\s*(?=</?(address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|pre|p|param|script|section|source|style|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(\s|$|/?>)) + (^|\G)\s*(?=<(script|style|pre)(\s|$|>)) + patterns + + + begin + (\s*|$) + patterns + + + include + text.html.basic + + + while + ^\s*(?!</(script|style|pre)>) + + + end + (?=</(script|style|pre)>) + + + begin + (^|\G)\s*(?=</?(address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(\s|$|/?>)) patterns @@ -333,7 +355,7 @@ begin - (^|\G)\s*(?=(<[a-zA-Z0-9\-].*>|</[a-zA-Z0-9\-]>)\s*$) + (^|\G)\s*(?=(<[a-zA-Z0-9\-](/?>|\s.*?>)|</[a-zA-Z0-9\-]>)\s*$) patterns @@ -546,15 +568,6 @@ (^|\G)(?!\s*$|#|[ ]{0,3}((([*_][ ]{0,2}\2){2,}[ \t]*$\n?)|([*+-]([ ]{1,3}|\t)))|\s*\[.+?\]:|>) - - - - - - - - - fenced_code_block_css begin @@ -589,7 +602,6 @@ punctuation.definition.markdown - patterns @@ -641,7 +653,6 @@ punctuation.definition.markdown - patterns diff --git a/extensions/typescript/src/features/codeActionProvider.ts b/extensions/typescript/src/features/codeActionProvider.ts index e0ff2b694124..51a9aed5a67c 100644 --- a/extensions/typescript/src/features/codeActionProvider.ts +++ b/extensions/typescript/src/features/codeActionProvider.ts @@ -5,7 +5,7 @@ 'use strict'; -import { CodeActionProvider, TextDocument, Range, CancellationToken, CodeActionContext, Command, commands, Uri, workspace, WorkspaceEdit, TextEdit } from 'vscode'; +import { CodeActionProvider, TextDocument, Range, CancellationToken, CodeActionContext, Command, commands, Uri, workspace, WorkspaceEdit, TextEdit, Position } from 'vscode'; import * as Proto from '../protocol'; import { ITypescriptServiceClient } from '../typescriptService'; @@ -46,7 +46,7 @@ export default class TypeScriptCodeActionProvider implements CodeActionProvider public provideCodeActions(document: TextDocument, range: Range, context: CodeActionContext, token: CancellationToken): Thenable { const file = this.client.asAbsolutePath(document.uri); if (!file) { - return Promise.resolve(null); + return Promise.resolve([]); } const source: Source = { @@ -99,11 +99,29 @@ export default class TypeScriptCodeActionProvider implements CodeActionProvider private onCodeAction(source: Source, workspaceEdit: WorkspaceEdit) { workspace.applyEdit(workspaceEdit).then(success => { if (!success) { - return Promise.reject(null); + return Promise.reject(false); } + + let firstEdit: TextEdit | null = null; + for (const [uri, edits] of workspaceEdit.entries()) { + if (uri.fsPath === source.uri.fsPath) { + firstEdit = edits[0]; + break; + } + } + + if (!firstEdit) { + return true; + } + + const newLines = firstEdit.newText.match(/\n/g); + const editedRange = new Range( + new Position(firstEdit.range.start.line, 0), + new Position(firstEdit.range.end.line + 1 + (newLines ? newLines.length : 0), 0)); + // TODO: Workaround for https://github.com/Microsoft/TypeScript/issues/12249 // apply formatting to the source range until TS returns formatted results - return commands.executeCommand('vscode.executeFormatRangeProvider', source.uri, source.range, {}).then((edits: TextEdit[]) => { + return commands.executeCommand('vscode.executeFormatRangeProvider', source.uri, editedRange, {}).then((edits: TextEdit[]) => { if (!edits || !edits.length) { return false; } diff --git a/extensions/typescript/src/features/completionItemProvider.ts b/extensions/typescript/src/features/completionItemProvider.ts index 65a725816fae..71cb92ffd940 100644 --- a/extensions/typescript/src/features/completionItemProvider.ts +++ b/extensions/typescript/src/features/completionItemProvider.ts @@ -119,7 +119,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP public provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken): Promise { if (this.typingsStatus.isAcquiringTypings) { - return Promise.reject({ + return Promise.reject({ label: localize('acquiringTypingsLabel', 'Acquiring typings...'), detail: localize('acquiringTypingsDetail', 'Acquiring typings definitions for IntelliSense.') }); @@ -220,7 +220,6 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP // Don't complete function calls inside of destructive assigments or imports return this.client.execute('quickinfo', args).then(infoResponse => { const info = infoResponse.body; - console.log(info && info.kind); switch (info && info.kind) { case 'var': case 'let': diff --git a/extensions/typescript/src/typescriptServiceClient.ts b/extensions/typescript/src/typescriptServiceClient.ts index ad18e62f4c36..12e36d958e9f 100644 --- a/extensions/typescript/src/typescriptServiceClient.ts +++ b/extensions/typescript/src/typescriptServiceClient.ts @@ -346,17 +346,20 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient switch (selected.id) { case MessageAction.useLocal: let pathValue = './node_modules/typescript/lib'; - tsConfig.update('tsdk', pathValue, false); - window.showInformationMessage(localize('updatedtsdk', 'Updated workspace setting \'typescript.tsdk\' to {0}', pathValue)); + tsConfig.update('tsdk', pathValue, false).then( + () => window.showInformationMessage(localize('updatedtsdk', 'Updated workspace setting \'typescript.tsdk\' to {0}', pathValue)), + () => window.showErrorMessage(localize('updateTsdkFailed', 'Could not update the \'typescript.tsdk\' workspace setting. Please check that the workspace settings file is valid'))); showVersionStatusItem = true; return localModulePath; case MessageAction.useBundled: - tsConfig.update(checkWorkspaceVersionKey, false, false); - window.showInformationMessage(localize('updateLocalWorkspaceCheck', 'Updated workspace setting \'typescript.check.workspaceVersion\' to false')); + tsConfig.update(checkWorkspaceVersionKey, false, false).then( + () => window.showInformationMessage(localize('updateLocalWorkspaceCheck', 'Updated workspace setting \'typescript.check.workspaceVersion\' to false')), + () => window.showErrorMessage(localize('updateLocalWorkspaceCheckFailed', 'Could not update the \'typescript.check.workspaceVersion\' workspace setting. Please check that the workspace settings file is valid'))); return modulePath; case MessageAction.neverCheckLocalVersion: - window.showInformationMessage(localize('updateGlobalWorkspaceCheck', 'Updated user setting \'typescript.check.workspaceVersion\' to false')); - tsConfig.update(checkWorkspaceVersionKey, false, true); + tsConfig.update(checkWorkspaceVersionKey, false, true).then( + () => window.showInformationMessage(localize('updateGlobalWorkspaceCheck', 'Updated user setting \'typescript.check.workspaceVersion\' to false')), + () => window.showErrorMessage(localize('updateGlobalWorkspaceCheckFailed', 'Could not update \'typescript.check.workspaceVersion\' user setting. Please check that your user settings file is valid'))); return modulePath; default: return modulePath; diff --git a/package.json b/package.json index d099f65135a3..84846e8827e3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "code-oss-dev", "version": "1.9.0", "electronVersion": "1.4.6", - "distro": "2eecc8b68318fba1fc5b62930287914ac9225e8a", + "distro": "ef07477c3bbf2aa2f274b13093cbe0d96fa59fdd", "author": { "name": "Microsoft Corporation" }, @@ -48,6 +48,7 @@ "eslint": "^3.4.0", "event-stream": "^3.1.7", "express": "^4.13.1", + "flatpak-bundler": "^0.1.1", "ghooks": "1.0.3", "glob": "^5.0.13", "gulp": "^3.8.9", @@ -59,6 +60,7 @@ "gulp-cssnano": "^2.1.0", "gulp-filter": "^3.0.0", "gulp-flatmap": "^1.0.0", + "gulp-image-resize": "^0.10.0", "gulp-json-editor": "^2.2.1", "gulp-mocha": "^2.1.3", "gulp-remote-src": "^0.4.0", @@ -71,8 +73,6 @@ "gulp-uglify": "^2.0.0", "gulp-util": "^3.0.6", "gulp-vinyl-zip": "^1.2.2", - "gulp-image-resize": "^0.10.0", - "flatpak-bundler": "^0.1.1", "innosetup-compiler": "^5.5.60", "is": "^3.1.0", "istanbul": "^0.3.17", @@ -91,8 +91,8 @@ "sinon": "^1.17.2", "source-map": "^0.4.4", "tslint": "^3.3.0", - "typescript": "2.0.3", - "typescript-formatter": "3.1.0", + "typescript": "^2.1.4", + "typescript-formatter": "4.0.1", "uglify-js": "2.4.8", "underscore": "^1.8.2", "vinyl": "^0.4.5", @@ -116,4 +116,4 @@ "windows-mutex": "^0.2.0", "fsevents": "0.3.8" } -} +} \ No newline at end of file diff --git a/resources/linux/code.desktop b/resources/linux/code.desktop index 80d41601b7bb..47ed1616faa8 100644 --- a/resources/linux/code.desktop +++ b/resources/linux/code.desktop @@ -2,7 +2,7 @@ Name=@@NAME_LONG@@ Comment=Code Editing. Redefined. GenericName=Text Editor -Exec=/usr/share/@@NAME@@/@@NAME@@ %U +Exec=/usr/share/@@NAME@@/@@NAME@@ --new-window-if-not-first %U Icon=@@NAME@@ Type=Application StartupNotify=true diff --git a/src/main.js b/src/main.js index 289e0e4fb154..d45cc975b8d5 100644 --- a/src/main.js +++ b/src/main.js @@ -180,6 +180,7 @@ var nodeCachedDataDir = getNodeCachedDataDir().then(function (value) { // Load our code once ready app.once('ready', function () { + global.perfAppReady = Date.now(); var nlsConfig = getNLSConfiguration(); process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig); diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index c5ceceb4cf42..2c36a2f80126 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -7,7 +7,7 @@ import 'vs/css!./list'; import { IDisposable } from 'vs/base/common/lifecycle'; import { range } from 'vs/base/common/arrays'; import { IDelegate, IRenderer, IFocusChangeEvent, ISelectionChangeEvent } from './list'; -import { List } from './listWidget'; +import { List, IListOptions } from './listWidget'; import { IPagedModel } from 'vs/base/common/paging'; import Event, { mapEvent } from 'vs/base/common/event'; @@ -67,10 +67,11 @@ export class PagedList { constructor( container: HTMLElement, delegate: IDelegate, - renderers: IPagedRenderer[] + renderers: IPagedRenderer[], + options: IListOptions = {} ) { const pagedRenderers = renderers.map(r => new PagedRenderer>(r, () => this.model)); - this.list = new List(container, delegate, pagedRenderers); + this.list = new List(container, delegate, pagedRenderers, options); } get onFocusChange(): Event> { diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index f05b962354a0..64ab0b7ed485 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -116,7 +116,7 @@ class FocusTrait extends Trait { renderElement(element: T, index: number, container: HTMLElement): void { super.renderElement(element, index, container); - container.setAttribute('role', 'option'); + container.setAttribute('role', 'treeitem'); container.setAttribute('id', this.getElementId(index)); } } @@ -201,6 +201,7 @@ class Controller implements IDisposable { } export interface IListOptions extends IListViewOptions { + ariaLabel?: string; } const DefaultOptions: IListOptions = {}; @@ -245,13 +246,17 @@ export class List implements IDisposable { }); this.view = new ListView(container, delegate, renderers, options); - this.view.domNode.setAttribute('role', 'listbox'); + this.view.domNode.setAttribute('role', 'tree'); this.view.domNode.tabIndex = 0; this.controller = new Controller(this, this.view); this.disposables = [this.focus, this.selection, this.view, this.controller]; this._onDOMFocus = domEvent(this.view.domNode, 'focus'); this.onFocusChange(this._onFocusChange, this, this.disposables); + + if (options.ariaLabel) { + this.view.domNode.setAttribute('aria-label', options.ariaLabel); + } } splice(start: number, deleteCount: number, ...elements: T[]): void { @@ -418,7 +423,16 @@ export class List implements IDisposable { } private _onFocusChange(): void { - DOM.toggleClass(this.view.domNode, 'element-focused', this.focus.get().length > 0); + const focus = this.focus.get(); + + if (focus.length > 0) { + this.view.domNode.setAttribute('aria-activedescendant', this.getElementId(focus[0])); + } else { + this.view.domNode.removeAttribute('aria-activedescendant'); + } + + this.view.domNode.setAttribute('role', 'tree'); + DOM.toggleClass(this.view.domNode, 'element-focused', focus.length > 0); } dispose(): void { diff --git a/src/vs/base/browser/ui/sash/sash.css b/src/vs/base/browser/ui/sash/sash.css index 5a2687d5055c..0c733246563c 100644 --- a/src/vs/base/browser/ui/sash/sash.css +++ b/src/vs/base/browser/ui/sash/sash.css @@ -25,22 +25,22 @@ cursor: default !important; } -.vertical-cursor-container * { +.vertical-cursor-container { cursor: ew-resize; } -.horizontal-cursor-container * { +.horizontal-cursor-container { cursor: ns-resize; } /** Custom Mac Cursor */ .monaco-sash.mac.vertical, -.vertical-cursor-container-mac * { +.vertical-cursor-container-mac { cursor: col-resize; } .monaco-sash.mac.horizontal, -.horizontal-cursor-container-mac * { +.horizontal-cursor-container-mac { cursor: row-resize; } \ No newline at end of file diff --git a/src/vs/base/browser/ui/timer/timer.css b/src/vs/base/browser/ui/timer/timer.css deleted file mode 100644 index 9f3515b0d95b..000000000000 --- a/src/vs/base/browser/ui/timer/timer.css +++ /dev/null @@ -1,29 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -.benchmarktimerbox { - z-index: 100; - position: absolute; - color: black; - background: lightblue; - top: 100px; - right: 20px; - font-family: monospace; -} - -.benchmarktimerbox .inner { - width: 600px; - height: 300px; - overflow: scroll; -} - -.benchmarktimerbox .timeFilter { - width: 50px; -} - -.benchmarktimerbox pre { - margin: 0; -} - -.timer-event-1 { background: rgba(190, 191, 193, 0.4); color: black; } \ No newline at end of file diff --git a/src/vs/base/browser/ui/timer/timer.ts b/src/vs/base/browser/ui/timer/timer.ts deleted file mode 100644 index 1b19e91e145c..000000000000 --- a/src/vs/base/browser/ui/timer/timer.ts +++ /dev/null @@ -1,281 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 'vs/css!./timer'; -import { TimeKeeper, ITimerEvent, getTimeKeeper } from 'vs/base/common/timer'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import DomUtils = require('vs/base/browser/dom'); - -export class TimeKeeperRenderer { - - private listenersToRemove: IDisposable[]; - private timeKeeper: TimeKeeper; - private outerDomNode: HTMLElement; - private domNode: HTMLElement; - private renderCnt: number; - private lastEventIndex: number; - - private textFilter: string; - private textFilterDomNode: HTMLInputElement; - private timeFilter: number; - private timeFilterDomNode: HTMLInputElement; - private intervalTokenId: number; - - private renderedEvents: { - [key: string]: ITimerEvent; - }; - - private onHide: () => void; - - constructor(onHide: () => void) { - this.timeKeeper = getTimeKeeper(); - this.onHide = onHide; - this.lastEventIndex = 0; - this.renderedEvents = {}; - this.renderCnt = 0; - this.listenersToRemove = []; - this.domNode = this._createDomNode(); - this.intervalTokenId = window.setInterval(() => this._render(), 500); - } - - public destroy(): void { - document.body.removeChild(this.outerDomNode); - window.clearInterval(this.intervalTokenId); - this.listenersToRemove = dispose(this.listenersToRemove); - } - - private _createDomNode(): HTMLElement { - this.outerDomNode = document.createElement('div'); - this.outerDomNode.className = 'benchmarktimerbox'; - - // Clear - let cancel: HTMLInputElement = document.createElement('input'); - cancel.type = 'button'; - cancel.value = 'Clear'; - this.listenersToRemove.push(DomUtils.addDisposableListener(cancel, 'click', () => this._onClear())); - this.outerDomNode.appendChild(cancel); - - // Text filter - this.textFilterDomNode = document.createElement('input'); - this.textFilterDomNode.type = 'text'; - this.textFilterDomNode.className = 'textFilter'; - this.listenersToRemove.push(DomUtils.addDisposableListener(this.textFilterDomNode, 'keydown', () => this.onTextFilterChange())); - this.textFilter = ''; - this.outerDomNode.appendChild(document.createTextNode('Filter')); - this.outerDomNode.appendChild(this.textFilterDomNode); - - // Time filter - this.timeFilterDomNode = document.createElement('input'); - this.timeFilterDomNode.type = 'text'; - this.timeFilterDomNode.value = '0'; - this.timeFilterDomNode.className = 'timeFilter'; - this.listenersToRemove.push(DomUtils.addDisposableListener(this.timeFilterDomNode, 'keydown', () => this.onTimeFilterChange())); - this.timeFilter = 0; - this.outerDomNode.appendChild(document.createTextNode('Hide time under')); - this.outerDomNode.appendChild(this.timeFilterDomNode); - - let hide: HTMLInputElement = document.createElement('input'); - hide.type = 'button'; - hide.value = 'Close'; - this.listenersToRemove.push(DomUtils.addDisposableListener(hide, 'click', () => { - this.onHide(); - })); - this.outerDomNode.appendChild(hide); - - let heading = document.createElement('pre'); - heading.appendChild(document.createTextNode(this.renderRow('TOPIC', 'NAME', 'TOOK', 'START', 'END'))); - this.outerDomNode.appendChild(heading); - this.outerDomNode.appendChild(document.createElement('hr')); - - let domNode = document.createElement('div'); - domNode.className = 'inner'; - this.outerDomNode.appendChild(domNode); - - document.body.appendChild(this.outerDomNode); - - return domNode; - } - - private onTextFilterChange(): void { - setTimeout(() => { - this.refilter(); - }); - } - - private onTimeFilterChange(): void { - setTimeout(() => { - this.refilter(); - }); - } - - private matchesTextFilter(event: ITimerEvent): boolean { - if (!this.textFilter) { - return true; - } - if (event.topic.toLowerCase().indexOf(this.textFilter.toLowerCase()) >= 0) { - return true; - } - if (event.name.toLowerCase().indexOf(this.textFilter.toLowerCase()) >= 0) { - return true; - } - return false; - } - - private matchesTimeFilter(event: ITimerEvent): boolean { - if (!this.timeFilter) { - return true; - } - if (event.timeTaken() >= this.timeFilter) { - return true; - } - return false; - } - - private shouldShow(event: ITimerEvent): boolean { - return this.matchesTextFilter(event) && this.matchesTimeFilter(event); - } - - private refilter(): void { - this.textFilter = this.textFilterDomNode.value; - this.timeFilter = parseInt(this.timeFilterDomNode.value, 10); - - let domNodes = Array.prototype.slice.call(this.domNode.children, 0); - for (let i = 0; i < domNodes.length; i++) { - let eventId = domNodes[i].getAttribute('data-event-id'); - let event = this.renderedEvents[eventId]; - - if (this.shouldShow(event)) { - domNodes[i].style.display = 'inherit'; - } else { - domNodes[i].style.display = 'none'; - } - } - } - - private _onClear(): void { - this.lastEventIndex = this.timeKeeper.getCollectedEvents().length; - this.renderedEvents = {}; - this.renderCnt = 0; - DomUtils.clearNode(this.domNode); - } - - private leftPaddedString(size: number, padChar: string, str: string): string { - let spaces = this._repeatStr(padChar, Math.max(0, size - str.length)); - return spaces + str; - } - - private rightPaddedString(size: number, padChar: string, str: string): string { - let spaces = this._repeatStr(padChar, Math.max(0, size - str.length)); - return str + spaces; - } - - private renderRow(topic: string, name: string, timeTook: string, timeStart: string, timerEnd: string): string { - let result = ' '; - result += this.rightPaddedString(10, ' ', topic); - result += this.rightPaddedString(30, ' ', name); - result += ' ' + this.leftPaddedString(15, ' ', timeTook); - result += ' ' + this.leftPaddedString(13, ' ', timeStart); - return result; - } - - private _suffix0(s: string): string { - if (s.charAt(s.length - 3) === '.') { - return s; - } - if (s.charAt(s.length - 2) === '.') { - return s + '0'; - } - return s + '.00'; - } - - private _twoPrecision(a: number): string { - return this._suffix0(Math.round(a * 100) / 100 + ''); - } - - private _absoluteTime(t: number): string { - if (t < 1000) { - return this._twoPrecision(t) + ' ms'; - } - t /= 1000; - if (t < 60) { - return this._twoPrecision(t) + ' s'; - } - t /= 60; - if (t < 60) { - return this._twoPrecision(t) + ' m'; - } - t /= 60; - return this._twoPrecision(t) + ' h'; - } - - private _renderEvent(domNode: HTMLElement, event: ITimerEvent): void { - let start = event.startTime.getTime() - TimeKeeper.PARSE_TIME.getTime(); - - let result = this.renderRow( - event.topic, - event.name, - this._twoPrecision(event.timeTaken()), - this._absoluteTime(start) + '', - this._absoluteTime(start + event.timeTaken()) - ); - domNode.textContent = ''; - domNode.appendChild(document.createTextNode(result)); - } - - private _renderStartTimerEvent(event: ITimerEvent): void { - let domNode = document.createElement('pre'); - this._renderEvent(domNode, event); - this.domNode.appendChild(domNode); - let idString = event.id.toString(); - - domNode.setAttribute('data-event-id', idString); - domNode.className = 'timer-event-' + (event.id % 2); - this.renderedEvents[idString] = event; - - if (this.shouldShow(this.renderedEvents[idString])) { - domNode.style.display = 'inherit'; - } else { - domNode.style.display = 'none'; - } - - this.renderCnt++; - } - - private _render(): void { - let allEvents = this.timeKeeper.getCollectedEvents(), didSomething = false; - - for (let i = this.lastEventIndex; i < allEvents.length; i++) { - let ev = allEvents[i]; - - if (!ev.stopTime) { - // This event is not yet finished => block - this.lastEventIndex = i; - if (didSomething) { - this.domNode.scrollTop = 100000; - } - return; - } - - this._renderStartTimerEvent(ev); - didSomething = true; - } - - if (didSomething) { - this.domNode.scrollTop = 100000; - } - this.lastEventIndex = allEvents.length; - } - - private _repeatStr(str: string, cnt: number): string { - let r = ''; - for (let i = 0; i < cnt; i++) { - r += str; - } - return r; - } -} - diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 2eb260b91bb6..176cc70a5016 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -18,7 +18,7 @@ export function equals(one: T[], other: T[], itemEquals: (a: T, b: T) => bool return false; } - for (var i = 0, len = one.length; i < len; i++) { + for (let i = 0, len = one.length; i < len; i++) { if (!itemEquals(one[i], other[i])) { return false; } @@ -177,7 +177,7 @@ export function first(array: T[], fn: (item: T) => boolean, notFoundValue: T export function commonPrefixLength(one: T[], other: T[], equals: (a: T, b: T) => boolean = (a, b) => a === b): number { let result = 0; - for (var i = 0, len = Math.min(one.length, other.length); i < len && equals(one[i], other[i]); i++) { + for (let i = 0, len = Math.min(one.length, other.length); i < len && equals(one[i], other[i]); i++) { result++; } diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index b899d0f7350b..fadef1d89ea9 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -71,8 +71,8 @@ export interface ITask { * The throttler implements this via the queue() method, by providing it a task * factory. Following the example: * - * var throttler = new Throttler(); - * var letters = []; + * const throttler = new Throttler(); + * const letters = []; * * function deliver() { * const lettersToDeliver = letters; @@ -166,8 +166,8 @@ export class SimpleThrottler { * to be executed and the waiting period (delay) must be passed in as arguments. Following * the example: * - * var delayer = new Delayer(WAITING_PERIOD); - * var letters = []; + * const delayer = new Delayer(WAITING_PERIOD); + * const letters = []; * * function letterReceived(l) { * letters.push(l); diff --git a/src/vs/base/common/dates.ts b/src/vs/base/common/dates.ts deleted file mode 100644 index 8a442c17f1a5..000000000000 --- a/src/vs/base/common/dates.ts +++ /dev/null @@ -1,38 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import nls = require('vs/nls'); - -export function since(date: Date): string { - var seconds = (new Date().getTime() - date.getTime()) / 1000; - if (seconds < 60) { - return nls.localize('diff.seconds.verbose', "just now"); - } - - var minutes = seconds / 60; - if (minutes < 60) { - return Math.floor(minutes) === 1 ? nls.localize('diff.minute.verbose', "1 minute ago") : nls.localize('diff.minutes.verbose', "{0} minutes ago", Math.floor(minutes)); - } - - var hours = minutes / 60; - if (hours < 24) { - return Math.floor(hours) === 1 ? nls.localize('diff.hour.verbose', "1 hour ago") : nls.localize('diff.hours.verbose', "{0} hours ago", Math.floor(hours)); - } - - var days = hours / 24; - if (Math.floor(days) === 1) { - return nls.localize('diff.days.yesterday', "yesterday"); - } - - if (days > 6 && days < 8) { - return nls.localize('diff.days.week', "a week ago"); - } - - if (days > 30 && days < 40) { - return nls.localize('diff.days.month', "a month ago"); - } - - return nls.localize('diff.days.verbose', "{0} days ago", Math.floor(days)); -} \ No newline at end of file diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index 66e7daaa755e..128f64faa1bb 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -272,10 +272,11 @@ export function matchesCamelCase(word: string, camelCaseWord: string): IMatch[] } // Matches beginning of words supporting non-ASCII languages -// E.g. "gp" or "g p" will match "Git: Pull" +// If `contiguous` is true then matches word with beginnings of the words in the target. E.g. "pul" will match "Git: Pull" +// Otherwise also matches sub string of the word with beginnings of the words in the target. E.g. "gp" or "g p" will match "Git: Pull" // Useful in cases where the target is words (e.g. command labels) -export function matchesWords(word: string, target: string): IMatch[] { +export function matchesWords(word: string, target: string, contiguous: boolean = false): IMatch[] { if (!target || target.length === 0) { return null; } @@ -283,14 +284,14 @@ export function matchesWords(word: string, target: string): IMatch[] { let result: IMatch[] = null; let i = 0; - while (i < target.length && (result = _matchesWords(word.toLowerCase(), target, 0, i)) === null) { + while (i < target.length && (result = _matchesWords(word.toLowerCase(), target, 0, i, contiguous)) === null) { i = nextWord(target, i + 1); } return result; } -function _matchesWords(word: string, target: string, i: number, j: number): IMatch[] { +function _matchesWords(word: string, target: string, i: number, j: number, contiguous: boolean): IMatch[] { if (i === word.length) { return []; } else if (j === target.length) { @@ -300,10 +301,12 @@ function _matchesWords(word: string, target: string, i: number, j: number): IMat } else { let result: IMatch[] = null; let nextWordIndex = j + 1; - result = _matchesWords(word, target, i + 1, j + 1); - while (!result && (nextWordIndex = nextWord(target, nextWordIndex)) < target.length) { - result = _matchesWords(word, target, i + 1, nextWordIndex); - nextWordIndex++; + result = _matchesWords(word, target, i + 1, j + 1, contiguous); + if (!contiguous) { + while (!result && (nextWordIndex = nextWord(target, nextWordIndex)) < target.length) { + result = _matchesWords(word, target, i + 1, nextWordIndex, contiguous); + nextWordIndex++; + } } return result === null ? null : join({ start: j, end: j + 1 }, result); } diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 3906356d003c..0eb5f89db377 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -52,7 +52,7 @@ export class LinkedMap { } public keys(): K[] { - var keys: K[] = []; + const keys: K[] = []; for (let key in this.map) { keys.push(this.map[key].key); } @@ -60,7 +60,7 @@ export class LinkedMap { } public values(): T[] { - var values: T[] = []; + const values: T[] = []; for (let key in this.map) { values.push(this.map[key].value); } @@ -68,7 +68,7 @@ export class LinkedMap { } public entries(): Entry[] { - var entries: Entry[] = []; + const entries: Entry[] = []; for (let key in this.map) { entries.push(this.map[key]); } diff --git a/src/vs/base/common/mime.ts b/src/vs/base/common/mime.ts index d47010f4e0b3..f05d9576d9bd 100644 --- a/src/vs/base/common/mime.ts +++ b/src/vs/base/common/mime.ts @@ -143,7 +143,7 @@ function guessMimeTypeByPath(path: string, filename: string, associations: IText let patternMatch: ITextMimeAssociationItem; let extensionMatch: ITextMimeAssociationItem; - for (var i = 0; i < associations.length; i++) { + for (let i = 0; i < associations.length; i++) { let association = associations[i]; // First exact name match @@ -243,7 +243,7 @@ export function isUnspecific(mime: string[] | string): boolean { } export function suggestFilename(langId: string, prefix: string): string { - for (var i = 0; i < registeredAssociations.length; i++) { + for (let i = 0; i < registeredAssociations.length; i++) { let association = registeredAssociations[i]; if (association.userConfigured) { continue; // only support registered ones diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index ee18a5ec9d2d..2bd0f2a3d452 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -12,23 +12,23 @@ export namespace Schemas { * A schema that is used for models that exist in memory * only and that have no correspondence on a server or such. */ - export var inMemory: string = 'inmemory'; + export const inMemory: string = 'inmemory'; /** * A schema that is used for setting files */ - export var vscode: string = 'vscode'; + export const vscode: string = 'vscode'; /** * A schema that is used for internal private files */ - export var internal: string = 'private'; + export const internal: string = 'private'; - export var http: string = 'http'; + export const http: string = 'http'; - export var https: string = 'https'; + export const https: string = 'https'; - export var file: string = 'file'; + export const file: string = 'file'; } export interface IXHROptions { diff --git a/src/vs/base/common/paths.ts b/src/vs/base/common/paths.ts index 084eacd9782b..92de20b38efe 100644 --- a/src/vs/base/common/paths.ts +++ b/src/vs/base/common/paths.ts @@ -11,12 +11,12 @@ import { CharCode } from 'vs/base/common/charCode'; /** * The forward slash path separator. */ -export var sep = '/'; +export const sep = '/'; /** * The native path separator depending on the OS. */ -export var nativeSep = isWindows ? '\\' : '/'; +export const nativeSep = isWindows ? '\\' : '/'; export function relative(from: string, to: string): string { const originalNormalizedFrom = normalize(from); @@ -50,7 +50,7 @@ export function relative(from: string, to: string): string { * @returns the directory name of a path. */ export function dirname(path: string): string { - var idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\'); + const idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\'); if (idx === 0) { return '.'; } else if (~idx === 0) { @@ -64,7 +64,7 @@ export function dirname(path: string): string { * @returns the base name of a path. */ export function basename(path: string): string { - var idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\'); + const idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\'); if (idx === 0) { return path; } else if (~idx === path.length - 1) { @@ -79,7 +79,7 @@ export function basename(path: string): string { */ export function extname(path: string): string { path = basename(path); - var idx = ~path.lastIndexOf('.'); + const idx = ~path.lastIndexOf('.'); return idx ? path.substring(~idx) : ''; } diff --git a/src/vs/base/common/stopwatch.ts b/src/vs/base/common/stopwatch.ts index 4f0fc157da35..fe3889b29184 100644 --- a/src/vs/base/common/stopwatch.ts +++ b/src/vs/base/common/stopwatch.ts @@ -6,7 +6,7 @@ import { globals } from 'vs/base/common/platform'; -var hasPerformanceNow = (globals.performance && typeof globals.performance.now === 'function'); +const hasPerformanceNow = (globals.performance && typeof globals.performance.now === 'function'); export class StopWatch { diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index f8b5625a7376..fb4c42cd4520 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -607,8 +607,8 @@ export function safeBtoa(str: string): string { } export function repeat(s: string, count: number): string { - var result = ''; - for (var i = 0; i < count; i++) { + let result = ''; + for (let i = 0; i < count; i++) { result += s; } return result; diff --git a/src/vs/base/common/timer.ts b/src/vs/base/common/timer.ts deleted file mode 100644 index 59e007293683..000000000000 --- a/src/vs/base/common/timer.ts +++ /dev/null @@ -1,293 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 Platform = require('vs/base/common/platform'); -import errors = require('vs/base/common/errors'); -import precision = require('vs/base/common/stopwatch'); -import { IDisposable } from 'vs/base/common/lifecycle'; - -export var ENABLE_TIMER = false; -var msWriteProfilerMark = Platform.globals['msWriteProfilerMark']; - -export enum Topic { - EDITOR, - LANGUAGES, - WORKER, - WORKBENCH, - STARTUP -} - -export interface ITimerEvent { - id: number; - topic: string; - name: string; - description: string; - data: any; - - startTime: Date; - stopTime: Date; - - stop(stopTime?: Date): void; - timeTaken(): number; -} - -export interface IExistingTimerEvent { - topic: string; - name: string; - - description?: string; - - startTime: Date; - stopTime: Date; -} - -class NullTimerEvent implements ITimerEvent { - public id: number; - public topic: string; - public name: string; - public description: string; - public data: any; - - public startTime: Date; - public stopTime: Date; - - public stop(): void { - return; - } - - public timeTaken(): number { - return -1; - } -} - -class TimerEvent implements ITimerEvent { - public id: number; - public topic: string; - public name: string; - public description: string; - public data: any; - - public startTime: Date; - public stopTime: Date; - - private timeKeeper: TimeKeeper; - private sw: precision.StopWatch; - - constructor(timeKeeper: TimeKeeper, name: string, topic: string, startTime?: Date, description?: string) { - this.timeKeeper = timeKeeper; - this.name = name; - this.description = description; - this.topic = topic; - this.stopTime = null; - - if (startTime) { - this.startTime = startTime; - return; - } - - this.startTime = new Date(); - this.sw = precision.StopWatch.create(); - - if (msWriteProfilerMark) { - var profilerName = ['Monaco', this.topic, this.name, 'start']; - msWriteProfilerMark(profilerName.join('|')); - } - } - - public stop(stopTime?: Date): void { - - // already stopped - if (this.stopTime !== null) { - return; - } - - if (stopTime) { - this.stopTime = stopTime; - this.sw = null; - this.timeKeeper._onEventStopped(this); - return; - } - - this.stopTime = new Date(); - if (this.sw) { - this.sw.stop(); - } - - this.timeKeeper._onEventStopped(this); - - if (msWriteProfilerMark) { - var profilerName = ['Monaco', this.topic, this.name, 'stop']; - msWriteProfilerMark(profilerName.join('|')); - } - } - - public timeTaken(): number { - if (this.sw) { - return this.sw.elapsed(); - } - if (this.stopTime) { - return this.stopTime.getTime() - this.startTime.getTime(); - } - return -1; - } -} - -export interface IEventsListener { - (events: ITimerEvent[]): void; -} - -export class TimeKeeper { - /** - * After being started for 1 minute, all timers are automatically stopped. - */ - private static _MAX_TIMER_LENGTH = 60000; // 1 minute - /** - * Every 2 minutes, a sweep of current started timers is done. - */ - private static _CLEAN_UP_INTERVAL = 120000; // 2 minutes - /** - * Collect at most 1000 events. - */ - private static _EVENT_CACHE_LIMIT = 1000; - - private static EVENT_ID = 1; - public static PARSE_TIME = new Date(); - - - private cleaningIntervalId: Platform.IntervalToken; - private collectedEvents: ITimerEvent[]; - private listeners: IEventsListener[]; - - constructor() { - this.cleaningIntervalId = -1; - this.collectedEvents = []; - this.listeners = []; - } - - public isEnabled(): boolean { - return ENABLE_TIMER; - } - - public start(topic: Topic | string, name: string, start?: Date, description?: string): ITimerEvent { - if (!this.isEnabled()) { - return nullEvent; - } - - var strTopic: string; - - if (typeof topic === 'string') { - strTopic = topic; - } else if (topic === Topic.EDITOR) { - strTopic = 'Editor'; - } else if (topic === Topic.LANGUAGES) { - strTopic = 'Languages'; - } else if (topic === Topic.WORKER) { - strTopic = 'Worker'; - } else if (topic === Topic.WORKBENCH) { - strTopic = 'Workbench'; - } else if (topic === Topic.STARTUP) { - strTopic = 'Startup'; - } - - this.initAutoCleaning(); - var event = new TimerEvent(this, name, strTopic, start, description); - this.addEvent(event); - - return event; - } - - public dispose(): void { - if (this.cleaningIntervalId !== -1) { - Platform.clearInterval(this.cleaningIntervalId); - this.cleaningIntervalId = -1; - } - } - - public addListener(listener: IEventsListener): IDisposable { - this.listeners.push(listener); - return { - dispose: () => { - for (var i = 0; i < this.listeners.length; i++) { - if (this.listeners[i] === listener) { - this.listeners.splice(i, 1); - return; - } - } - } - }; - } - - private addEvent(event: ITimerEvent): void { - event.id = TimeKeeper.EVENT_ID; - TimeKeeper.EVENT_ID++; - this.collectedEvents.push(event); - // expire items from the front of the cache - if (this.collectedEvents.length > TimeKeeper._EVENT_CACHE_LIMIT) { - this.collectedEvents.shift(); - } - } - - private initAutoCleaning(): void { - if (this.cleaningIntervalId === -1) { - this.cleaningIntervalId = Platform.setInterval(() => { - var now = Date.now(); - this.collectedEvents.forEach((event) => { - if (!event.stopTime && (now - event.startTime.getTime()) >= TimeKeeper._MAX_TIMER_LENGTH) { - event.stop(); - } - }); - }, TimeKeeper._CLEAN_UP_INTERVAL); - } - } - - public getCollectedEvents(): ITimerEvent[] { - return this.collectedEvents.slice(0); - } - - public clearCollectedEvents(): void { - this.collectedEvents = []; - } - - _onEventStopped(event: ITimerEvent): void { - var emitEvents = [event]; - - var listeners = this.listeners.slice(0); - for (var i = 0; i < listeners.length; i++) { - try { - listeners[i](emitEvents); - } catch (e) { - errors.onUnexpectedError(e); - } - } - } - - public setInitialCollectedEvents(events: IExistingTimerEvent[], startTime?: Date): void { - if (!this.isEnabled()) { - return; - } - - if (startTime) { - TimeKeeper.PARSE_TIME = startTime; - } - - events.forEach((event) => { - var e = new TimerEvent(this, event.name, event.topic, event.startTime, event.description); - e.stop(event.stopTime); - this.addEvent(e); - }); - } -} - -var timeKeeper = new TimeKeeper(); -export var nullEvent: ITimerEvent = new NullTimerEvent(); - -export function start(topic: Topic | string, name: string, start?: Date, description?: string): ITimerEvent { - return timeKeeper.start(topic, name, start, description); -} - -export function getTimeKeeper(): TimeKeeper { - return timeKeeper; -} diff --git a/src/vs/base/parts/tree/browser/tree.ts b/src/vs/base/parts/tree/browser/tree.ts index b59f4b267cc8..10029064360c 100644 --- a/src/vs/base/parts/tree/browser/tree.ts +++ b/src/vs/base/parts/tree/browser/tree.ts @@ -328,7 +328,7 @@ export interface ITree extends Events.IEventEmitter { * Returns a navigator which allows to discover the visible and * expanded elements in the tree. */ - getNavigator(): INavigator; + getNavigator(fromElement?: any, subTreeOnly?: boolean): INavigator; /** * Disposes the tree diff --git a/src/vs/base/parts/tree/browser/treeImpl.ts b/src/vs/base/parts/tree/browser/treeImpl.ts index 3e8879bdb90c..fe1198623fe5 100644 --- a/src/vs/base/parts/tree/browser/treeImpl.ts +++ b/src/vs/base/parts/tree/browser/treeImpl.ts @@ -133,7 +133,7 @@ export class Tree extends Events.EventEmitter implements _.ITree { } public collapse(element: any, recursive: boolean = false): WinJS.Promise { - return this.model.collapse(element); + return this.model.collapse(element, recursive); } public collapseAll(elements: any[] = null, recursive: boolean = false): WinJS.Promise { @@ -318,8 +318,8 @@ export class Tree extends Events.EventEmitter implements _.ITree { return this.model.hasTrait(trait, element); } - getNavigator(): INavigator { - return new MappedNavigator(this.model.getNavigator(), i => i && i.getElement()); + getNavigator(fromElement?: any, subTreeOnly?: boolean): INavigator { + return new MappedNavigator(this.model.getNavigator(fromElement, subTreeOnly), i => i && i.getElement()); } public dispose(): void { diff --git a/src/vs/base/test/common/filters.test.ts b/src/vs/base/test/common/filters.test.ts index 21eae022f5b8..4da6a3a3b36e 100644 --- a/src/vs/base/test/common/filters.test.ts +++ b/src/vs/base/test/common/filters.test.ts @@ -186,5 +186,8 @@ suite('Filters', () => { filterOk(matchesWords, 'git プル', 'git: プル', [{ start: 0, end: 3 }, { start: 4, end: 7 }]); filterOk(matchesWords, 'öäk', 'Öhm: Älles Klar', [{ start: 0, end: 1 }, { start: 5, end: 6 }, { start: 11, end: 12 }]); + + assert.ok(matchesWords('gipu', 'Category: Git: Pull', true) === null); + assert.deepEqual(matchesWords('pu', 'Category: Git: Pull', true), [{ start: 15, end: 17 }]); }); }); diff --git a/src/vs/base/test/common/utils.ts b/src/vs/base/test/common/utils.ts index f8693d9cd69d..f69d864e509e 100644 --- a/src/vs/base/test/common/utils.ts +++ b/src/vs/base/test/common/utils.ts @@ -13,18 +13,21 @@ import URI from 'vs/base/common/uri'; export class DeferredTPromise extends TPromise { - public canceled = false; + public canceled: boolean; private completeCallback: TValueCallback; private errorCallback: (err: any) => void; private progressCallback: ProgressCallback; constructor() { + let captured: any; super((c, e, p) => { - this.completeCallback = c; - this.errorCallback = e; - this.progressCallback = p; + captured = { c, e, p }; }, () => this.oncancel()); + this.canceled = false; + this.completeCallback = captured.c; + this.errorCallback = captured.e; + this.progressCallback = captured.p; } public complete(value: T) { @@ -51,7 +54,13 @@ export class DeferredPPromise extends PPromise { private progressCallback: TProgressCallback

; constructor(init: (complete: TValueCallback, error: (err: any) => void, progress: TProgressCallback

) => void = (c, e, p) => { }, oncancel?: any) { - super((c, e, p) => { this.completeCallback = c; this.errorCallback = e; this.progressCallback = p; }, oncancel ? oncancel : () => this.oncancel); + let captured: any; + super((c, e, p) => { + captured = { c, e, p }; + }, oncancel ? oncancel : () => this.oncancel); + this.completeCallback = captured.c; + this.errorCallback = captured.e; + this.progressCallback = captured.p; } private oncancel(): void { diff --git a/src/vs/code/electron-main/launch.ts b/src/vs/code/electron-main/launch.ts index 026fad8e55b5..e413e7775bf6 100644 --- a/src/vs/code/electron-main/launch.ts +++ b/src/vs/code/electron-main/launch.ts @@ -93,7 +93,7 @@ export class LaunchService implements ILaunchService { let usedWindows: VSCodeWindow[]; if (!!args.extensionDevelopmentPath) { this.windowsService.openExtensionDevelopmentHostWindow({ context, cli: args, userEnv }); - } else if (args._.length === 0 && args['new-window']) { + } else if (args._.length === 0 && args['new-window'] || args['new-window-if-not-first']) { usedWindows = this.windowsService.open({ context, cli: args, userEnv, forceNewWindow: true, forceEmpty: true }); } else if (args._.length === 0) { usedWindows = [this.windowsService.focusLastActive(args, context)]; @@ -102,7 +102,7 @@ export class LaunchService implements ILaunchService { context, cli: args, userEnv, - forceNewWindow: args.wait || args['new-window'], + forceNewWindow: args.wait || args['new-window'] || args['new-window-if-not-first'], preferNewWindow: !args['reuse-window'], forceReuseWindow: args['reuse-window'], diffMode: args.diff diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index b9e11a0702a5..5eb0da6e884b 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -257,7 +257,7 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo } else if (global.macOpenFiles && global.macOpenFiles.length && (!environmentService.args._ || !environmentService.args._.length)) { windowsMainService.open({ context: OpenContext.DOCK, cli: environmentService.args, pathsToOpen: global.macOpenFiles, initialStartup: true }); // mac: open-file event received on startup } else { - windowsMainService.open({ context, cli: environmentService.args, forceNewWindow: environmentService.args['new-window'], diffMode: environmentService.args.diff, initialStartup: true }); // default: read paths from cli + windowsMainService.open({ context, cli: environmentService.args, forceNewWindow: environmentService.args['new-window'] || environmentService.args['new-window-if-not-first'], diffMode: environmentService.args.diff, initialStartup: true }); // default: read paths from cli } // Install Menu diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 89779d04901c..5f66d03b6187 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -83,6 +83,7 @@ export interface IWindowConfiguration extends ParsedArgs { isInitialStartup?: boolean; perfStartTime?: number; + perfAppReady?: number; perfWindowLoadTime?: number; workspacePath?: string; @@ -212,21 +213,6 @@ export class VSCodeWindow implements IVSCodeWindow { 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(); @@ -245,6 +231,11 @@ export class VSCodeWindow implements IVSCodeWindow { const windowConfig = this.configurationService.getConfiguration('window'); this.setMenuBarVisibility(windowConfig && windowConfig.menuBarVisibility); + // 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 + this.setCommonHTTPHeaders(); + this.registerListeners(); this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(e.config)); @@ -260,6 +251,20 @@ export class VSCodeWindow implements IVSCodeWindow { } }; + private setCommonHTTPHeaders(): void { + 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) }); + }); + }); + } + public hasHiddenTitleBarStyle(): boolean { return this.hiddenTitleBarStyle; } @@ -502,6 +507,7 @@ export class VSCodeWindow implements IVSCodeWindow { // Perf Counters windowConfiguration.perfStartTime = global.perfStartTime; + windowConfiguration.perfAppReady = global.perfAppReady; windowConfiguration.perfWindowLoadTime = Date.now(); // Config (combination of process.argv and window configuration) diff --git a/src/vs/editor/browser/config/configuration.ts b/src/vs/editor/browser/config/configuration.ts index 26cdc0c5193a..34a5c02a2fe0 100644 --- a/src/vs/editor/browser/config/configuration.ts +++ b/src/vs/editor/browser/config/configuration.ts @@ -41,41 +41,6 @@ class CSSBasedConfigurationCache { } } -class CharWidthReader { - - private _chr: string; - private _width: number; - - public get width(): number { return this._width; } - - constructor(chr: string) { - this._chr = chr; - this._width = 0; - } - - public render(out: HTMLSpanElement): void { - if (this._chr === ' ') { - let htmlString = ' '; - // Repeat character 256 (2^8) times - for (let i = 0; i < 8; i++) { - htmlString += htmlString; - } - out.innerHTML = htmlString; - } else { - let testString = this._chr; - // Repeat character 256 (2^8) times - for (let i = 0; i < 8; i++) { - testString += testString; - } - out.textContent = testString; - } - } - - public read(out: HTMLSpanElement): void { - this._width = out.offsetWidth / 256; - } -} - class CSSBasedConfiguration extends Disposable { public static INSTANCE = new CSSBasedConfiguration(); @@ -154,59 +119,15 @@ class CSSBasedConfiguration extends Disposable { } } - private static _testElementId(index: number): string { - return 'editorSizeProvider' + index; - } - - private static _createTestElements(bareFontInfo: BareFontInfo, readers: CharWidthReader[]): HTMLElement { - let container = document.createElement('div'); - Configuration.applyFontInfoSlow(container, bareFontInfo); - container.style.position = 'absolute'; - container.style.top = '-50000px'; - container.style.width = '50000px'; - - for (let i = 0, len = readers.length; i < len; i++) { - container.appendChild(document.createElement('br')); - - let testElement = document.createElement('span'); - testElement.id = this._testElementId(i); - readers[i].render(testElement); - - container.appendChild(testElement); - } - - container.appendChild(document.createElement('br')); - - return container; - } - - private static _readFromTestElements(readers: CharWidthReader[]): void { - for (let i = 0, len = readers.length; i < len; i++) { - readers[i].read(document.getElementById(this._testElementId(i))); - } - } - - private static _runReaders(bareFontInfo: BareFontInfo, readers: CharWidthReader[]): void { - // Create a test container with all these test elements - let testContainer = this._createTestElements(bareFontInfo, readers); - - // Add the container to the DOM - document.body.appendChild(testContainer); - - // Read various properties - this._readFromTestElements(readers); - - // Remove the container from the DOM - document.body.removeChild(testContainer); - } - private static _actualReadConfiguration(bareFontInfo: BareFontInfo): FontInfo { - let typicalHalfwidthCharacter = new CharWidthReader('n'); - let typicalFullwidthCharacter = new CharWidthReader('\uff4d'); - let space = new CharWidthReader(' '); - let digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].map(chr => new CharWidthReader(chr)); + let canvasElem = document.createElement('canvas'); + let context = canvasElem.getContext('2d'); + context.font = `normal normal normal normal ${bareFontInfo.fontSize}px / ${bareFontInfo.lineHeight}px ${bareFontInfo.fontFamily}`; - this._runReaders(bareFontInfo, digits.concat([typicalHalfwidthCharacter, typicalFullwidthCharacter, space])); + let typicalHalfwidthCharacter = context.measureText('n'); + let typicalFullwidthCharacter = context.measureText('\uff4d'); + let space = context.measureText(' '); + let digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].map(chr => context.measureText(chr)); let maxDigitWidth = 0; for (let i = 0, len = digits.length; i < len; i++) { diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 4084eb9362d5..505fe390815f 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -7,7 +7,6 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { EmitterEvent, IEventEmitter } from 'vs/base/common/eventEmitter'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import * as timer from 'vs/base/common/timer'; import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { StyleMutator } from 'vs/base/browser/styleMutator'; @@ -899,14 +898,12 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp if (!dom.isInDOM(this.domNode)) { return; } - let t = timer.start(timer.Topic.EDITOR, 'View.render'); let viewPartsToRender = this._getViewPartsToRender(); if (!this.viewLines.shouldRender() && viewPartsToRender.length === 0) { // Nothing to render this.keyboardHandler.writeToTextArea(); - t.stop(); return; } @@ -940,8 +937,6 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp // Render the scrollbar this.layoutProvider.renderScrollbar(); - - t.stop(); } private _setHasFocus(newHasFocus: boolean): void { diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 11f9a39f1b1a..dfd9904fa86f 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -598,50 +598,8 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed protected abstract readConfiguration(styling: editorCommon.BareFontInfo): editorCommon.FontInfo; } -/** - * Helper to update Monaco Editor Settings from configurations service. - */ -export class EditorConfiguration { - public static EDITOR_SECTION = 'editor'; - public static DIFF_EDITOR_SECTION = 'diffEditor'; - - /** - * Ask the provided configuration service to apply its configuration to the provided editor. - */ - public static apply(config: any, editor: editorCommon.IEditor): void { - if (!config) { - return; - } - - // Editor Settings (Code Editor, Diff, Terminal) - if (editor && typeof editor.updateOptions === 'function') { - let type = editor.getEditorType(); - if (type !== editorCommon.EditorType.ICodeEditor && type !== editorCommon.EditorType.IDiffEditor) { - return; - } - - let editorConfig = config[EditorConfiguration.EDITOR_SECTION]; - if (type === editorCommon.EditorType.IDiffEditor) { - let diffEditorConfig = config[EditorConfiguration.DIFF_EDITOR_SECTION]; - if (diffEditorConfig) { - if (!editorConfig) { - editorConfig = diffEditorConfig; - } else { - editorConfig = objects.mixin(editorConfig, diffEditorConfig); - } - } - } - - if (editorConfig) { - delete editorConfig.readOnly; // Prevent someone from making editor readonly - editor.updateOptions(editorConfig); - } - } - } -} - -let configurationRegistry = Registry.as(Extensions.Configuration); -let editorConfiguration: IConfigurationNode = { +const configurationRegistry = Registry.as(Extensions.Configuration); +const editorConfiguration: IConfigurationNode = { 'id': 'editor', 'order': 5, 'type': 'object', diff --git a/src/vs/editor/common/model/textModelWithTokens.ts b/src/vs/editor/common/model/textModelWithTokens.ts index 3ffa3e17768b..f5d1c5c4c9cd 100644 --- a/src/vs/editor/common/model/textModelWithTokens.ts +++ b/src/vs/editor/common/model/textModelWithTokens.ts @@ -8,7 +8,6 @@ import * as nls from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; -import * as timer from 'vs/base/common/timer'; import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { TextModel } from 'vs/editor/common/model/textModel'; @@ -294,7 +293,6 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke this._withModelTokensChangedEventBuilder((eventBuilder) => { - var t1 = timer.start(timer.Topic.EDITOR, 'backgroundTokenization'); toLineNumber = Math.min(this._lines.length, toLineNumber); var MAX_ALLOWED_TIME = 20, @@ -342,8 +340,6 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke if (this._invalidLineStartIndex < this._lines.length) { this._beginBackgroundTokenization(); } - - t1.stop(); }); } diff --git a/src/vs/editor/common/modes/linkComputer.ts b/src/vs/editor/common/modes/linkComputer.ts index ce1c82562acf..24b528847521 100644 --- a/src/vs/editor/common/modes/linkComputer.ts +++ b/src/vs/editor/common/modes/linkComputer.ts @@ -125,7 +125,7 @@ const enum CharacterClass { } const classifier = (function () { - let result = new CharacterClassifier(CharacterClass.None); + let result = new CharacterClassifier(CharacterClass.None); const FORCE_TERMINATION_CHARACTERS = ' \t<>\'\"、。。、,.:;?!@#$%&*‘“〈《「『【〔([{「」}])〕】』」》〉”’`~…'; for (let i = 0; i < FORCE_TERMINATION_CHARACTERS.length; i++) { diff --git a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts index 979421d8f9da..10e0304f3a2c 100644 --- a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts +++ b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts @@ -76,7 +76,7 @@ export class DefinitionAction extends EditorAction { let result: Location[] = []; for (let i = 0; i < references.length; i++) { let reference = references[i]; - if (!reference) { + if (!reference || !reference.range) { continue; } let {uri, range} = reference; @@ -110,7 +110,7 @@ export class DefinitionAction extends EditorAction { } else { let next = model.nearestReference(editor.getModel().uri, editor.getPosition()); this._openReference(editorService, next, this._configuration.openToSide).then(editor => { - if (model.references.length > 1) { + if (editor && model.references.length > 1) { this._openInPeek(editorService, editor, model); } else { model.dispose(); @@ -128,7 +128,7 @@ export class DefinitionAction extends EditorAction { revealIfVisible: !sideBySide } }, sideBySide).then(editor => { - return editor.getControl(); + return editor && editor.getControl(); }); } diff --git a/src/vs/editor/contrib/suggest/common/suggestModel.ts b/src/vs/editor/contrib/suggest/common/suggestModel.ts index fc522397f667..7e8eab227da5 100644 --- a/src/vs/editor/contrib/suggest/common/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/common/suggestModel.ts @@ -9,9 +9,8 @@ import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { forEach } from 'vs/base/common/collections'; import Event, { Emitter } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { startsWith } from 'vs/base/common/strings'; import { TPromise } from 'vs/base/common/winjs.base'; -import { ICommonCodeEditor, ICursorSelectionChangedEvent, CursorChangeReason, IModel, IPosition } from 'vs/editor/common/editorCommon'; +import { ICommonCodeEditor, ICursorSelectionChangedEvent, CursorChangeReason, IModel, IPosition, IWordAtPosition } from 'vs/editor/common/editorCommon'; import { ISuggestSupport, SuggestRegistry } from 'vs/editor/common/modes'; import { provideSuggestionItems, getSuggestionComparator, ISuggestionItem } from './suggest'; import { CompletionModel } from './completionModel'; @@ -30,104 +29,51 @@ export interface ISuggestEvent { auto: boolean; } -export class Context { +export class LineContext { + + static shouldAutoTrigger(editor: ICommonCodeEditor): boolean { + const model = editor.getModel(); + if (!model) { + return false; + } + const pos = editor.getPosition(); + const word = model.getWordAtPosition(pos); + if (!word) { + return false; + } + if (word.endColumn !== pos.column) { + return false; + } + if (!isNaN(Number(word.word))) { + return false; + } + return true; + } + + static isInEditableRange(editor: ICommonCodeEditor): boolean { + const model = editor.getModel(); + const position = editor.getPosition(); + if (model.hasEditableRange()) { + const editableRange = model.getEditableRange(); + if (!editableRange.containsPosition(position)) { + return false; + } + } + return true; + } readonly lineNumber: number; readonly column: number; - readonly isInEditableRange: boolean; - - readonly lineContentBefore: string; - - readonly wordBefore: string; - readonly wordAfter: string; - - constructor(model: IModel, position: IPosition, private auto: boolean) { - const lineContent = model.getLineContent(position.lineNumber); - const wordUnderCursor = model.getWordAtPosition(position); - - if (wordUnderCursor) { - this.wordBefore = lineContent.substring(wordUnderCursor.startColumn - 1, position.column - 1); - this.wordAfter = lineContent.substring(position.column - 1, wordUnderCursor.endColumn - 1); - } else { - this.wordBefore = ''; - this.wordAfter = ''; - } + readonly leadingLineContent: string; + readonly leadingWord: IWordAtPosition; + readonly auto; + constructor(model: IModel, position: IPosition, auto: boolean) { + this.leadingLineContent = model.getLineContent(position.lineNumber).substr(0, position.column - 1); + this.leadingWord = model.getWordUntilPosition(position); this.lineNumber = position.lineNumber; this.column = position.column; - this.lineContentBefore = lineContent.substr(0, position.column - 1); - - this.isInEditableRange = true; - if (model.hasEditableRange()) { - const editableRange = model.getEditableRange(); - - if (!editableRange.containsPosition(position)) { - this.isInEditableRange = false; - } - } - } - - shouldAutoTrigger(): boolean { - - if (this.wordBefore.length === 0) { - // Word before position is empty - return false; - } - - if (!isNaN(Number(this.wordBefore))) { - // Word before is number only - return false; - } - - if (this.wordAfter.length > 0) { - // Word after position is non empty - return false; - } - - return true; - } - - isDifferentContext(context: Context): boolean { - if (this.lineNumber !== context.lineNumber) { - // Line number has changed - return true; - } - - if (context.column < this.column - this.wordBefore.length) { - // column went before word start - return true; - } - - if (!startsWith(context.lineContentBefore, this.lineContentBefore)) { - // Line has changed before position - return true; - } - - if (context.wordBefore === '' && context.lineContentBefore !== this.lineContentBefore) { - // Most likely a space has been typed - return true; - } - - return false; - } - - shouldRetrigger(context: Context): boolean { - if (!startsWith(this.lineContentBefore, context.lineContentBefore)) { - // Doesn't look like the same line - return false; - } - - if (this.lineContentBefore.length > context.lineContentBefore.length && this.wordBefore.length === 0) { - // Text was deleted and previous current word was empty - return false; - } - - if (this.auto && context.wordBefore.length === 0) { - // Currently in auto mode and new current word is empty - return false; - } - - return true; + this.auto = auto; } } @@ -147,7 +93,7 @@ export class SuggestModel implements IDisposable { private state: State; private requestPromise: TPromise; - private context: Context; + private context: LineContext; private completionModel: CompletionModel; @@ -274,12 +220,11 @@ export class SuggestModel implements IDisposable { } private onCursorChange(e: ICursorSelectionChangedEvent): void { - if (!e.selection.isEmpty()) { - this.cancel(); - return; - } - if (e.source !== 'keyboard' || e.reason !== CursorChangeReason.NotSet) { + if (!e.selection.isEmpty() + || e.source !== 'keyboard' + || e.reason !== CursorChangeReason.NotSet) { + this.cancel(); return; } @@ -288,32 +233,28 @@ export class SuggestModel implements IDisposable { return; } - const isInactive = this.state === State.Idle; - - if (isInactive && !this.editor.getConfiguration().contribInfo.quickSuggestions) { - return; - } - const model = this.editor.getModel(); - if (!model) { return; } - const ctx = new Context(model, this.editor.getPosition(), false); + if (this.state === State.Idle) { - if (isInactive) { - // trigger was not called or it was canceled - this.cancel(); - - if (ctx.shouldAutoTrigger()) { - this.triggerAutoSuggestPromise = TPromise.timeout(this.quickSuggestDelay); - this.triggerAutoSuggestPromise.then(() => { - this.triggerAutoSuggestPromise = null; - this.trigger(true); - }); + if (this.editor.getConfiguration().contribInfo.quickSuggestions) { + // trigger 24x7 IntelliSense when idle and enabled + this.cancel(); + if (LineContext.shouldAutoTrigger(this.editor)) { + this.triggerAutoSuggestPromise = TPromise.timeout(this.quickSuggestDelay); + this.triggerAutoSuggestPromise.then(() => { + this.triggerAutoSuggestPromise = null; + this.trigger(true); + }); + } } + } else { + // refine active suggestion + const ctx = new LineContext(model, this.editor.getPosition(), this.state === State.Auto); this.onNewContext(ctx); } } @@ -326,9 +267,9 @@ export class SuggestModel implements IDisposable { return; } - const ctx = new Context(model, this.editor.getPosition(), auto); + const ctx = new LineContext(model, this.editor.getPosition(), auto); - if (!ctx.isInEditableRange) { + if (!LineContext.isInEditableRange(this.editor)) { return; } @@ -359,9 +300,9 @@ export class SuggestModel implements IDisposable { items = items.concat(existingItems).sort(cmpFn); } - const ctx = new Context(model, this.editor.getPosition(), auto); + const ctx = new LineContext(model, this.editor.getPosition(), auto); this.completionModel = new CompletionModel(items, this.context.column, { - leadingLineContent: ctx.lineContentBefore, + leadingLineContent: ctx.leadingLineContent, characterCountDelta: this.context ? ctx.column - this.context.column : 0 }); this.onNewContext(ctx); @@ -369,42 +310,66 @@ export class SuggestModel implements IDisposable { }).then(null, onUnexpectedError); } - private onNewContext(ctx: Context): void { - if (this.context && this.context.isDifferentContext(ctx)) { - if (this.context.shouldRetrigger(ctx)) { - this.trigger(this.state === State.Auto, true); - } else { - this.cancel(); + private onNewContext(ctx: LineContext): void { + + if (!this.context) { + // happens when 24x7 IntelliSense is enabled and still in its delay + return; + } + + if (ctx.lineNumber !== this.context.lineNumber) { + // e.g. happens when pressing Enter while IntelliSense is computed + this.cancel(); + return; + } + + if (ctx.column < this.context.column) { + // typed -> moved cursor LEFT -> retrigger if still on a word + if (ctx.leadingWord.word) { + this.trigger(this.context.auto, true); } + return; + } - } else if (this.completionModel) { + if (!this.completionModel) { + // happens when IntelliSense is not yet computed + return; + } - if (this.completionModel.incomplete && ctx.column > this.context.column) { - const {complete, incomplete} = this.completionModel.resolveIncompleteInfo(); - this.trigger(this.state === State.Auto, true, incomplete, complete); - return; - } + if (ctx.column > this.context.column && this.completionModel.incomplete) { + // typed -> moved cursor RIGHT & incomple model -> retrigger + const {complete, incomplete} = this.completionModel.resolveIncompleteInfo(); + this.trigger(this.state === State.Auto, true, incomplete, complete); - const auto = this.state === State.Auto; - const oldLineContext = this.completionModel.lineContext; + } else { + // typed -> moved cursor RIGHT -> update UI + let oldLineContext = this.completionModel.lineContext; let isFrozen = false; this.completionModel.lineContext = { - leadingLineContent: ctx.lineContentBefore, - characterCountDelta: this.context ? ctx.column - this.context.column : 0 + leadingLineContent: ctx.leadingLineContent, + characterCountDelta: ctx.column - this.context.column }; - // when explicitly request when the next context goes - // from 'results' to 'no results' freeze - if (!auto && this.completionModel.items.length === 0) { - this.completionModel.lineContext = oldLineContext; - isFrozen = this.completionModel.items.length > 0; + if (this.completionModel.items.length === 0) { + + if (LineContext.shouldAutoTrigger(this.editor) && this.context.leadingWord.endColumn < ctx.leadingWord.startColumn) { + // retrigger when heading into a new word + this.trigger(this.context.auto, true); + return; + } + + if (!this.context.auto) { + // freeze when IntelliSense was manually requested + this.completionModel.lineContext = oldLineContext; + isFrozen = this.completionModel.items.length > 0; + } } this._onDidSuggest.fire({ completionModel: this.completionModel, + auto: this.context.auto, isFrozen, - auto }); } } diff --git a/src/vs/editor/contrib/suggest/test/common/suggestModel.test.ts b/src/vs/editor/contrib/suggest/test/common/suggestModel.test.ts index aa331b329d3a..7304fc44d367 100644 --- a/src/vs/editor/contrib/suggest/test/common/suggestModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/common/suggestModel.test.ts @@ -12,7 +12,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { Model } from 'vs/editor/common/model/model'; import { ICommonCodeEditor, Handler } from 'vs/editor/common/editorCommon'; import { ISuggestSupport, ISuggestResult, SuggestRegistry } from 'vs/editor/common/modes'; -import { SuggestModel, Context } from 'vs/editor/contrib/suggest/common/suggestModel'; +import { SuggestModel, LineContext } from 'vs/editor/contrib/suggest/common/suggestModel'; import { MockCodeEditor, MockScopeLocation } from 'vs/editor/test/common/mocks/mockCodeEditor'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; @@ -49,8 +49,10 @@ suite('SuggestModel - Context', function () { function assertAutoTrigger(offset: number, expected: boolean): void { const pos = model.getPositionAt(offset); - const ctx = new Context(model, pos, false); - assert.equal(ctx.shouldAutoTrigger(), expected); + const editor = createMockEditor(model); + editor.setPosition(pos); + assert.equal(LineContext.shouldAutoTrigger(editor), expected); + editor.dispose(); } assertAutoTrigger(3, true); // end of word, Das| @@ -59,28 +61,6 @@ suite('SuggestModel - Context', function () { assertAutoTrigger(55, false); // number, 1861| }); - test('Context - isDifferentContext', function () { - - // different line - const ctx = new Context(model, { lineNumber: 1, column: 8 }, true); // Das Pfer|d - assert.equal(ctx.isDifferentContext(new Context(model, { lineNumber: 2, column: 1 }, true)), true); - - - function createEndContext(value: string) { - const model = Model.createFromString(value); - const ctx = new Context(model, model.getPositionAt(value.length), true); // Das Pfer|d - return ctx; - } - - // got shorter -> redo - assert.equal(createEndContext('One Two').isDifferentContext(createEndContext('One Tw')), true); - - // got longer inside word -> keep - assert.equal(createEndContext('One Tw').isDifferentContext(createEndContext('One Two')), false); - - // got longer new word -> redo - assert.equal(createEndContext('One Two').isDifferentContext(createEndContext('One Two ')), true); - }); }); suite('SuggestModel - TriggerAndCancelOracle', function () { @@ -239,4 +219,49 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { }); }); }); -}); \ No newline at end of file + + test('#17400: Keep filtering suggestModel.ts after space', function () { + + disposables.push(SuggestRegistry.register({ scheme: 'test' }, { + triggerCharacters: [], + provideCompletionItems(doc, pos) { + return { + currentWord: '', + incomplete: false, + suggestions: [{ + label: 'My Table', + type: 'property', + insertText: 'My Table' + }] + }; + } + })); + + model.setValue(''); + + return withOracle((model, editor) => { + + return assertEvent(model.onDidSuggest, () => { + editor.setPosition({ lineNumber: 1, column: 1 }); + editor.trigger('keyboard', Handler.Type, { text: 'My' }); + + }, event => { + assert.equal(event.auto, true); + assert.equal(event.completionModel.items.length, 1); + const [first] = event.completionModel.items; + assert.equal(first.suggestion.label, 'My Table'); + + return assertEvent(model.onDidSuggest, () => { + editor.setPosition({ lineNumber: 1, column: 3 }); + editor.trigger('keyboard', Handler.Type, { text: ' ' }); + + }, event => { + assert.equal(event.auto, true); + assert.equal(event.completionModel.items.length, 1); + const [first] = event.completionModel.items; + assert.equal(first.suggestion.label, 'My Table'); + }); + }); + }); + }); +}); diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index d789272604af..ea5a3ee80fe6 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -9,6 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import Event from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { IEditorViewState } from 'vs/editor/common/editorCommon'; export const IEditorService = createDecorator('editorService'); @@ -238,6 +239,11 @@ export interface ITextEditorOptions extends IEditorOptions { endColumn?: number; }; + /** + * Text editor view state. + */ + viewState?: IEditorViewState; + /** * Option to scroll vertically or horizontally as necessary and reveal a range centered vertically only if it lies outside the viewport. */ diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index a95ec8f2bea2..36ecf5f1fb64 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -14,6 +14,7 @@ export interface ParsedArgs { diff?: boolean; goto?: boolean; 'new-window'?: boolean; + 'new-window-if-not-first'?: boolean; 'reuse-window'?: boolean; locale?: string; 'user-data-dir'?: string; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index caa91b82bb41..c6f6cc136c52 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -30,6 +30,7 @@ const options: minimist.Opts = { 'diff', 'goto', 'new-window', + 'new-window-if-not-first', 'reuse-window', 'performance', 'verbose', diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index e3e072b288e2..37f5a52df217 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -223,7 +223,7 @@ export class FileChangesEvent extends events.Event { return false; } - return this._changes.some((change) => { + return this._changes.some(change => { if (change.type !== type) { return false; } @@ -280,11 +280,11 @@ export class FileChangesEvent extends events.Event { } private getOfType(type: FileChangeType): IFileChange[] { - return this._changes.filter((change) => change.type === type); + return this._changes.filter(change => change.type === type); } private hasType(type: FileChangeType): boolean { - return this._changes.some((change) => { + return this._changes.some(change => { return change.type === type; }); } diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index f4fb6eb0dfb3..27ffed195548 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -123,13 +123,15 @@ export class InstantiationService implements IInstantiationService { // arguments defined by service decorators let serviceDependencies = _util.getServiceDependencies(desc.ctor).sort((a, b) => a.index - b.index); - let serviceArgs = serviceDependencies.map(dependency => { + let serviceArgs: any[] = []; + for (const dependency of serviceDependencies) { let service = this._getOrCreateServiceInstance(dependency.id); if (!service && this._strict && !dependency.optional) { throw new Error(`[createInstance] ${desc.ctor.name} depends on UNKNOWN service ${dependency.id}.`); } - return service; - }); + serviceArgs.push(service); + } + let firstServiceArgPos = serviceDependencies.length > 0 ? serviceDependencies[0].index : staticArgs.length; // check for argument mismatches, adjust static args if needed diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index dfbd18ee3109..ba99b7663df1 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -110,7 +110,7 @@ export class ResourceLabel extends IconLabel { const resource = this.label.resource; let title = ''; - if (this.options && this.options.title) { + if (this.options && typeof this.options.title === 'string') { title = this.options.title; } else if (resource) { title = getPathLabel(resource.fsPath); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts index ce92c4c1a148..ebf4a30612c9 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts @@ -758,7 +758,7 @@ export class EditorGroupsControl implements IEditorGroupsControl, IVerticalSashL this.sashTwo.setOrientation(this.layoutVertically ? Orientation.VERTICAL : Orientation.HORIZONTAL); // Trigger layout - this.arrangeGroups(GroupArrangement.EVEN); + this.arrangeGroups(); } } @@ -766,7 +766,7 @@ export class EditorGroupsControl implements IEditorGroupsControl, IVerticalSashL return this.layoutVertically ? 'vertical' : 'horizontal'; } - public arrangeGroups(arrangement: GroupArrangement): void { + public arrangeGroups(arrangement?: GroupArrangement): void { if (!this.dimension) { return; // too early } @@ -778,27 +778,49 @@ export class EditorGroupsControl implements IEditorGroupsControl, IVerticalSashL return; // need more editors } - // Minimize Others - if (arrangement === GroupArrangement.MINIMIZE_OTHERS) { - POSITIONS.forEach(position => { - if (this.visibleEditors[position]) { - if (position !== this.lastActivePosition) { - this.silosSize[position] = this.minSize; - availableSize -= this.minSize; + switch (arrangement) { + case GroupArrangement.MINIMIZE_OTHERS: + // Minimize Others + POSITIONS.forEach(position => { + if (this.visibleEditors[position]) { + if (position !== this.lastActivePosition) { + this.silosSize[position] = this.minSize; + availableSize -= this.minSize; + } } - } - }); + }); - this.silosSize[this.lastActivePosition] = availableSize; - } + this.silosSize[this.lastActivePosition] = availableSize; + break; + case GroupArrangement.EVEN: + // Even Sizes + POSITIONS.forEach(position => { + if (this.visibleEditors[position]) { + this.silosSize[position] = availableSize / visibleEditors; + } + }); + break; + default: + // Minimized editors should remain minimized, others should keep their relative Sizes + let oldNonMinimizedTotal = 0; + POSITIONS.forEach(position => { + if (this.visibleEditors[position]) { + if (this.silosMinimized[position]) { + this.silosSize[position] = this.minSize; + availableSize -= this.minSize; + } else { + oldNonMinimizedTotal += this.silosSize[position]; + } + } + }); - // Even Sizes - else if (arrangement === GroupArrangement.EVEN) { - POSITIONS.forEach(position => { - if (this.visibleEditors[position]) { - this.silosSize[position] = availableSize / visibleEditors; - } - }); + // Set size for non-minimized editors + const scaleFactor = availableSize / oldNonMinimizedTotal; + POSITIONS.forEach(position => { + if (this.visibleEditors[position] && !this.silosMinimized[position]) { + this.silosSize[position] *= scaleFactor; + } + }); } // Since we triggered a change in minimized/maximized editors, we need diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 7ede1f4eb2ba..ac4094b5fe15 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -19,7 +19,6 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { EditorLabel } from 'vs/workbench/browser/labels'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; @@ -59,7 +58,6 @@ export class TabsTitleControl extends TitleControl { constructor( @IContextMenuService contextMenuService: IContextMenuService, @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IEditorGroupService editorGroupService: IEditorGroupService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService, @@ -71,7 +69,7 @@ export class TabsTitleControl extends TitleControl { @IQuickOpenService quickOpenService: IQuickOpenService, @IWindowService private windowService: IWindowService ) { - super(contextMenuService, instantiationService, configurationService, editorService, editorGroupService, contextKeyService, keybindingService, telemetryService, messageService, menuService, quickOpenService); + super(contextMenuService, instantiationService, editorService, editorGroupService, contextKeyService, keybindingService, telemetryService, messageService, menuService, quickOpenService); this.tabDisposeables = []; this.editorLabels = []; @@ -218,6 +216,11 @@ export class TabsTitleControl extends TitleControl { // Container tabContainer.setAttribute('aria-label', `${name}, tab`); tabContainer.title = verboseDescription; + if (this.tabOptions.showTabCloseButton) { + DOM.removeClass(tabContainer, 'no-close-button'); + } else { + DOM.addClass(tabContainer, 'no-close-button'); + } // Label const tabLabel = this.editorLabels[index]; @@ -362,12 +365,6 @@ export class TabsTitleControl extends TitleControl { tabContainer.setAttribute('role', 'presentation'); // cannot use role "tab" here due to https://github.com/Microsoft/vscode/issues/8659 DOM.addClass(tabContainer, 'tab monaco-editor-background'); - if (!this.tabOptions.showTabCloseButton) { - DOM.addClass(tabContainer, 'no-close-button'); - } else { - DOM.removeClass(tabContainer, 'no-close-button'); - } - // Tab Editor Label const editorLabel = this.instantiationService.createInstance(EditorLabel, tabContainer, void 0); this.editorLabels.push(editorLabel); diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index d9747208de95..18a161598544 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -9,13 +9,12 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { Dimension, Builder } from 'vs/base/browser/builder'; import objects = require('vs/base/common/objects'); import errors = require('vs/base/common/errors'); +import types = require('vs/base/common/types'); import DOM = require('vs/base/browser/dom'); import { CodeEditor } from 'vs/editor/browser/codeEditor'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { EditorConfiguration } from 'vs/editor/common/config/commonEditorConfig'; -import { IEditorViewState, IEditor, IEditorOptions, EventType as EditorEventType } from 'vs/editor/common/editorCommon'; -import { IFilesConfiguration } from 'vs/platform/files/common/files'; +import { IEditorViewState, IEditor, IEditorOptions, EventType as EditorEventType, EditorType } from 'vs/editor/common/editorCommon'; import { Position } from 'vs/platform/editor/common/editor'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -34,6 +33,11 @@ interface ITextEditorViewState { 2?: IEditorViewState; } +interface IEditorConfiguration { + editor: any; + diffEditor: any; +} + /** * The base class of editors that leverage the text editor for the editing experience. This class is only intended to * be subclassed and not instantiated. @@ -56,14 +60,14 @@ export abstract class BaseTextEditor extends BaseEditor { super(id, telemetryService); this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.handleConfigurationChangeEvent(e.config))); - this.toUnbind.push(themeService.onDidColorThemeChange(_ => this.handleConfigurationChangeEvent())); + this.toUnbind.push(themeService.onDidColorThemeChange(e => this.handleConfigurationChangeEvent(this.configurationService.getConfiguration()))); } protected get instantiationService(): IInstantiationService { return this._instantiationService; } - private handleConfigurationChangeEvent(configuration?: any): void { + private handleConfigurationChangeEvent(configuration: IEditorConfiguration): void { if (this.isVisible()) { this.applyConfiguration(configuration); } else { @@ -73,28 +77,27 @@ export abstract class BaseTextEditor extends BaseEditor { private consumePendingConfigurationChangeEvent(): void { if (this.hasPendingConfigurationChange) { - this.applyConfiguration(this.configurationService.getConfiguration()); + this.applyConfiguration(this.configurationService.getConfiguration()); this.hasPendingConfigurationChange = false; } } - protected applyConfiguration(configuration?: any): void { + private applyConfiguration(configuration: IEditorConfiguration): void { if (!this.editorControl) { return; } - // Configuration & Options - if (configuration) { - const specificEditorSettings = this.getCodeEditorOptions(); - configuration = objects.clone(configuration); // dont modify original config - objects.assign(configuration[EditorConfiguration.EDITOR_SECTION], specificEditorSettings); - EditorConfiguration.apply(configuration, this.editorControl); + // Specific editor options always overwrite user configuration + const editorConfiguration = types.isObject(configuration.editor) ? objects.clone(configuration.editor) : Object.create(null); + objects.assign(editorConfiguration, this.getCodeEditorOptions()); + + // Handle diff editor specially by merging in diffEditor configuration + if (this.editorControl.getEditorType() === EditorType.IDiffEditor && types.isObject(configuration.diffEditor)) { + objects.mixin(editorConfiguration, configuration.diffEditor); } - // Just options - else { - this.editorControl.updateOptions(this.getCodeEditorOptions()); - } + // Apply to control + this.editorControl.updateOptions(editorConfiguration); } protected getCodeEditorOptions(): IEditorOptions { @@ -119,7 +122,7 @@ export abstract class BaseTextEditor extends BaseEditor { this.toUnbind.push(DOM.addDisposableListener(window, DOM.EventType.BLUR, () => this.onWindowFocusLost())); // Configuration - this.applyConfiguration(this.configurationService.getConfiguration()); + this.applyConfiguration(this.configurationService.getConfiguration()); } private onEditorFocusLost(): void { diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index bc9e500d5943..bd997951c328 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -23,7 +23,6 @@ import { IActionItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/a import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorGroupService, ITabOptions } from 'vs/workbench/services/group/common/groupService'; import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -94,7 +93,6 @@ export abstract class TitleControl implements ITitleAreaControl { constructor( @IContextMenuService protected contextMenuService: IContextMenuService, @IInstantiationService protected instantiationService: IInstantiationService, - @IConfigurationService protected configurationService: IConfigurationService, @IWorkbenchEditorService protected editorService: IWorkbenchEditorService, @IEditorGroupService protected editorGroupService: IEditorGroupService, @IContextKeyService protected contextKeyService: IContextKeyService, diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index c99ab5c20ac6..e1acf4872ec1 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -358,9 +358,9 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe let index = -1; let context: IEntryRunContext; - entries.forEach((entry, i) => { + entries.forEach(entry => { if (entry.shouldRunWithContext) { - index = i; + index = entry.index; context = entry.shouldRunWithContext; } }); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 4717f2ec35c5..dc18cc01c1f1 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -583,7 +583,7 @@ export class TextEditorOptions extends EditorOptions { public static from(input: IBaseResourceInput): TextEditorOptions { let options: TextEditorOptions = null; if (input && input.options) { - if (input.options.selection || input.options.forceOpen || input.options.revealIfVisible || input.options.preserveFocus || input.options.pinned || input.options.inactive || typeof input.options.index === 'number') { + if (input.options.selection || input.options.viewState || input.options.forceOpen || input.options.revealIfVisible || input.options.preserveFocus || input.options.pinned || input.options.inactive || typeof input.options.index === 'number') { options = new TextEditorOptions(); } @@ -616,6 +616,10 @@ export class TextEditorOptions extends EditorOptions { options.revealInCenterIfOutsideViewport = true; } + if (input.options.viewState) { + options.editorViewState = input.options.viewState; + } + if (typeof input.options.index === 'number') { options.index = input.options.index; } diff --git a/src/vs/workbench/common/editor/rangeDecorations.ts b/src/vs/workbench/common/editor/rangeDecorations.ts index 17e06a72c535..62c1ccd9ec18 100644 --- a/src/vs/workbench/common/editor/rangeDecorations.ts +++ b/src/vs/workbench/common/editor/rangeDecorations.ts @@ -62,7 +62,8 @@ export class RangeHighlightDecorations implements IDisposable { this.editor = editor; this.editorDisposables.push(this.editor.onDidChangeCursorPosition((e: editorCommon.ICursorPositionChangedEvent) => { if ( - e.reason === editorCommon.CursorChangeReason.Explicit + e.reason === editorCommon.CursorChangeReason.NotSet + || e.reason === editorCommon.CursorChangeReason.Explicit || e.reason === editorCommon.CursorChangeReason.Undo || e.reason === editorCommon.CursorChangeReason.Redo ) { @@ -95,8 +96,10 @@ export class RangeHighlightDecorations implements IDisposable { } public dispose() { - this.removeHighlightRange(); - this.disposeEditorListeners(); - this.editor = null; + if (this.editor && this.editor.getModel()) { + this.removeHighlightRange(); + this.disposeEditorListeners(); + this.editor = null; + } } } \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index 7d3dead920ee..42097c6ebb8e 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -396,7 +396,8 @@ export class ShowStartupPerformance extends Action { const metrics: IStartupMetrics = this.timerService.startupMetrics; if (metrics.initialStartup) { - table.push({ Topic: '[main] start => window.loadUrl()', 'Took (ms)': metrics.timers.ellapsedWindowLoad }); + table.push({ Topic: '[main] start => app.isReady', 'Took (ms)': metrics.timers.ellapsedAppReady }); + table.push({ Topic: '[main] app.isReady => window.loadUrl()', 'Took (ms)': metrics.timers.ellapsedWindowLoad }); } table.push({ Topic: '[renderer] window.loadUrl() => begin to require(workbench.main.js)', 'Took (ms)': metrics.timers.ellapsedWindowLoadToRequire }); diff --git a/src/vs/workbench/electron-browser/bootstrap/index.js b/src/vs/workbench/electron-browser/bootstrap/index.js index b03d213e3483..5d438e137a2c 100644 --- a/src/vs/workbench/electron-browser/bootstrap/index.js +++ b/src/vs/workbench/electron-browser/bootstrap/index.js @@ -16,24 +16,7 @@ const ipc = electron.ipcRenderer; process.lazyEnv = new Promise(function (resolve) { - - const origEnv = process.env; - - // warn about missing environment variables - // while we are resolve lazyEnv - process.env = new Proxy(origEnv, { - get: function (target, name) { - const result = target[name]; - if (typeof result === 'undefined') { - console.warn('process.env[\'' + name + '\'] is undefined AND \'process.lazyEnv\' is NOT READY yet.'); - } - return result; - } - }); - ipc.once('vscode:acceptShellEnv', function (event, shellEnv) { - // store process.env, mixin shellEnv, done - process.env = origEnv; assign(process.env, shellEnv); resolve(process.env); }); @@ -202,6 +185,7 @@ function main() { isInitialStartup: !!configuration.isInitialStartup, hasAccessibilitySupport: !!configuration.accessibilitySupport, start: new Date(configuration.perfStartTime), + appReady: new Date(configuration.perfAppReady), windowLoad: new Date(configuration.perfWindowLoadTime), beforeLoadWorkbenchMain: new Date() }; diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index cc391d5aa7da..4f2a20f03730 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -14,7 +14,6 @@ import { domContentLoaded } from 'vs/base/browser/dom'; import errors = require('vs/base/common/errors'); import platform = require('vs/base/common/platform'); import paths = require('vs/base/common/paths'); -import timer = require('vs/base/common/timer'); import uri from 'vs/base/common/uri'; import strings = require('vs/base/common/strings'); import { IResourceInput } from 'vs/platform/editor/common/editor'; @@ -62,10 +61,6 @@ export function startup(configuration: IWindowConfiguration): TPromise { filesToDiff }; - if (configuration.performance) { - timer.ENABLE_TIMER = true; - } - // Resolve workspace return getWorkspace(configuration.workspacePath).then(workspace => { @@ -171,4 +166,4 @@ function loaderError(err: Error): Error { } return new Error(nls.localize('loaderErrorNative', "Failed to load a required file. Please restart the application to try again. Details: {0}", JSON.stringify(err))); -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/debug/browser/debugActions.ts b/src/vs/workbench/parts/debug/browser/debugActions.ts index 2f2f422e136c..997807ba6e98 100644 --- a/src/vs/workbench/parts/debug/browser/debugActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugActions.ts @@ -717,15 +717,7 @@ export class FocusProcessAction extends AbstractDebugAction { return this.debugService.focusStackFrameAndEvaluate(null, process).then(() => { const stackFrame = this.debugService.getViewModel().focusedStackFrame; if (stackFrame) { - return this.editorService.openEditor({ - resource: stackFrame.source.uri, - options: { - preserveFocus: true, - selection: { startLineNumber: stackFrame.lineNumber, startColumn: 1 }, - revealIfVisible: true, - revealInCenterIfOutsideViewport: true - } - }); + return stackFrame.openInEditor(this.editorService, true); } }); } diff --git a/src/vs/workbench/parts/debug/browser/debugContentProvider.ts b/src/vs/workbench/parts/debug/browser/debugContentProvider.ts index e74b027c1883..ceb3ae212006 100644 --- a/src/vs/workbench/parts/debug/browser/debugContentProvider.ts +++ b/src/vs/workbench/parts/debug/browser/debugContentProvider.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as lifecycle from 'vs/base/common/lifecycle'; import uri from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { guessMimeTypes } from 'vs/base/common/mime'; @@ -10,13 +11,15 @@ import { IModel } from 'vs/editor/common/editorCommon'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ITextModelResolverService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; -import { DEBUG_SCHEME, IDebugService } from 'vs/workbench/parts/debug/common/debug'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { DEBUG_SCHEME, IDebugService, State } from 'vs/workbench/parts/debug/common/debug'; import { Model } from 'vs/workbench/parts/debug/common/debugModel'; import { Source } from 'vs/workbench/parts/debug/common/debugSource'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; export class DebugContentProvider implements IWorkbenchContribution, ITextModelContentProvider { + private modelsToDispose: IModel[]; + constructor( @ITextModelResolverService textModelResolverService: ITextModelResolverService, @IDebugService private debugService: IDebugService, @@ -24,6 +27,12 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC @IModeService private modeService: IModeService ) { textModelResolverService.registerTextModelContentProvider(DEBUG_SCHEME, this); + this.modelsToDispose = []; + this.debugService.onDidChangeState(() => { + if (this.debugService.state === State.Inactive) { + this.modelsToDispose = lifecycle.dispose(this.modelsToDispose); + } + }); } public getId(): string { @@ -39,8 +48,10 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC return process.session.source({ sourceReference: Source.getSourceReference(resource) }).then(response => { const mime = response.body.mimeType || guessMimeTypes(resource.toString())[0]; const modePromise = this.modeService.getOrCreateMode(mime); + const model = this.modelService.createModel(response.body.content, modePromise, resource); + this.modelsToDispose.push(model); - return this.modelService.createModel(response.body.content, modePromise, resource); + return model; }, err => { (this.debugService.getModel()).sourceIsUnavailable(resource); return err; diff --git a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts b/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts index dac4d20c36e9..258c0a29a136 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts +++ b/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts @@ -18,7 +18,7 @@ interface IDebugEditorModelData { toDispose: lifecycle.IDisposable[]; breakpointDecorationIds: string[]; breakpointLines: number[]; - breakpointDecorationsAsMap: { [decorationId: string]: boolean; }; + breakpointDecorationsAsMap: Map; currentStackDecorations: string[]; topStackFrameRange: IRange; dirty: boolean; @@ -29,16 +29,14 @@ const stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges; export class DebugEditorModelManager implements IWorkbenchContribution { static ID = 'breakpointManager'; - private modelData: { - [modelUrl: string]: IDebugEditorModelData; - }; + private modelDataMap: Map; private toDispose: lifecycle.IDisposable[]; constructor( @IModelService private modelService: IModelService, @IDebugService private debugService: IDebugService ) { - this.modelData = Object.create(null); + this.modelDataMap = new Map(); this.toDispose = []; this.registerListeners(); } @@ -48,14 +46,14 @@ export class DebugEditorModelManager implements IWorkbenchContribution { } public dispose(): void { - Object.keys(this.modelData).forEach(modelUriStr => { - lifecycle.dispose(this.modelData[modelUriStr].toDispose); - this.modelData[modelUriStr].model.deltaDecorations(this.modelData[modelUriStr].breakpointDecorationIds, []); - this.modelData[modelUriStr].model.deltaDecorations(this.modelData[modelUriStr].currentStackDecorations, []); + this.modelDataMap.forEach(modelData => { + lifecycle.dispose(modelData.toDispose); + modelData.model.deltaDecorations(modelData.breakpointDecorationIds, []); + modelData.model.deltaDecorations(modelData.currentStackDecorations, []); }); this.toDispose = lifecycle.dispose(this.toDispose); - this.modelData = null; + this.modelDataMap.clear(); } private registerListeners(): void { @@ -67,7 +65,7 @@ export class DebugEditorModelManager implements IWorkbenchContribution { this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(() => this.onFocusStackFrame())); this.toDispose.push(this.debugService.onDidChangeState(() => { if (this.debugService.state === State.Inactive) { - Object.keys(this.modelData).forEach(key => this.modelData[key].dirty = false); + this.modelDataMap.forEach(modelData => modelData.dirty = false); } })); } @@ -80,40 +78,41 @@ export class DebugEditorModelManager implements IWorkbenchContribution { const breakPointDecorations = model.deltaDecorations([], this.createBreakpointDecorations(breakpoints)); const toDispose: lifecycle.IDisposable[] = [model.onDidChangeDecorations((e) => this.onModelDecorationsChanged(modelUrlStr, e))]; + const breakpointDecorationsAsMap = new Map(); + breakPointDecorations.forEach(bpd => breakpointDecorationsAsMap.set(bpd, true)); - this.modelData[modelUrlStr] = { + this.modelDataMap.set(modelUrlStr, { model: model, toDispose: toDispose, breakpointDecorationIds: breakPointDecorations, breakpointLines: breakpoints.map(bp => bp.lineNumber), - breakpointDecorationsAsMap: objects.toObject(breakPointDecorations, key => key, key => true), + breakpointDecorationsAsMap, currentStackDecorations: currentStackDecorations, topStackFrameRange: null, dirty: false - }; + }); } private onModelRemoved(model: IModel): void { const modelUriStr = model.uri.toString(); - if (this.modelData[modelUriStr]) { - lifecycle.dispose(this.modelData[modelUriStr].toDispose); - delete this.modelData[modelUriStr]; + if (this.modelDataMap.has(modelUriStr)) { + lifecycle.dispose(this.modelDataMap.get(modelUriStr).toDispose); + this.modelDataMap.delete(modelUriStr); } } // call stack management. Represent data coming from the debug service. private onFocusStackFrame(): void { - Object.keys(this.modelData).forEach(modelUrlStr => { - const modelData = this.modelData[modelUrlStr]; - modelData.currentStackDecorations = modelData.model.deltaDecorations(modelData.currentStackDecorations, this.createCallStackDecorations(modelUrlStr)); + this.modelDataMap.forEach((modelData, uri) => { + modelData.currentStackDecorations = modelData.model.deltaDecorations(modelData.currentStackDecorations, this.createCallStackDecorations(uri)); }); } - private createCallStackDecorations(modelUrlStr: string): IModelDeltaDecoration[] { + private createCallStackDecorations(modelUriStr: string): IModelDeltaDecoration[] { const result: IModelDeltaDecoration[] = []; const stackFrame = this.debugService.getViewModel().focusedStackFrame; - if (!stackFrame || stackFrame.source.uri.toString() !== modelUrlStr) { + if (!stackFrame || stackFrame.source.uri.toString() !== modelUriStr) { return result; } @@ -140,15 +139,16 @@ export class DebugEditorModelManager implements IWorkbenchContribution { range: wholeLineRange }); - if (this.modelData[modelUrlStr]) { - if (this.modelData[modelUrlStr].topStackFrameRange && this.modelData[modelUrlStr].topStackFrameRange.startLineNumber === wholeLineRange.startLineNumber && - this.modelData[modelUrlStr].topStackFrameRange.startColumn !== wholeLineRange.startColumn) { + if (this.modelDataMap.has(modelUriStr)) { + const modelData = this.modelDataMap.get(modelUriStr); + if (modelData.topStackFrameRange && modelData.topStackFrameRange.startLineNumber === wholeLineRange.startLineNumber && + modelData.topStackFrameRange.startColumn !== wholeLineRange.startColumn) { result.push({ options: DebugEditorModelManager.TOP_STACK_FRAME_COLUMN_DECORATION, range: wholeLineRange }); } - this.modelData[modelUrlStr].topStackFrameRange = wholeLineRange; + modelData.topStackFrameRange = wholeLineRange; } } } else { @@ -168,22 +168,21 @@ export class DebugEditorModelManager implements IWorkbenchContribution { // breakpoints management. Represent data coming from the debug service and also send data back. private onModelDecorationsChanged(modelUrlStr: string, e: IModelDecorationsChangedEvent): void { - const modelData = this.modelData[modelUrlStr]; - let myDecorationsCount = Object.keys(modelData.breakpointDecorationsAsMap).length; - if (myDecorationsCount === 0) { + const modelData = this.modelDataMap.get(modelUrlStr); + if (modelData.breakpointDecorationsAsMap.size === 0) { // I have no decorations return; } - if (!e.changedDecorations.some(decorationId => modelData.breakpointDecorationsAsMap[decorationId])) { + if (!e.changedDecorations.some(decorationId => modelData.breakpointDecorationsAsMap.has(decorationId))) { // nothing to do, my decorations did not change. return; } const data: IRawBreakpoint[] = []; - const lineToBreakpointDataMap: { [key: number]: IBreakpoint } = {}; + const lineToBreakpointDataMap = new Map(); this.debugService.getModel().getBreakpoints().filter(bp => bp.uri.toString() === modelUrlStr).forEach(bp => { - lineToBreakpointDataMap[bp.lineNumber] = bp; + lineToBreakpointDataMap.set(bp.lineNumber, bp); }); const modelUri = modelData.model.uri; @@ -192,13 +191,14 @@ export class DebugEditorModelManager implements IWorkbenchContribution { const lineNumber = modelData.breakpointLines[i]; // check if the line got deleted. if (decorationRange.endColumn - decorationRange.startColumn > 0) { + const breakpoint = lineToBreakpointDataMap.get(lineNumber); // since we know it is collapsed, it cannot grow to multiple lines data.push({ lineNumber: decorationRange.startLineNumber, - enabled: lineToBreakpointDataMap[lineNumber].enabled, - condition: lineToBreakpointDataMap[lineNumber].condition, - hitCondition: lineToBreakpointDataMap[lineNumber].hitCondition, - column: lineToBreakpointDataMap[lineNumber].column + enabled: breakpoint.enabled, + condition: breakpoint.condition, + hitCondition: breakpoint.hitCondition, + column: breakpoint.column }); } } @@ -213,31 +213,32 @@ export class DebugEditorModelManager implements IWorkbenchContribution { } private onBreakpointsChange(): void { - const breakpointsMap: { [key: string]: IBreakpoint[] } = Object.create(null); + const breakpointsMap = new Map(); this.debugService.getModel().getBreakpoints().forEach(bp => { const uriStr = bp.uri.toString(); - if (breakpointsMap[uriStr]) { - breakpointsMap[uriStr].push(bp); + if (breakpointsMap.has(uriStr)) { + breakpointsMap.get(uriStr).push(bp); } else { - breakpointsMap[uriStr] = [bp]; + breakpointsMap.set(uriStr, [bp]); } }); - Object.keys(breakpointsMap).forEach(modelUriStr => { - if (this.modelData[modelUriStr]) { - this.updateBreakpoints(this.modelData[modelUriStr], breakpointsMap[modelUriStr]); + breakpointsMap.forEach((bps, uri) => { + if (this.modelDataMap.has(uri)) { + this.updateBreakpoints(this.modelDataMap.get(uri), breakpointsMap.get(uri)); } }); - Object.keys(this.modelData).forEach(modelUriStr => { - if (!breakpointsMap[modelUriStr]) { - this.updateBreakpoints(this.modelData[modelUriStr], []); + this.modelDataMap.forEach((modelData, uri) => { + if (!breakpointsMap.has(uri)) { + this.updateBreakpoints(modelData, []); } }); } private updateBreakpoints(modelData: IDebugEditorModelData, newBreakpoints: IBreakpoint[]): void { modelData.breakpointDecorationIds = modelData.model.deltaDecorations(modelData.breakpointDecorationIds, this.createBreakpointDecorations(newBreakpoints)); - modelData.breakpointDecorationsAsMap = objects.toObject(modelData.breakpointDecorationIds, key => key, (key) => true); + modelData.breakpointDecorationsAsMap.clear(); + modelData.breakpointDecorationIds.forEach(id => modelData.breakpointDecorationsAsMap.set(id, true)); modelData.breakpointLines = newBreakpoints.map(bp => bp.lineNumber); } @@ -254,7 +255,7 @@ export class DebugEditorModelManager implements IWorkbenchContribution { const activated = this.debugService.getModel().areBreakpointsActivated(); const state = this.debugService.state; const debugActive = state === State.Running || state === State.Stopped || state === State.Initializing; - const modelData = this.modelData[breakpoint.uri.toString()]; + const modelData = this.modelDataMap.get(breakpoint.uri.toString()); let result = (!breakpoint.enabled || !activated) ? DebugEditorModelManager.BREAKPOINT_DISABLED_DECORATION : debugActive && modelData && modelData.dirty && !breakpoint.verified ? DebugEditorModelManager.BREAKPOINT_DIRTY_DECORATION : diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 86188d6eb8de..c665252b3e51 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -14,6 +14,7 @@ import { ISuggestion } from 'vs/editor/common/modes'; import { Source } from 'vs/workbench/parts/debug/common/debugSource'; import { Range } from 'vs/editor/common/core/range'; import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; export const VIEWLET_ID = 'workbench.view.debug'; export const REPL_ID = 'workbench.panel.repl'; @@ -170,6 +171,7 @@ export interface IStackFrame extends ITreeElement { getScopes(): TPromise; restart(): TPromise; toString(): string; + openInEditor(editorService: IWorkbenchEditorService, preserveFocus?: boolean, sideBySide?: boolean): TPromise; } export interface IEnablement extends ITreeElement { diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index 23a4ebba24f9..5b97853ef25b 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -19,6 +19,7 @@ import { ISuggestion } from 'vs/editor/common/modes'; import { Position } from 'vs/editor/common/core/position'; import * as debug from 'vs/workbench/parts/debug/common/debug'; import { Source } from 'vs/workbench/parts/debug/common/debugSource'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; const MAX_REPL_LENGTH = 10000; const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source"); @@ -71,7 +72,7 @@ export class OutputNameValueElement extends AbstractOutputElement implements deb } public get hasChildren(): boolean { - return isObject(this.valueObj) && Object.getOwnPropertyNames(this.valueObj).length > 0; + return (Array.isArray(this.valueObj) && this.valueObj.length > 0) || (isObject(this.valueObj) && Object.getOwnPropertyNames(this.valueObj).length > 0); } public getChildren(): TPromise { @@ -90,7 +91,7 @@ export class OutputNameValueElement extends AbstractOutputElement implements deb export class ExpressionContainer implements debug.IExpressionContainer { - public static allValues: { [id: string]: string } = {}; + public static allValues: Map = new Map(); // Use chunks to support variable paging #9537 private static BASE_CHUNK_SIZE = 100; @@ -173,9 +174,9 @@ export class ExpressionContainer implements debug.IExpressionContainer { public set value(value: string) { this._value = value; - this.valueChanged = ExpressionContainer.allValues[this.getId()] && - ExpressionContainer.allValues[this.getId()] !== Expression.DEFAULT_VALUE && ExpressionContainer.allValues[this.getId()] !== value; - ExpressionContainer.allValues[this.getId()] = value; + this.valueChanged = ExpressionContainer.allValues.get(this.getId()) && + ExpressionContainer.allValues.get(this.getId()) !== Expression.DEFAULT_VALUE && ExpressionContainer.allValues.get(this.getId()) !== value; + ExpressionContainer.allValues.set(this.getId(), value); } } @@ -349,7 +350,20 @@ export class StackFrame implements debug.IStackFrame { } public toString(): string { - return `${this.name} (${this.source.name}:${this.lineNumber})`; + return `${this.name} (${this.source.inMemory ? this.source.name : this.source.uri.fsPath}:${this.lineNumber})`; + } + + public openInEditor(editorService: IWorkbenchEditorService, preserveFocus?: boolean, sideBySide?: boolean): TPromise { + return editorService.openEditor({ + resource: this.source.uri, + description: this.source.origin, + options: { + preserveFocus, + selection: { startLineNumber: this.lineNumber, startColumn: 1 }, + revealIfVisible: true, + revealInCenterIfOutsideViewport: true + } + }, sideBySide); } } @@ -463,10 +477,10 @@ export class Thread implements debug.IThread { export class Process implements debug.IProcess { - private threads: { [reference: number]: Thread; }; + private threads: Map; constructor(public name: string, private _session: debug.ISession & debug.ITreeElement) { - this.threads = {}; + this.threads = new Map(); } public get session(): debug.ISession { @@ -474,11 +488,13 @@ export class Process implements debug.IProcess { } public getThread(threadId: number): Thread { - return this.threads[threadId]; + return this.threads.get(threadId); } public getAllThreads(): debug.IThread[] { - return Object.keys(this.threads).map(key => this.threads[key]); + const result = []; + this.threads.forEach(t => result.push(t)); + return result; } public getId(): string { @@ -487,66 +503,66 @@ export class Process implements debug.IProcess { public rawUpdate(data: debug.IRawModelUpdate): void { - if (data.thread && !this.threads[data.threadId]) { + if (data.thread && !this.threads.has(data.threadId)) { // A new thread came in, initialize it. - this.threads[data.threadId] = new Thread(this, data.thread.name, data.thread.id); + this.threads.set(data.threadId, new Thread(this, data.thread.name, data.thread.id)); } if (data.stoppedDetails) { // Set the availability of the threads' callstacks depending on // whether the thread is stopped or not if (data.allThreadsStopped) { - Object.keys(this.threads).forEach(ref => { + this.threads.forEach(thread => { // Only update the details if all the threads are stopped // because we don't want to overwrite the details of other // threads that have stopped for a different reason - this.threads[ref].stoppedDetails = clone(data.stoppedDetails); - this.threads[ref].stopped = true; - this.threads[ref].clearCallStack(); + thread.stoppedDetails = clone(data.stoppedDetails); + thread.stopped = true; + thread.clearCallStack(); }); - } else { + } else if (this.threads.has(data.threadId)) { // One thread is stopped, only update that thread. - this.threads[data.threadId].stoppedDetails = data.stoppedDetails; - this.threads[data.threadId].clearCallStack(); - this.threads[data.threadId].stopped = true; + const thread = this.threads.get(data.threadId); + thread.stoppedDetails = data.stoppedDetails; + thread.clearCallStack(); + thread.stopped = true; } } } public clearThreads(removeThreads: boolean, reference: number = undefined): void { if (reference) { - if (this.threads[reference]) { - this.threads[reference].clearCallStack(); - this.threads[reference].stoppedDetails = undefined; - this.threads[reference].stopped = false; + if (this.threads.has(reference)) { + const thread = this.threads.get(reference); + thread.clearCallStack(); + thread.stoppedDetails = undefined; + thread.stopped = false; if (removeThreads) { - delete this.threads[reference]; + this.threads.delete(reference); } } } else { - Object.keys(this.threads).forEach(ref => { - this.threads[ref].clearCallStack(); - this.threads[ref].stoppedDetails = undefined; - this.threads[ref].stopped = false; + this.threads.forEach(thread => { + thread.clearCallStack(); + thread.stoppedDetails = undefined; + thread.stopped = false; }); if (removeThreads) { - this.threads = {}; - ExpressionContainer.allValues = {}; + this.threads.clear(); + ExpressionContainer.allValues.clear(); } } } public sourceIsUnavailable(uri: uri): void { - Object.keys(this.threads).forEach(key => { - if (this.threads[key].getCachedCallStack()) { - this.threads[key].getCachedCallStack().forEach(stackFrame => { - if (stackFrame.source.uri.toString() === uri.toString()) { - stackFrame.source.available = false; - } - }); - } + this.threads.forEach(thread => { + thread.getCallStack().forEach(stackFrame => { + if (stackFrame.source.uri.toString() === uri.toString()) { + stackFrame.source.available = false; + } + }); }); } diff --git a/src/vs/workbench/parts/debug/common/debugProtocol.d.ts b/src/vs/workbench/parts/debug/common/debugProtocol.d.ts index 790de1fcc919..21c69e610901 100644 --- a/src/vs/workbench/parts/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/parts/debug/common/debugProtocol.d.ts @@ -1042,11 +1042,11 @@ declare module DebugProtocol { /** A Source is a descriptor for source code. It is returned from the debug adapter as part of a StackFrame and it is used by clients when specifying breakpoints. */ export interface Source { - /** The short name of the source. Every source returned from the debug adapter has a name. When specifying a source to the debug adapter this name is optional. */ + /** The short name of the source. Every source returned from the debug adapter has a name. When sending a source to the debug adapter this name is optional. */ name?: string; - /** The long (absolute) path of the source. It is not guaranteed that the source exists at this location. */ + /** The path of the source to be shown in the UI. It is only used to locate and load the content of the source if no sourceReference is specified (or its vaule is 0). */ path?: string; - /** If sourceReference > 0 the contents of the source can be retrieved through the SourceRequest. A sourceReference is only valid for a session, so it must not be used to persist a source. */ + /** If sourceReference > 0 the contents of the source must be retrieved through the SourceRequest (even if a path is specified). A sourceReference is only valid for a session, so it must not be used to persist a source. */ sourceReference?: number; /** The (optional) origin of this source: possible values 'internal module', 'inlined content from source map', etc. */ origin?: string; @@ -1218,7 +1218,7 @@ declare module DebugProtocol { } /** Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them. */ - export type CompletionItemType = 'method' | 'function' | 'constructor' | 'field' | 'variable' | 'class' | 'interface' | 'module' | 'property' | 'unit' | 'value' | 'enum' | 'keyword' | 'snippet' | 'text' | 'color' | 'file' | 'reference' | 'customcolor' | 'folder'; + export type CompletionItemType = 'method' | 'function' | 'constructor' | 'field' | 'variable' | 'class' | 'interface' | 'module' | 'property' | 'unit' | 'value' | 'enum' | 'keyword' | 'snippet' | 'text' | 'color' | 'file' | 'reference' | 'customcolor'; /** Names of checksum algorithms that may be supported by a debug adapter. */ export type ChecksumAlgorithm = 'MD5' | 'SHA1' | 'SHA256' | 'SHA1Normalized' | 'SHA256Normalized' | 'timestamp'; diff --git a/src/vs/workbench/parts/debug/common/debugSource.ts b/src/vs/workbench/parts/debug/common/debugSource.ts index df728f73675c..e51a24cfefb8 100644 --- a/src/vs/workbench/parts/debug/common/debugSource.ts +++ b/src/vs/workbench/parts/debug/common/debugSource.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import uri from 'vs/base/common/uri'; -import * as paths from 'vs/base/common/paths'; import { DEBUG_SCHEME } from 'vs/workbench/parts/debug/common/debug'; export class Source { @@ -15,7 +14,8 @@ export class Source { private static INTERNAL_URI_PREFIX = `${DEBUG_SCHEME}://internal/`; constructor(public raw: DebugProtocol.Source, available = true) { - this.uri = raw.path ? uri.file(paths.normalize(raw.path)) : uri.parse(Source.INTERNAL_URI_PREFIX + raw.sourceReference + '/' + raw.name); + const path = raw.path || raw.name; + this.uri = raw.sourceReference > 0 ? uri.parse(Source.INTERNAL_URI_PREFIX + raw.sourceReference + '/' + path) : uri.file(path); this.available = available; } diff --git a/src/vs/workbench/parts/debug/common/replHistory.ts b/src/vs/workbench/parts/debug/common/replHistory.ts index 9ed159fe91be..92beb99600d6 100644 --- a/src/vs/workbench/parts/debug/common/replHistory.ts +++ b/src/vs/workbench/parts/debug/common/replHistory.ts @@ -17,12 +17,12 @@ export class ReplHistory { private historyPointer: number; private currentExpressionStoredMarkers: boolean; - private historyOverwrites: { [position: string]: string; }; + private historyOverwrites: Map; constructor(private history: string[]) { this.historyPointer = this.history.length; this.currentExpressionStoredMarkers = false; - this.historyOverwrites = {}; + this.historyOverwrites = new Map(); } public next(): string { @@ -48,8 +48,8 @@ export class ReplHistory { this.historyPointer = newPointer; // check for overwrite - if (this.historyOverwrites && this.historyOverwrites[newPointer.toString()]) { - return this.historyOverwrites[newPointer.toString()]; + if (this.historyOverwrites.has(newPointer.toString())) { + return this.historyOverwrites.get(newPointer.toString()); } return this.history[newPointer]; @@ -78,11 +78,7 @@ export class ReplHistory { // keep edits that are made to history items up until the user actually evaluates a expression else { - if (!this.historyOverwrites) { - this.historyOverwrites = {}; - } - - this.historyOverwrites[previousPointer.toString()] = expression; + this.historyOverwrites.set(previousPointer.toString(), expression); } } @@ -104,7 +100,7 @@ export class ReplHistory { this.currentExpressionStoredMarkers = false; // reset overwrites - this.historyOverwrites = null; + this.historyOverwrites.clear(); } public save(): string[] { diff --git a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts index 0f9d61b48278..828364cbc408 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts @@ -133,7 +133,16 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0), handler(accessor: ServicesAccessor, configurationOrName: any) { const debugService = accessor.get(IDebugService); - return debugService.createProcess(configurationOrName || debugService.getViewModel().selectedConfigurationName); + if (!configurationOrName) { + const viewModel = debugService.getViewModel(); + if (!viewModel.selectedConfigurationName) { + const name = debugService.getConfigurationManager().getConfigurationNames().shift(); + viewModel.setSelectedConfigurationName(name); + } + configurationOrName = viewModel.selectedConfigurationName; + } + + return debugService.createProcess(configurationOrName); }, when: CONTEXT_NOT_IN_DEBUG_MODE, primary: undefined diff --git a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts index 3519e4256495..ecec45008b08 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts @@ -193,7 +193,7 @@ jsonRegistry.registerSchema(schemaId, schema); export class ConfigurationManager implements debug.IConfigurationManager { private adapters: Adapter[]; - private allModeIdsForBreakpoints: { [key: string]: boolean }; + private breakpointModeIdsSet: Set; constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService, @@ -208,7 +208,7 @@ export class ConfigurationManager implements debug.IConfigurationManager { ) { this.adapters = []; this.registerListeners(); - this.allModeIdsForBreakpoints = {}; + this.breakpointModeIdsSet = new Set(); } private registerListeners(): void { @@ -220,7 +220,7 @@ export class ConfigurationManager implements debug.IConfigurationManager { } if (rawAdapter.enableBreakpointsFor) { rawAdapter.enableBreakpointsFor.languageIds.forEach(modeId => { - this.allModeIdsForBreakpoints[modeId] = true; + this.breakpointModeIdsSet.add(modeId); }); } @@ -250,7 +250,7 @@ export class ConfigurationManager implements debug.IConfigurationManager { breakpointsExtPoint.setHandler(extensions => { extensions.forEach(ext => { ext.value.forEach(breakpoints => { - this.allModeIdsForBreakpoints[breakpoints.language] = true; + this.breakpointModeIdsSet.add(breakpoints.language); }); }); }); @@ -386,6 +386,6 @@ export class ConfigurationManager implements debug.IConfigurationManager { const mode = model ? model.getMode() : null; const modeId = mode ? mode.getId() : null; - return !!this.allModeIdsForBreakpoints[modeId]; + return this.breakpointModeIdsSet.has(modeId); } } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index ad7f18836986..75c04ff7cded 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -59,7 +59,7 @@ const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname'; export class DebugService implements debug.IDebugService { public _serviceBrand: any; - private sessionStates: { [id: string]: debug.State }; + private sessionStates: Map; private _onDidChangeState: Emitter; private model: Model; private viewModel: ViewModel; @@ -67,9 +67,9 @@ export class DebugService implements debug.IDebugService { private customTelemetryService: ITelemetryService; private lastTaskEvent: TaskEvent; private toDispose: lifecycle.IDisposable[]; - private toDisposeOnSessionEnd: { [id: string]: lifecycle.IDisposable[] }; + private toDisposeOnSessionEnd: Map; private inDebugMode: IContextKey; - private breakpointsToSendOnResourceSaved: { [uri: string]: boolean }; + private breakpointsToSendOnResourceSaved: Set; constructor( @IStorageService private storageService: IStorageService, @@ -93,10 +93,10 @@ export class DebugService implements debug.IDebugService { @IConfigurationService private configurationService: IConfigurationService ) { this.toDispose = []; - this.toDisposeOnSessionEnd = {}; - this.breakpointsToSendOnResourceSaved = {}; + this.toDisposeOnSessionEnd = new Map(); + this.breakpointsToSendOnResourceSaved = new Set(); this._onDidChangeState = new Emitter(); - this.sessionStates = {}; + this.sessionStates = new Map(); this.configurationManager = this.instantiationService.createInstance(ConfigurationManager); this.inDebugMode = debug.CONTEXT_IN_DEBUG_MODE.bindTo(contextKeyService); @@ -236,8 +236,8 @@ export class DebugService implements debug.IDebugService { } private registerSessionListeners(process: Process, session: RawDebugSession): void { - this.toDisposeOnSessionEnd[session.getId()].push(session); - this.toDisposeOnSessionEnd[session.getId()].push(session.onDidInitialize(event => { + this.toDisposeOnSessionEnd.get(session.getId()).push(session); + this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidInitialize(event => { aria.status(nls.localize('debuggingStarted', "Debugging started.")); const sendConfigurationDone = () => { if (session && session.configuration.capabilities.supportsConfigurationDoneRequest) { @@ -255,7 +255,7 @@ export class DebugService implements debug.IDebugService { .done(() => this.fetchThreads(session), errors.onUnexpectedError); })); - this.toDisposeOnSessionEnd[session.getId()].push(session.onDidStop(event => { + this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidStop(event => { this.setStateAndEmit(session.getId(), debug.State.Stopped); const threadId = event.body.threadId; @@ -283,21 +283,14 @@ export class DebugService implements debug.IDebugService { this.windowService.getWindow().focus(); aria.alert(nls.localize('debuggingPaused', "Debugging paused, reason {0}, {1} {2}", event.body.reason, stackFrameToFocus.source ? stackFrameToFocus.source.name : '', stackFrameToFocus.lineNumber)); - return this.editorService.openEditor({ - resource: stackFrameToFocus.source.uri, - options: { - selection: { startLineNumber: stackFrameToFocus.lineNumber, startColumn: 1 }, - revealIfVisible: true, - revealInCenterIfOutsideViewport: true - } - }); + return stackFrameToFocus.openInEditor(this.editorService); } }); } }, errors.onUnexpectedError); })); - this.toDisposeOnSessionEnd[session.getId()].push(session.onDidThread(event => { + this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidThread(event => { if (event.body.reason === 'started') { this.fetchThreads(session).done(undefined, errors.onUnexpectedError); } else if (event.body.reason === 'exited') { @@ -305,7 +298,7 @@ export class DebugService implements debug.IDebugService { } })); - this.toDisposeOnSessionEnd[session.getId()].push(session.onDidTerminateDebugee(event => { + this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidTerminateDebugee(event => { aria.status(nls.localize('debuggingStopped', "Debugging stopped.")); if (session && session.getId() === event.body.sessionId) { if (event.body && typeof event.body.restart === 'boolean' && event.body.restart) { @@ -316,7 +309,7 @@ export class DebugService implements debug.IDebugService { } })); - this.toDisposeOnSessionEnd[session.getId()].push(session.onDidContinued(event => { + this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidContinued(event => { const threadId = event.body.allThreadsContinued ? undefined : event.body.threadId; this.model.clearThreads(session.getId(), false, threadId); if (this.viewModel.focusedProcess.getId() === session.getId()) { @@ -325,7 +318,7 @@ export class DebugService implements debug.IDebugService { this.setStateAndEmit(session.getId(), session.requestType === debug.SessionRequestType.LAUNCH_NO_DEBUG ? debug.State.RunningNoDebug : debug.State.Running); })); - this.toDisposeOnSessionEnd[session.getId()].push(session.onDidOutput(event => { + this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidOutput(event => { if (!event.body) { return; } @@ -351,7 +344,7 @@ export class DebugService implements debug.IDebugService { } })); - this.toDisposeOnSessionEnd[session.getId()].push(session.onDidBreakpoint(event => { + this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidBreakpoint(event => { const id = event.body && event.body.breakpoint ? event.body.breakpoint.id : undefined; const breakpoint = this.model.getBreakpoints().filter(bp => bp.idFromAdapter === id).pop(); if (breakpoint) { @@ -364,9 +357,9 @@ export class DebugService implements debug.IDebugService { } })); - this.toDisposeOnSessionEnd[session.getId()].push(session.onDidExitAdapter(event => { + this.toDisposeOnSessionEnd.get(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) { + if (session && session.configuration.type === 'extensionHost' && this.sessionStates.get(session.getId()) === debug.State.RunningNoDebug) { this.windowsService.closeExtensionHostWindow(this.contextService.getWorkspace().resource.fsPath); } if (session && session.getId() === event.body.sessionId) { @@ -439,11 +432,11 @@ export class DebugService implements debug.IDebugService { const focusedProcess = this.viewModel.focusedProcess; if (focusedProcess) { - return this.sessionStates[focusedProcess.getId()]; + return this.sessionStates.get(focusedProcess.getId()); } const processes = this.model.getProcesses(); if (processes.length > 0) { - return this.sessionStates[processes[0].getId()]; + return this.sessionStates.get(processes[0].getId()); } return debug.State.Inactive; @@ -454,7 +447,7 @@ export class DebugService implements debug.IDebugService { } private setStateAndEmit(sessionId: string, newState: debug.State): void { - this.sessionStates[sessionId] = newState; + this.sessionStates.set(sessionId, newState); this._onDidChangeState.fire(); } @@ -671,9 +664,9 @@ export class DebugService implements debug.IDebugService { if (!this.viewModel.focusedProcess) { this.focusStackFrameAndEvaluate(null, process); } - this.toDisposeOnSessionEnd[session.getId()] = []; + this.toDisposeOnSessionEnd.set(session.getId(), []); if (client) { - this.toDisposeOnSessionEnd[session.getId()].push(client); + this.toDisposeOnSessionEnd.get(session.getId()).push(client); } this.registerSessionListeners(process, session); @@ -846,7 +839,7 @@ export class DebugService implements debug.IDebugService { }); try { - this.toDisposeOnSessionEnd[session.getId()] = lifecycle.dispose(this.toDisposeOnSessionEnd[session.getId()]); + this.toDisposeOnSessionEnd.set(session.getId(), lifecycle.dispose(this.toDisposeOnSessionEnd.get(session.getId()))); } catch (e) { // an internal module might be open so the dispose can throw -> ignore and continue with stop session. } @@ -903,7 +896,7 @@ export class DebugService implements debug.IDebugService { } if (this.textFileService.isDirty(modelUri)) { // Only send breakpoints for a file once it is not dirty #8077 - this.breakpointsToSendOnResourceSaved[modelUri.toString()] = true; + this.breakpointsToSendOnResourceSaved.add(modelUri.toString()); return TPromise.as(null); } @@ -998,8 +991,8 @@ export class DebugService implements debug.IDebugService { fileChangesEvent.contains(bp.uri, FileChangeType.DELETED))); fileChangesEvent.getUpdated().forEach(event => { - if (this.breakpointsToSendOnResourceSaved[event.resource.toString()]) { - this.breakpointsToSendOnResourceSaved[event.resource.toString()] = false; + if (this.breakpointsToSendOnResourceSaved.has(event.resource.toString())) { + this.breakpointsToSendOnResourceSaved.delete(event.resource.toString()); this.sendBreakpoints(event.resource, true).done(null, errors.onUnexpectedError); } }); @@ -1015,7 +1008,7 @@ export class DebugService implements debug.IDebugService { } public dispose(): void { - Object.keys(this.toDisposeOnSessionEnd).forEach(key => lifecycle.dispose(this.toDisposeOnSessionEnd[key])); + this.toDisposeOnSessionEnd.forEach(toDispose => lifecycle.dispose(toDispose)); this.toDispose = lifecycle.dispose(this.toDispose); } } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts b/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts index c8e9b640f37d..d82164b14522 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts @@ -309,15 +309,7 @@ export class CallStackController extends BaseDebugController { private focusStackFrame(stackFrame: debug.IStackFrame, event: IKeyboardEvent | IMouseEvent, preserveFocus: boolean): void { this.debugService.focusStackFrameAndEvaluate(stackFrame).then(() => { const sideBySide = (event && (event.ctrlKey || event.metaKey)); - return this.editorService.openEditor({ - resource: stackFrame.source.uri, - options: { - preserveFocus, - selection: { startLineNumber: stackFrame.lineNumber, startColumn: 1 }, - revealIfVisible: true, - revealInCenterIfOutsideViewport: true - }, - }, sideBySide); + return stackFrame.openInEditor(this.editorService, preserveFocus, sideBySide); }, errors.onUnexpectedError); } } @@ -563,7 +555,7 @@ export class CallStackRenderer implements IRenderer { private renderStackFrame(stackFrame: debug.IStackFrame, data: IStackFrameTemplateData): void { stackFrame.source.available ? dom.removeClass(data.stackFrame, 'disabled') : dom.addClass(data.stackFrame, 'disabled'); - data.file.title = stackFrame.source.uri.fsPath; + data.file.title = stackFrame.source.raw.path || stackFrame.source.name; data.label.textContent = stackFrame.name; data.label.title = stackFrame.name; data.fileName.textContent = getSourceName(stackFrame.source, this.contextService); diff --git a/src/vs/workbench/parts/debug/node/v8Protocol.ts b/src/vs/workbench/parts/debug/node/v8Protocol.ts index 3dd75e0e8dbc..8c368528db6b 100644 --- a/src/vs/workbench/parts/debug/node/v8Protocol.ts +++ b/src/vs/workbench/parts/debug/node/v8Protocol.ts @@ -13,14 +13,14 @@ export abstract class V8Protocol { private outputStream: stream.Writable; private sequence: number; - private pendingRequests: { [id: number]: (e: DebugProtocol.Response) => void; }; + private pendingRequests: Map void>; private rawData: Buffer; private contentLength: number; constructor(private id: string) { this.sequence = 1; this.contentLength = -1; - this.pendingRequests = {}; + this.pendingRequests = new Map void>(); this.rawData = new Buffer(0); } @@ -77,7 +77,7 @@ export abstract class V8Protocol { if (clb) { // store callback for this request - this.pendingRequests[request.seq] = clb; + this.pendingRequests.set(request.seq, clb); } } @@ -130,9 +130,9 @@ export abstract class V8Protocol { break; case 'response': const response = rawData; - const clb = this.pendingRequests[response.request_seq]; + const clb = this.pendingRequests.get(response.request_seq); if (clb) { - delete this.pendingRequests[response.request_seq]; + this.pendingRequests.delete(response.request_seq); clb(response); } break; diff --git a/src/vs/workbench/parts/extensions/browser/extensionsList.ts b/src/vs/workbench/parts/extensions/browser/extensionsList.ts index c5dd01e143ad..88bb1de5ce3d 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionsList.ts @@ -23,6 +23,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IExtensionService } from 'vs/platform/extensions/common/extensions'; export interface ITemplateData { + root: HTMLElement; element: HTMLElement; icon: HTMLImageElement; name: HTMLElement; @@ -92,7 +93,7 @@ export class Renderer implements IPagedRenderer { const disposables = [versionWidget, installCountWidget, ratingsWidget, builtinStatusAction, updateAction, reloadAction, manageAction, actionbar]; return { - element, icon, name, installCount, ratings, author, description, disposables, + root, element, icon, name, installCount, ratings, author, description, disposables, extensionDisposables: [], set extension(extension: IExtension) { versionWidget.extension = extension; @@ -110,6 +111,7 @@ export class Renderer implements IPagedRenderer { renderPlaceholder(index: number, data: ITemplateData): void { addClass(data.element, 'loading'); + data.root.removeAttribute('aria-label'); data.extensionDisposables = dispose(data.extensionDisposables); data.icon.src = ''; data.name.textContent = ''; @@ -142,6 +144,7 @@ export class Renderer implements IPagedRenderer { data.icon.style.visibility = 'inherit'; } + data.root.setAttribute('aria-label', extension.displayName); data.name.textContent = extension.displayName; data.author.textContent = extension.publisherDisplayName; data.description.textContent = extension.description; diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index 08263b5fe238..6ee74bb6e7ab 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -102,7 +102,9 @@ export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet { const delegate = new Delegate(); const renderer = this.instantiationService.createInstance(Renderer); - this.list = new PagedList(this.extensionsBox, delegate, [renderer]); + this.list = new PagedList(this.extensionsBox, delegate, [renderer], { + ariaLabel: localize('extensions', "Extensions") + }); const onKeyDown = chain(domEvent(this.searchBox, 'keydown')) .map(e => new StandardKeyboardEvent(e)); @@ -434,7 +436,7 @@ export class StatusUpdater implements IWorkbenchContribution { private onServiceChange(): void { if (this.extensionsWorkbenchService.local.some(e => e.state === ExtensionState.Installing)) { - this.activityBarService.showActivity(VIEWLET_ID, new ProgressBadge(() => localize('extensions', 'Extensions')), 'extensions-badge progress-badge'); + this.activityBarService.showActivity(VIEWLET_ID, new ProgressBadge(() => localize('extensions', "Extensions")), 'extensions-badge progress-badge'); return; } diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensions.css b/src/vs/workbench/parts/extensions/electron-browser/media/extensions.css index a0d329f9f8f4..8acfffe05676 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensions.css +++ b/src/vs/workbench/parts/extensions/electron-browser/media/extensions.css @@ -3,157 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.quick-open-widget .extension { - padding: 0 14px 0 0; - height: 48px; -} - -.quick-open-widget .extension.loading, -.extensions-viewlet > .extensions .extension.loading, -.extension-editor > .body > .content.loading { - background: url('data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjU3NSIgaGVpZ2h0PSI2cHgiPg0KICA8c3R5bGU+DQogICAgY2lyY2xlIHsNCiAgICAgIGFuaW1hdGlvbjogYmFsbCAyLjVzIGN1YmljLWJlemllcigwLjAwMCwgMS4wMDAsIDEuMDAwLCAwLjAwMCkgaW5maW5pdGU7DQogICAgICBmaWxsOiAjYmJiOw0KICAgIH0NCg0KICAgICNiYWxscyB7DQogICAgICBhbmltYXRpb246IGJhbGxzIDIuNXMgbGluZWFyIGluZmluaXRlOw0KICAgIH0NCg0KICAgICNjaXJjbGUyIHsgYW5pbWF0aW9uLWRlbGF5OiAwLjFzOyB9DQogICAgI2NpcmNsZTMgeyBhbmltYXRpb24tZGVsYXk6IDAuMnM7IH0NCiAgICAjY2lyY2xlNCB7IGFuaW1hdGlvbi1kZWxheTogMC4zczsgfQ0KICAgICNjaXJjbGU1IHsgYW5pbWF0aW9uLWRlbGF5OiAwLjRzOyB9DQoNCiAgICBAa2V5ZnJhbWVzIGJhbGwgew0KICAgICAgZnJvbSB7IHRyYW5zZm9ybTogbm9uZTsgfQ0KICAgICAgMjAlIHsgdHJhbnNmb3JtOiBub25lOyB9DQogICAgICA4MCUgeyB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoODY0cHgpOyB9DQogICAgICB0byB7IHRyYW5zZm9ybTogdHJhbnNsYXRlWCg4NjRweCk7IH0NCiAgICB9DQoNCiAgICBAa2V5ZnJhbWVzIGJhbGxzIHsNCiAgICAgIGZyb20geyB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoLTQwcHgpOyB9DQogICAgICB0byB7IHRyYW5zZm9ybTogdHJhbnNsYXRlWCgzMHB4KTsgfQ0KICAgIH0NCiAgPC9zdHlsZT4NCiAgPGcgaWQ9ImJhbGxzIj4NCiAgICA8Y2lyY2xlIGNsYXNzPSJjaXJjbGUiIGlkPSJjaXJjbGUxIiBjeD0iLTExNSIgY3k9IjMiIHI9IjMiLz4NCiAgICA8Y2lyY2xlIGNsYXNzPSJjaXJjbGUiIGlkPSJjaXJjbGUyIiBjeD0iLTEzMCIgY3k9IjMiIHI9IjMiIC8+DQogICAgPGNpcmNsZSBjbGFzcz0iY2lyY2xlIiBpZD0iY2lyY2xlMyIgY3g9Ii0xNDUiIGN5PSIzIiByPSIzIiAvPg0KICAgIDxjaXJjbGUgY2xhc3M9ImNpcmNsZSIgaWQ9ImNpcmNsZTQiIGN4PSItMTYwIiBjeT0iMyIgcj0iMyIgLz4NCiAgICA8Y2lyY2xlIGNsYXNzPSJjaXJjbGUiIGlkPSJjaXJjbGU1IiBjeD0iLTE3NSIgY3k9IjMiIHI9IjMiIC8+DQogIDwvZz4NCjwvc3ZnPg==') center center no-repeat; -} - -.quick-open-widget .extension.loading > * { - opacity: 0.4; -} - -.quick-open-widget .extension .row { - display: block; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - height: 24px; -} - -.quick-open-widget .extension .row .actions { - float: right; -} - -.quick-open-widget .extension .description { - display: block; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - opacity: 0.6; -} - -.quick-open-widget .extension .install { - margin-left: 6px; - padding: 1px 3px; - border-radius: 3px; - background-color: rgba(132, 132, 132, 0.3); - font-size: smaller; - opacity: 0.7; -} - -.quick-open-widget .extension .install > .octicon { - font-size: small; - margin-right: 3px; -} - -.quick-open-widget .extension .icon.octicon-x:before { - margin-right: 2px; - margin-top: 2px; - display: inline-block; -} - -.quick-open-widget .extension .published { - float: right; - opacity: 0.6; - font-size: smaller; -} - -.quick-open-widget .extension .published > .version { - opacity: 0.6; - margin-right: 0.5em; -} - -@keyframes move-background { - to { background-position: 8px 0; } -} - -.quick-open-widget .extension .actions .action-item:not(.disabled) { - display: none; -} - -.quick-open-widget .monaco-tree-row:hover .extension .actions .action-item, -.quick-open-widget .monaco-tree-row.focused .extension .actions .action-item { - display: inherit; -} - -.quick-open-widget .extension .actions .action-item:not(:first-child) { - margin-left: 2px; -} - -.quick-open-widget .extension .actions .action-item { - line-height: 12px; -} - -.quick-open-widget .extension .actions .action-label { - width: 12px; - height: 12px; - font-size: smaller; - border: 1px solid rgba(132, 132, 132, 0.5); - /*border-radius: 2px;*/ - padding: 1px; - vertical-align: text-bottom; - text-transform: uppercase; - color: rgb(0, 157, 255); - border-color: rgb(0, 157, 255); -} - -.quick-open-widget .monaco-tree.focused .monaco-tree-row.focused .extension .actions .action-label:focus { - outline: 1px solid #DF740C; - outline-offset: -1px; -} - -.quick-open-widget .monaco-tree.focused .monaco-tree-row.focused .extension .actions .action-label:active { - outline: none; -} - -.quick-open-widget .extension .actions .action-label:not(.icon) { - padding: 1px 3px; - display: inline-block; - width: auto; - font-size: x-small; -} - -.quick-open-widget .extension .actions .action-item.disabled .action-label { - animation: move-background 0.5s linear infinite; - background-color: rgba(132, 132, 132, 0.5); - background-size: 8px; - background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.5) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.5) 50%, rgba(255, 255, 255, 0.5) 75%, transparent 75%, transparent); - color: rgb(0, 0, 0); - border-color: rgb(0, 0, 0); -} - -.quick-open-widget .extension .actions .action-item .action-label:hover { - text-decoration: none; -} - -.quick-open-widget .extension .actions .action-item:not(.disabled) .action-label:hover { - color: inherit; - background: rgba(132, 132, 132, 0.2); - border-color: #CCC; -} - -.quick-open-widget .extension .actions .action-item:not(.disabled) .action-label:active { - background: rgba(132, 132, 132, 0.5); -} - -.quick-open-widget .extension .actions .action-item:active { - transform: none; -} - -.monaco-workbench > .activitybar .monaco-action-bar .action-label.extensions { - background: url('extensions-status.svg') center center no-repeat; -} - -/* Global action */ - -.monaco-workbench > .activitybar .monaco-action-bar .action-label.extensions { - background-size: 22px; - background-repeat: no-repeat; - background-position: 50% !important; +.monaco-workbench > .activitybar > .content .monaco-action-bar .action-label.extensions { + background: url('extensions-status.svg') center center/22px no-repeat; } \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 2cda12aa0b44..64e21cd4d8d1 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -580,11 +580,10 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { .then(null, onUnexpectedError); } } - } - - if (extension.gallery) { - // Report telemetry only for gallery extensions - this.reportTelemetry(installing, !error); + if (extension.gallery) { + // Report telemetry only for gallery extensions + this.reportTelemetry(installing, !error); + } } this._onChange.fire(); } diff --git a/src/vs/workbench/parts/files/browser/fileActions.ts b/src/vs/workbench/parts/files/browser/fileActions.ts index 94b082fb8ff3..623df36d1cb9 100644 --- a/src/vs/workbench/parts/files/browser/fileActions.ts +++ b/src/vs/workbench/parts/files/browser/fileActions.ts @@ -44,8 +44,8 @@ import { IInstantiationService, IConstructorSignature2 } from 'vs/platform/insta import { IMessageService, IMessageWithAction, IConfirmation, Severity, CancelAction } from 'vs/platform/message/common/message'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { Keybinding, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { Selection } from 'vs/editor/common/core/selection'; import { getCodeEditor } from 'vs/editor/common/services/codeEditorService'; +import { IEditorViewState } from 'vs/editor/common/editorCommon'; export interface IEditableData { action: IAction; @@ -1119,11 +1119,11 @@ export class GlobalCompareResourcesAction extends Action { } public run(): TPromise { - const fileResource = toResource(this.editorService.getActiveEditorInput(), { filter: 'file' }); - if (fileResource) { + const activeResource = toResource(this.editorService.getActiveEditorInput(), { filter: ['file', 'untitled'] }); + if (activeResource) { // Keep as resource to compare - globalResourceToCompare = fileResource; + globalResourceToCompare = activeResource; // Pick another entry from history interface IHistoryPickEntry extends IFilePickOpenEntry { @@ -1137,18 +1137,22 @@ export class GlobalCompareResourcesAction extends Action { let description: string; if (input instanceof EditorInput) { - return void 0; // only files supported + resource = toResource(input, { filter: ['file', 'untitled'] }); + } else { + resource = (input as IResourceInput).resource; } - const resourceInput = input as IResourceInput; - resource = resourceInput.resource; - label = paths.basename(resourceInput.resource.fsPath); - description = labels.getPathLabel(paths.dirname(resource.fsPath), this.contextService); + if (!resource) { + return void 0; // only support to compare with files and untitled + } + + label = paths.basename(resource.fsPath); + description = resource.scheme === 'file' ? labels.getPathLabel(paths.dirname(resource.fsPath), this.contextService) : void 0; return { input, resource, label, description }; }).filter(p => !!p); - return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickHistory', "Select an editor history entry to compare with"), autoFocus: { autoFocusFirstEntry: true }, matchOnDescription: true }).then(pick => { + return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickHistory', "Select a previously opened file to compare with"), autoFocus: { autoFocusFirstEntry: true }, matchOnDescription: true }).then(pick => { if (pick) { const compareAction = this.instantiationService.createInstance(CompareResourcesAction, pick.resource, null); if (compareAction._isEnabled()) { @@ -1303,13 +1307,13 @@ export abstract class BaseSaveFileAction extends BaseActionWithErrorReporting { encodingOfSource = textModel && textModel.getEncoding(); // text model can be null e.g. if this is a binary file! } - let selectionOfSource: Selection; + let viewStateOfSource: IEditorViewState; const activeEditor = this.editorService.getActiveEditor(); const editor = getCodeEditor(activeEditor); if (editor) { const activeResource = toResource(activeEditor.input, { supportSideBySide: true, filter: ['file', 'untitled'] }); if (activeResource && activeResource.toString() === source.toString()) { - selectionOfSource = editor.getSelection(); + viewStateOfSource = editor.saveViewState(); } } @@ -1340,13 +1344,13 @@ export abstract class BaseSaveFileAction extends BaseActionWithErrorReporting { encoding: encodingOfSource, options: { pinned: true, - selection: selectionOfSource + viewState: viewStateOfSource } }; return this.editorService.replaceEditors([{ toReplace: { resource: source }, - replaceWith: replaceWith + replaceWith }]).then(() => true); }); } diff --git a/src/vs/workbench/parts/files/browser/views/openEditorsViewer.ts b/src/vs/workbench/parts/files/browser/views/openEditorsViewer.ts index 7b9579c7f957..99b26caf5ef5 100644 --- a/src/vs/workbench/parts/files/browser/views/openEditorsViewer.ts +++ b/src/vs/workbench/parts/files/browser/views/openEditorsViewer.ts @@ -413,6 +413,7 @@ export class ActionProvider extends ContributableActionProvider { result.unshift(this.instantiationService.createInstance(OpenToSideAction, tree, resource, false)); if (!openEditor.isUntitled()) { + // Files: Save / Revert if (!autoSaveEnabled) { result.push(new Separator()); @@ -427,18 +428,10 @@ export class ActionProvider extends ContributableActionProvider { revertAction.enabled = openEditor.isDirty(); result.push(revertAction); } - - result.push(new Separator()); - - // Compare Actions - const runCompareAction = this.instantiationService.createInstance(CompareResourcesAction, resource, tree); - if (runCompareAction._isEnabled()) { - result.push(runCompareAction); - } - result.push(this.instantiationService.createInstance(SelectResourceForCompareAction, resource, tree)); } + // Untitled: Save / Save As - else { + if (openEditor.isUntitled()) { result.push(new Separator()); if (this.untitledEditorService.hasAssociatedFilePath(resource)) { @@ -452,6 +445,14 @@ export class ActionProvider extends ContributableActionProvider { result.push(saveAsAction); } + // Compare Actions + result.push(new Separator()); + const runCompareAction = this.instantiationService.createInstance(CompareResourcesAction, resource, tree); + if (runCompareAction._isEnabled()) { + result.push(runCompareAction); + } + result.push(this.instantiationService.createInstance(SelectResourceForCompareAction, resource, tree)); + result.push(new Separator()); } diff --git a/src/vs/workbench/parts/markers/browser/media/markers.css b/src/vs/workbench/parts/markers/browser/media/markers.css index 491e4ac3590a..2cdcded90d2e 100644 --- a/src/vs/workbench/parts/markers/browser/media/markers.css +++ b/src/vs/workbench/parts/markers/browser/media/markers.css @@ -46,10 +46,6 @@ line-height: 22px; } -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry > * { - display: inline-block; -} - .markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .marker-stats { display: inline-block; margin-left: 10px; diff --git a/src/vs/workbench/parts/markers/common/markersModel.ts b/src/vs/workbench/parts/markers/common/markersModel.ts index bd7e75d2a243..ce4524b4ded9 100644 --- a/src/vs/workbench/parts/markers/common/markersModel.ts +++ b/src/vs/workbench/parts/markers/common/markersModel.ts @@ -26,8 +26,8 @@ export class Resource { private _path: string = null; constructor(public uri: URI, public markers: Marker[], - public statistics: MarkerStatistics, - public matches: IMatch[] = []) { + public statistics: MarkerStatistics, + public matches: IMatch[] = []) { } public get path(): string { @@ -47,8 +47,8 @@ export class Resource { export class Marker { constructor(public id: string, public marker: IMarker, - public labelMatches: IMatch[] = [], - public sourceMatches: IMatch[] = []) { } + public labelMatches: IMatch[] = [], + public sourceMatches: IMatch[] = []) { } public get resource(): URI { return this.marker.resource; diff --git a/src/vs/workbench/parts/preferences/browser/media/preferences.css b/src/vs/workbench/parts/preferences/browser/media/preferences.css index 420c2d861788..6c31b27d5e75 100644 --- a/src/vs/workbench/parts/preferences/browser/media/preferences.css +++ b/src/vs/workbench/parts/preferences/browser/media/preferences.css @@ -102,10 +102,6 @@ color: white; } -.monaco-editor .settings-group-title-widget .title-container > * { - vertical-align: middle; - display: inline-block; -} .monaco-editor.vs-dark .settings-group-title-widget .title-container.focused, .monaco-editor.vs .settings-group-title-widget .title-container.focused { @@ -133,6 +129,11 @@ height: 16px; } +.monaco-editor .settings-group-title-widget .title-container > div { + vertical-align: middle; + display: inline-block; +} + .monaco-editor.vs-dark .settings-group-title-widget .title-container .expand-collapse-icon, .monaco-editor.hc-black .settings-group-title-widget .title-container .expand-collapse-icon { background: url(expanded-dark.svg) 50% 50% no-repeat; @@ -147,19 +148,16 @@ background: url(collapsed-dark.svg) 50% 50% no-repeat; } -.monaco-editor .view-line:hover .copySetting:after { - cursor: pointer; - content:" "; +.monaco-editor .edit-preferences-widget { background: url('edit.svg') center center no-repeat; - margin-left: 1em; - display:inline-block; - position: absolute; - height:100%; + transform: rotate(-90deg); width:16px; + height: 16px; + cursor: pointer; } -.monaco-editor.hc-black .view-line:hover .copySetting:after, -.monaco-editor.vs-dark .view-line:hover .copySetting:after { +.monaco-editor.hc-black .edit-preferences-widget, +.monaco-editor.vs-dark .edit-preferences-widget { background: url('edit_inverse.svg') center center no-repeat; } diff --git a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts index c31f05326512..96c0a7da7ba8 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts @@ -15,11 +15,10 @@ import { ArrayIterator } from 'vs/base/common/iterator'; import { IAction } from 'vs/base/common/actions'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import Event, { Emitter } from 'vs/base/common/event'; -import { LinkedMap as Map } from 'vs/base/common/map'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { Registry } from 'vs/platform/platform'; -import { EditorOptions, EditorInput } from 'vs/workbench/common/editor'; import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; +import { Registry } from 'vs/platform/platform'; +import { EditorOptions, toResource } from 'vs/workbench/common/editor'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; @@ -30,13 +29,13 @@ import { Range } from 'vs/editor/common/core/range'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IPreferencesService, ISettingsGroup, ISetting, IPreferencesEditorModel, IFilterResult, CONTEXT_DEFAULT_SETTINGS_EDITOR, - DEFAULT_EDITOR_COMMAND_COLLAPSE_ALL, DEFAULT_EDITOR_COMMAND_FOCUS_SEARCH + DEFAULT_EDITOR_COMMAND_COLLAPSE_ALL, DEFAULT_EDITOR_COMMAND_FOCUS_SEARCH, ISettingsEditorModel } from 'vs/workbench/parts/preferences/common/preferences'; import { SettingsEditorModel, DefaultSettingsEditorModel } from 'vs/workbench/parts/preferences/common/preferencesModels'; import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; import { ICodeEditor, IEditorMouseEvent, IEditorContributionCtor } from 'vs/editor/browser/editorBrowser'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { DefaultSettingsHeaderWidget, SettingsGroupTitleWidget, SettingsCountWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; +import { IContextMenuService, ContextSubMenu } from 'vs/platform/contextview/browser/contextView'; +import { DefaultSettingsHeaderWidget, SettingsGroupTitleWidget, SettingsCountWidget, EditPreferenceWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { CommonEditorRegistry, EditorCommand, Command } from 'vs/editor/common/editorCommonExtensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -46,9 +45,12 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { ITextModelResolverService } from 'vs/editor/common/services/resolverService'; +import { RangeHighlightDecorations } from 'vs/workbench/common/editor/rangeDecorations'; +import { IConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditing'; // Ignore following contributions import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; @@ -63,8 +65,10 @@ export class DefaultPreferencesEditorInput extends ResourceEditorInput { private _willDispose = new Emitter(); public willDispose: Event = this._willDispose.event; - constructor(resource: URI, @ITextModelResolverService textModelResolverService: ITextModelResolverService) { - super(nls.localize('settingsEditorName', "Default Settings"), '', resource, textModelResolverService); + constructor(defaultSettingsResource: URI, + @ITextModelResolverService textModelResolverService: ITextModelResolverService + ) { + super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, textModelResolverService); } getTypeId(): string { @@ -95,15 +99,13 @@ export class DefaultPreferencesEditorInput extends ResourceEditorInput { export class DefaultPreferencesEditor extends BaseTextEditor { public static ID: string = 'workbench.editor.defaultPreferences'; - private static VIEW_STATE: Map = new Map(); - private inputDisposeListener: IDisposable; private defaultSettingHeaderWidget: DefaultSettingsHeaderWidget; - private delayedFilterLogging: Delayer; constructor( @ITelemetryService telemetryService: ITelemetryService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @IConfigurationService configurationService: IConfigurationService, @@ -140,7 +142,6 @@ export class DefaultPreferencesEditor extends BaseTextEditor { options.folding = false; options.renderWhitespace = 'none'; options.wrappingColumn = 0; - options.overviewRulerLanes = 0; options.renderIndentGuides = false; options.rulers = []; } @@ -148,9 +149,7 @@ export class DefaultPreferencesEditor extends BaseTextEditor { } setInput(input: DefaultPreferencesEditorInput, options: EditorOptions): TPromise { - this.listenToInput(input); - return super.setInput(input, options) - .then(() => this.updateInput()); + return super.setInput(input, options).then(() => this.updateInput()); } public layout(dimension: Dimension) { @@ -172,13 +171,18 @@ export class DefaultPreferencesEditor extends BaseTextEditor { private updateInput(): TPromise { return this.input.resolve() - .then(editorModel => editorModel.load()) - .then(editorModel => this.getControl().setModel((editorModel).textEditorModel)); + .then(editorModel => TPromise.join([ + editorModel.load(), + // Default preferences editor is always part of side by side editor hence getting the master preferences model from active editor + // TODO:@sandy check with Ben + this.preferencesService.resolvePreferencesEditorModel(toResource(this.editorService.getActiveEditorInput(), { supportSideBySide: true })) + ])) + .then(([editorModel, preferencesModel]) => (this.getControl()).setModels((editorModel).textEditorModel, preferencesModel)); } private filterPreferences(filter: string) { this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(filter)); - (this.getDefaultPreferencesContribution().getPreferencesRenderer()).filterPreferences(filter); + (this.getDefaultPreferencesContribution().getPreferencesRenderer()).filterPreferences(filter.trim()); } private focusNextPreference() { @@ -187,42 +191,11 @@ export class DefaultPreferencesEditor extends BaseTextEditor { public clearInput(): void { this.getControl().setModel(null); - this.saveState(this.input); - if (this.inputDisposeListener) { - this.inputDisposeListener.dispose(); - } super.clearInput(); } private getDefaultPreferencesContribution(): PreferencesEditorContribution { - return (this.getControl()).getContribution(PreferencesEditorContribution.ID); - } - - protected restoreViewState(input: EditorInput) { - const viewState = DefaultPreferencesEditor.VIEW_STATE.get((input).getResource()); - if (viewState) { - this.getControl().restoreViewState(viewState); - } - } - - private saveState(input: DefaultPreferencesEditorInput) { - const state = this.getControl().saveViewState(); - if (state) { - const resource = input.getResource(); - if (DefaultPreferencesEditor.VIEW_STATE.has(resource)) { - DefaultPreferencesEditor.VIEW_STATE.delete(resource); - } - DefaultPreferencesEditor.VIEW_STATE.set(resource, state); - } - } - - private listenToInput(input: EditorInput) { - if (this.inputDisposeListener) { - this.inputDisposeListener.dispose(); - } - if (input instanceof DefaultPreferencesEditorInput) { - this.inputDisposeListener = (input).willDispose(() => this.saveState(input)); - } + return (this.getControl()).getContribution(DefaultSettingsEditorContribution.ID); } private reportFilteringUsed(filter: string): void { @@ -234,6 +207,8 @@ export class DefaultPreferencesEditor extends BaseTextEditor { class DefaultPreferencesCodeEditor extends CodeEditor { + private _settingsModel: SettingsEditorModel; + protected _getContributions(): IEditorContributionCtor[] { let contributions = super._getContributions(); let skipContributions = [FoldingController.prototype, SelectionHighlighter.prototype, FindController.prototype]; @@ -241,16 +216,25 @@ class DefaultPreferencesCodeEditor extends CodeEditor { contributions.push(DefaultSettingsEditorContribution); return contributions; } + + setModels(model: editorCommon.IModel, settingsModel: SettingsEditorModel): void { + this._settingsModel = settingsModel; + return super.setModel(model); + } + + get settingsModel(): SettingsEditorModel { + return this._settingsModel; + } } export interface IPreferencesRenderer { - render(): void; - dispose(): void; + render(); + updatePreference(setting: ISetting, value: any): void; + dispose(); } export abstract class PreferencesEditorContribution extends Disposable implements editorCommon.IEditorContribution { - static ID: string = 'editor.contrib.preferences'; private preferencesRenderer: IPreferencesRenderer; constructor(protected editor: ICodeEditor, @@ -277,15 +261,12 @@ export abstract class PreferencesEditorContribution extends Disposable implement } } - getId(): string { - return PreferencesEditorContribution.ID; - } - getPreferencesRenderer(): IPreferencesRenderer { return this.preferencesRenderer; } protected abstract createPreferencesRenderer(editorModel: IPreferencesEditorModel): IPreferencesRenderer + abstract getId(): string; private disposePreferencesRenderer() { if (this.preferencesRenderer) { @@ -301,16 +282,30 @@ export abstract class PreferencesEditorContribution extends Disposable implement } export class DefaultSettingsEditorContribution extends PreferencesEditorContribution implements editorCommon.IEditorContribution { + + static ID: string = 'editor.contrib.defaultsettings'; + protected createPreferencesRenderer(editorModel: IPreferencesEditorModel): IPreferencesRenderer { if (editorModel instanceof DefaultSettingsEditorModel) { - return this.instantiationService.createInstance(DefaultSettingsRenderer, this.editor, editorModel); + return this.instantiationService.createInstance(DefaultSettingsRenderer, this.editor, editorModel, (this.editor).settingsModel); } return null; } + + getId(): string { + return DefaultSettingsEditorContribution.ID; + } } @editorContribution export class SettingsEditorContribution extends PreferencesEditorContribution implements editorCommon.IEditorContribution { + + static ID: string = 'editor.contrib.settings'; + + getId(): string { + return SettingsEditorContribution.ID; + } + protected createPreferencesRenderer(editorModel: IPreferencesEditorModel): IPreferencesRenderer { if (editorModel instanceof SettingsEditorModel) { return this.instantiationService.createInstance(SettingsRenderer, this.editor, editorModel); @@ -321,20 +316,35 @@ export class SettingsEditorContribution extends PreferencesEditorContribution im export class SettingsRenderer extends Disposable implements IPreferencesRenderer { - private copySettingActionRenderer: CopySettingActionRenderer; + private initializationPromise: TPromise; + private settingHighlighter: SettingHighlighter; + private editSettingActionRenderer: EditSettingRenderer; private modelChangeDelayer: Delayer = new Delayer(200); constructor(protected editor: ICodeEditor, protected settingsEditorModel: SettingsEditorModel, @IPreferencesService protected preferencesService: IPreferencesService, + @ITelemetryService private telemetryService: ITelemetryService, + @IConfigurationEditingService private configurationEditingService: IConfigurationEditingService, + @IMessageService private messageService: IMessageService, @IInstantiationService protected instantiationService: IInstantiationService ) { super(); - this.copySettingActionRenderer = this._register(instantiationService.createInstance(CopySettingActionRenderer, editor, false)); - this._register(editor.getModel().onDidChangeContent(() => this.modelChangeDelayer.trigger(() => this.onModelChanged()))); + this.settingHighlighter = this._register(instantiationService.createInstance(SettingHighlighter, editor)); + this.initializationPromise = this.initialize(); } public render(): void { - this.copySettingActionRenderer.render(this.settingsEditorModel.settingsGroups); + this.initializationPromise.then(() => this.editSettingActionRenderer.render(this.settingsEditorModel.settingsGroups)); + } + + private initialize(): TPromise { + return this.preferencesService.createDefaultPreferencesEditorModel(this.preferencesService.defaultSettingsResource) + .then(defaultSettingsModel => { + this.editSettingActionRenderer = this._register(this.instantiationService.createInstance(EditSettingRenderer, this.editor, this.settingsEditorModel, defaultSettingsModel, this.settingHighlighter)); + this._register(this.editor.getModel().onDidChangeContent(() => this.modelChangeDelayer.trigger(() => this.onModelChanged()))); + this._register(this.editSettingActionRenderer.onUpdateSetting(({setting, value}) => this.updatePreference(setting, value))); + return null; + }); } private onModelChanged(): void { @@ -344,32 +354,51 @@ export class SettingsRenderer extends Disposable implements IPreferencesRenderer } this.render(); } + + public updatePreference(setting: ISetting, value: any): void { + this.telemetryService.publicLog('defaultSettingsActions.copySetting', { userConfigurationKeys: [setting.key] }); + this.configurationEditingService.writeConfiguration(this.settingsEditorModel.configurationTarget, { key: setting.key, value }, { writeToBuffer: true, autoSave: true }) + .then(() => this.onSettingUpdated(setting), error => this.messageService.show(Severity.Error, error)); + } + + private onSettingUpdated(setting: ISetting) { + this.editor.focus(); + setting = this.settingsEditorModel.getSetting(setting.key); + // TODO:@sandy Selection range should be template range + this.editor.setSelection(setting.valueRange); + this.settingHighlighter.highlight(this.settingsEditorModel.getSetting(setting.key), true); + } } export class DefaultSettingsRenderer extends Disposable implements IPreferencesRenderer { private defaultSettingsEditorContextKey: IContextKey; + private settingHighlighter: SettingHighlighter; private settingsGroupTitleRenderer: SettingsGroupTitleRenderer; private filteredMatchesRenderer: FilteredMatchesRenderer; - private focusNextSettingRenderer: FocusNextSettingRenderer; + private filteredSettingsNavigationRenderer: FilteredSettingsNavigationRenderer; private hiddenAreasRenderer: HiddenAreasRenderer; - private copySettingActionRenderer: CopySettingActionRenderer; + private editSettingActionRenderer: EditSettingRenderer; private settingsCountWidget: SettingsCountWidget; - constructor(protected editor: ICodeEditor, protected settingsEditorModel: DefaultSettingsEditorModel, + constructor(protected editor: ICodeEditor, protected defaultSettingsEditorModel: DefaultSettingsEditorModel, + private settingsEditorModel: SettingsEditorModel, @IPreferencesService protected preferencesService: IPreferencesService, @IContextKeyService contextKeyService: IContextKeyService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IInstantiationService protected instantiationService: IInstantiationService ) { super(); this.defaultSettingsEditorContextKey = CONTEXT_DEFAULT_SETTINGS_EDITOR.bindTo(contextKeyService); + this.settingHighlighter = this._register(instantiationService.createInstance(SettingHighlighter, editor)); this.settingsGroupTitleRenderer = this._register(instantiationService.createInstance(SettingsGroupTitleRenderer, editor)); this.filteredMatchesRenderer = this._register(instantiationService.createInstance(FilteredMatchesRenderer, editor)); - this.focusNextSettingRenderer = this._register(instantiationService.createInstance(FocusNextSettingRenderer, editor)); - this.copySettingActionRenderer = this._register(instantiationService.createInstance(CopySettingActionRenderer, editor, true)); - this.settingsCountWidget = this._register(instantiationService.createInstance(SettingsCountWidget, editor, this.getCount(settingsEditorModel.settingsGroups))); - const paranthesisHidingRenderer = this._register(instantiationService.createInstance(StaticContentHidingRenderer, editor, settingsEditorModel.settingsGroups)); + this.filteredSettingsNavigationRenderer = this._register(instantiationService.createInstance(FilteredSettingsNavigationRenderer, editor, this.settingHighlighter)); + this.editSettingActionRenderer = this._register(instantiationService.createInstance(EditSettingRenderer, editor, defaultSettingsEditorModel, settingsEditorModel, this.settingHighlighter)); + this._register(this.editSettingActionRenderer.onUpdateSetting(({setting, value}) => this.updatePreference(setting, value))); + this.settingsCountWidget = this._register(instantiationService.createInstance(SettingsCountWidget, editor, this.getCount(defaultSettingsEditorModel.settingsGroups))); + const paranthesisHidingRenderer = this._register(instantiationService.createInstance(StaticContentHidingRenderer, editor, defaultSettingsEditorModel.settingsGroups)); this.hiddenAreasRenderer = this._register(instantiationService.createInstance(HiddenAreasRenderer, editor, [this.settingsGroupTitleRenderer, this.filteredMatchesRenderer, paranthesisHidingRenderer])); this._register(this.settingsGroupTitleRenderer.onHiddenAreasChanged(() => this.hiddenAreasRenderer.render())); @@ -377,31 +406,32 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR public render() { this.defaultSettingsEditorContextKey.set(true); - this.settingsGroupTitleRenderer.render(this.settingsEditorModel.settingsGroups); - this.copySettingActionRenderer.render(this.settingsEditorModel.settingsGroups); + this.settingsGroupTitleRenderer.render(this.defaultSettingsEditorModel.settingsGroups); + this.editSettingActionRenderer.render(this.defaultSettingsEditorModel.settingsGroups); this.settingsCountWidget.render(); - this.focusNextSettingRenderer.render([]); + this.hiddenAreasRenderer.render(); + this.filteredSettingsNavigationRenderer.render([]); this.settingsGroupTitleRenderer.showGroup(1); this.hiddenAreasRenderer.render(); } public filterPreferences(filter: string) { - const filterResult = this.settingsEditorModel.filterSettings(filter); + const filterResult = this.defaultSettingsEditorModel.filterSettings(filter); this.filteredMatchesRenderer.render(filterResult); this.settingsGroupTitleRenderer.render(filterResult.filteredGroups); this.settingsCountWidget.show(this.getCount(filterResult.filteredGroups)); if (!filter) { - this.focusNextSettingRenderer.render([]); + this.filteredSettingsNavigationRenderer.render([]); this.settingsGroupTitleRenderer.showGroup(1); } else { - this.focusNextSettingRenderer.render(filterResult.filteredGroups); + this.filteredSettingsNavigationRenderer.render(filterResult.filteredGroups); } this.hiddenAreasRenderer.render(); } public focusNextSetting(): void { - const setting = this.focusNextSettingRenderer.focusNext(); + const setting = this.filteredSettingsNavigationRenderer.next(); if (setting) { this.settingsGroupTitleRenderer.showSetting(setting); } @@ -411,6 +441,23 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR this.settingsGroupTitleRenderer.collapseAll(); } + public updatePreference(setting: ISetting, value: any): void { + const settingsEditor = this.getEditableSettingsEditor(); + if (settingsEditor) { + settingsEditor.getContribution(SettingsEditorContribution.ID).getPreferencesRenderer().updatePreference(setting, value); + } + } + + private getEditableSettingsEditor(): editorCommon.ICommonCodeEditor { + return this.editorService.getVisibleEditors() + .filter(editor => { + if (editorCommon.isCommonCodeEditor(editor.getControl())) { + return (editor.getControl()).getModel().uri.fsPath === this.settingsEditorModel.uri.fsPath; + } + }) + .map(editor => editor.getControl())[0]; + } + private getCount(settingsGroups: ISettingsGroup[]): number { let count = 0; for (const group of settingsGroups) { @@ -623,9 +670,9 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr }); } else { for (const section of group.sections) { - if (section.descriptionRange) { - if (!this.containsLine(section.descriptionRange.startLineNumber, filteredGroup)) { - notMatchesRanges.push(this.createCompleteRange(section.descriptionRange, model)); + if (section.titleRange) { + if (!this.containsLine(section.titleRange.startLineNumber, filteredGroup)) { + notMatchesRanges.push(this.createCompleteRange(section.titleRange, model)); } } for (const setting of section.settings) { @@ -645,7 +692,7 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr } for (const section of settingsGroup.sections) { - if (section.descriptionRange && lineNumber >= section.descriptionRange.startLineNumber && lineNumber <= section.descriptionRange.endLineNumber) { + if (section.titleRange && lineNumber >= section.titleRange.startLineNumber && lineNumber <= section.titleRange.endLineNumber) { return true; } @@ -677,44 +724,25 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr } } -export class FocusNextSettingRenderer extends Disposable { +class FilteredSettingsNavigationRenderer extends Disposable { private iterator: ArrayIterator; - private decorationIds: string[] = []; - constructor(private editor: ICodeEditor) { + constructor(private editor: ICodeEditor, private settingHighlighter: SettingHighlighter) { super(); } - public focusNext(): ISetting { - this.clear(); + public next(): ISetting { let setting = this.iterator.next() || this.iterator.first(); if (setting) { - const model = this.editor.getModel(); - this.editor.changeDecorations(changeAccessor => { - this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, [{ - range: { - startLineNumber: setting.valueRange.startLineNumber, - startColumn: model.getLineMinColumn(setting.valueRange.startLineNumber), - endLineNumber: setting.valueRange.endLineNumber, - endColumn: model.getLineMaxColumn(setting.valueRange.endLineNumber) - }, - options: { - stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - className: 'rangeHighlight', - isWholeLine: true - } - }]); - }); - this.editor.revealLinesInCenterIfOutsideViewport(setting.valueRange.startLineNumber, setting.valueRange.endLineNumber - 1); + this.settingHighlighter.highlight(setting, true); return setting; } return null; } public render(filteredGroups: ISettingsGroup[]) { - this.clear(); - + this.settingHighlighter.clear(true); const settings: ISetting[] = []; for (const group of filteredGroups) { for (const section of group.sections) { @@ -723,135 +751,148 @@ export class FocusNextSettingRenderer extends Disposable { } this.iterator = new ArrayIterator(settings); } - - private clear() { - this.editor.changeDecorations(changeAccessor => { - this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, []); - }); - } - - public dispose() { - this.clear(); - super.dispose(); - } } -export class CopySettingActionRenderer extends Disposable { +class EditSettingRenderer extends Disposable { + + private editPreferenceWidgetForCusorPosition: EditPreferenceWidget; + private editPreferenceWidgetForMouseMove: EditPreferenceWidget; - private decorationIds: string[] = []; private settingsGroups: ISettingsGroup[]; - private model: editorCommon.IModel; + private toggleEditPreferencesForMouseMoveDelayer: Delayer; - constructor(private editor: ICodeEditor, private isDefaultSettings: boolean, - @IPreferencesService private settingsService: IPreferencesService, + private _onUpdateSetting: Emitter<{ setting: ISetting, value: any }> = new Emitter<{ setting: ISetting, value: any }>(); + public readonly onUpdateSetting: Event<{ setting: ISetting, value: any }> = this._onUpdateSetting.event; + + constructor(private editor: ICodeEditor, private masterSettingsModel: ISettingsEditorModel, + private otherSettingsModel: ISettingsEditorModel, + private settingHighlighter: SettingHighlighter, + @IPreferencesService private preferencesService: IPreferencesService, + @IInstantiationService private instantiationService: IInstantiationService, @IContextMenuService private contextMenuService: IContextMenuService ) { super(); - this._register(editor.onMouseUp(e => this.onEditorMouseUp(e))); + + this.editPreferenceWidgetForCusorPosition = this._register(this.instantiationService.createInstance(EditPreferenceWidget, editor)); + this.editPreferenceWidgetForMouseMove = this._register(this.instantiationService.createInstance(EditPreferenceWidget, editor)); + this.toggleEditPreferencesForMouseMoveDelayer = new Delayer(75); + + this._register(this.editPreferenceWidgetForCusorPosition.onClick(setting => this.onEditSettingClicked(this.editPreferenceWidgetForCusorPosition))); + this._register(this.editPreferenceWidgetForMouseMove.onClick(setting => this.onEditSettingClicked(this.editPreferenceWidgetForMouseMove))); + + this._register(this.editPreferenceWidgetForCusorPosition.onMouseOver(setting => this.onMouseOver(this.editPreferenceWidgetForCusorPosition))); + this._register(this.editPreferenceWidgetForMouseMove.onMouseOver(setting => this.onMouseOver(this.editPreferenceWidgetForMouseMove))); + + this._register(this.editor.onDidChangeCursorPosition(positionChangeEvent => this.onPositionChanged(positionChangeEvent))); + this._register(this.editor.onMouseMove(mouseMoveEvent => this.onMouseMoved(mouseMoveEvent))); } public render(settingsGroups: ISettingsGroup[]): void { - this.model = this.editor.getModel(); + this.editPreferenceWidgetForCusorPosition.hide(); + this.editPreferenceWidgetForMouseMove.hide(); this.settingsGroups = settingsGroups; - this.model.changeDecorations(changeAccessor => { - this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, []); - }); - this.model.changeDecorations(changeAccessor => { - this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, this.createDecorations(this.model)); - }); - } - private createDecorations(model: editorCommon.IModel): editorCommon.IModelDeltaDecoration[] { - let result: editorCommon.IModelDeltaDecoration[] = []; - for (const settingsGroup of this.settingsGroups) { - for (const settingsSection of settingsGroup.sections) { - for (const setting of settingsSection.settings) { - const decoration = this.createSettingDecoration(setting, model); - if (decoration) { - result.push(decoration); - } - } - } + const settings = this.getSettings(this.editor.getPosition().lineNumber); + if (settings.length) { + this.showEditPreferencesWidget(this.editPreferenceWidgetForCusorPosition, settings); } - return result; } - private createSettingDecoration(setting: ISetting, model: editorCommon.IModel): editorCommon.IModelDeltaDecoration { - const jsonSchema: IJSONSchema = this.getConfigurationsMap()[setting.key]; - if (jsonSchema) { - const canChooseValue = jsonSchema.enum || jsonSchema.type === 'boolean'; - if (this.isDefaultSettings || canChooseValue) { - const lineNumber = setting.keyRange.startLineNumber; - return { - range: { - startLineNumber: lineNumber, - startColumn: model.getLineMaxColumn(lineNumber), - endLineNumber: lineNumber, - endColumn: model.getLineMaxColumn(lineNumber), - }, - options: { - afterContentClassName: 'copySetting', - stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - hoverMessage: canChooseValue ? this.isDefaultSettings ? nls.localize('selectAndCopySetting', "Select a value and copy to Settings") - : nls.localize('selectValue', "Select a value") : nls.localize('copy', "Copy to Settings") - } - }; - } + private isDefaultSettings(): boolean { + return this.masterSettingsModel instanceof DefaultSettingsEditorModel; + } + + private onPositionChanged(positionChangeEvent: editorCommon.ICursorPositionChangedEvent) { + this.editPreferenceWidgetForMouseMove.hide(); + const settings = this.getSettings(positionChangeEvent.position.lineNumber); + if (settings.length) { + this.showEditPreferencesWidget(this.editPreferenceWidgetForCusorPosition, settings); + } else { + this.editPreferenceWidgetForCusorPosition.hide(); + } + } + + private onMouseMoved(mouseMoveEvent: IEditorMouseEvent): void { + const editPreferenceWidget = this.getEditPreferenceWidgetUnderMouse(mouseMoveEvent); + if (editPreferenceWidget) { + this.onMouseOver(editPreferenceWidget); + return; + } + this.settingHighlighter.clear(); + this.toggleEditPreferencesForMouseMoveDelayer.trigger(() => this.toggleEidtPreferenceWidgetForMouseMove(mouseMoveEvent)); + } + + private getEditPreferenceWidgetUnderMouse(mouseMoveEvent: IEditorMouseEvent): EditPreferenceWidget { + if (mouseMoveEvent.event.target === this.editPreferenceWidgetForMouseMove.getDomNode()) { + return this.editPreferenceWidgetForMouseMove; + } + if (mouseMoveEvent.event.target === this.editPreferenceWidgetForCusorPosition.getDomNode()) { + return this.editPreferenceWidgetForCusorPosition; } return null; } - private onEditorMouseUp(e: IEditorMouseEvent): void { - let range = e.target.range; - if (!range || !range.isEmpty) { - return; - } - if (!e.event.leftButton) { - return; - } - - switch (e.target.type) { - case editorCommon.MouseTargetType.CONTENT_EMPTY: - if (DOM.hasClass(e.target.element, 'copySetting')) { - this.onClick(e); - } - return; - default: - return; + private toggleEidtPreferenceWidgetForMouseMove(mouseMoveEvent: IEditorMouseEvent): void { + const settings = mouseMoveEvent.target.position ? this.getSettings(mouseMoveEvent.target.position.lineNumber) : null; + if (settings && settings.length) { + this.showEditPreferencesWidget(this.editPreferenceWidgetForMouseMove, settings); + } else { + this.editPreferenceWidgetForMouseMove.hide(); } } - private getConfigurationsMap(): { [qualifiedKey: string]: IJSONSchema } { - return Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); + private showEditPreferencesWidget(editPreferencesWidget: EditPreferenceWidget, settings: ISetting[]) { + editPreferencesWidget.show(settings[0].valueRange.startLineNumber, settings); + editPreferencesWidget.getDomNode().title = nls.localize('editTtile', "Edit"); } - private onClick(e: IEditorMouseEvent) { - const setting = this.getSetting(e.target.range.startLineNumber); - if (setting) { - let jsonSchema: IJSONSchema = this.getConfigurationsMap()[setting.key]; - const actions = this.getActions(setting, jsonSchema); - let elementPosition = DOM.getDomNodePagePosition(e.target.element); - const anchor = { x: elementPosition.left, y: elementPosition.top + elementPosition.height + 10 }; - this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, - getActions: () => TPromise.wrap(actions) - }); - } + private getSettings(lineNumber: number): ISetting[] { + const configurationMap = this.getConfigurationsMap(); + return this.getSettingsAtLineNumber(lineNumber).filter(setting => { + let jsonSchema: IJSONSchema = configurationMap[setting.key]; + return jsonSchema && (this.isDefaultSettings() || jsonSchema.type === 'boolean' || jsonSchema.enum); + }); } - private getSetting(lineNumber: number): ISetting { + private getSettingsAtLineNumber(lineNumber: number): ISetting[] { + const settings = []; for (const group of this.settingsGroups) { + if (group.range.startLineNumber > lineNumber) { + break; + } if (lineNumber >= group.range.startLineNumber && lineNumber <= group.range.endLineNumber) { for (const section of group.sections) { for (const setting of section.settings) { - if (lineNumber >= setting.keyRange.startLineNumber && lineNumber <= setting.keyRange.endLineNumber) { - return setting; + if (setting.range.startLineNumber > lineNumber) { + break; + } + if (lineNumber >= setting.range.startLineNumber && lineNumber <= setting.range.endLineNumber) { + settings.push(setting); } } } } } - return null; + return settings; + } + + private onMouseOver(editPreferenceWidget: EditPreferenceWidget): void { + this.settingHighlighter.highlight(editPreferenceWidget.preferences[0]); + } + + private onEditSettingClicked(editPreferenceWidget: EditPreferenceWidget): void { + const elementPosition = DOM.getDomNodePagePosition(editPreferenceWidget.getDomNode()); + const anchor = { x: elementPosition.left + elementPosition.width, y: elementPosition.top + elementPosition.height + 10 }; + const actions = this.getSettingsAtLineNumber(editPreferenceWidget.getLine()).length === 1 ? this.getActions(editPreferenceWidget.preferences[0], this.getConfigurationsMap()[editPreferenceWidget.preferences[0].key]) + : editPreferenceWidget.preferences.map(setting => new ContextSubMenu(setting.key, this.getActions(setting, this.getConfigurationsMap()[setting.key]))); + this.contextMenuService.showContextMenu({ + getAnchor: () => anchor, + getActions: () => TPromise.wrap(actions) + }); + } + + private getConfigurationsMap(): { [qualifiedKey: string]: IJSONSchema } { + return Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); } private getActions(setting: ISetting, jsonSchema: IJSONSchema): IAction[] { @@ -860,12 +901,12 @@ export class CopySettingActionRenderer extends Disposable { id: 'truthyValue', label: 'true', enabled: true, - run: () => this.settingsService.copyConfiguration({ key: setting.key, value: true }) + run: () => this.updateSetting(setting, true) }, { id: 'falsyValue', label: 'false', enabled: true, - run: () => this.settingsService.copyConfiguration({ key: setting.key, value: false }) + run: () => this.updateSetting(setting, false) }]; } if (jsonSchema.enum) { @@ -874,27 +915,64 @@ export class CopySettingActionRenderer extends Disposable { id: value, label: JSON.stringify(value), enabled: true, - run: () => this.settingsService.copyConfiguration({ key: setting.key, value }) + run: () => this.updateSetting(setting, value) }; }); } - return [{ - id: 'copyToSettings', - label: nls.localize('copyToSettings', "Copy to Settings"), - enabled: true, - run: () => this.settingsService.copyConfiguration(setting) - }]; + return this.getDefaultActions(setting); } - public dispose() { - if (this.model) { - this.model.deltaDecorations(this.decorationIds, []); + private getDefaultActions(setting: ISetting): IAction[] { + const settingInOtherModel = this.otherSettingsModel.getSetting(setting.key); + if (this.isDefaultSettings()) { + return [{ + id: 'setDefaultValue', + label: settingInOtherModel ? nls.localize('replaceDefaultValue', "Replace in Settings") : nls.localize('copyDefaultValue', "Copy to Settings"), + enabled: true, + run: () => this.updateSetting(setting, setting.value) + }]; } - super.dispose(); + return []; + } + + private updateSetting(setting: ISetting, value: any): void { + this._onUpdateSetting.fire({ setting, value }); } } -const DefaultSettingsEditorCommand = EditorCommand.bindToContribution((editor: editorCommon.ICommonCodeEditor) => editor.getContribution(PreferencesEditorContribution.ID)); +class SettingHighlighter extends Disposable { + + private fixedHighlighter: RangeHighlightDecorations; + private volatileHighlighter: RangeHighlightDecorations; + + constructor(private editor: editorCommon.ICommonCodeEditor, @IInstantiationService instantiationService: IInstantiationService) { + super(); + this.fixedHighlighter = this._register(instantiationService.createInstance(RangeHighlightDecorations)); + this.volatileHighlighter = this._register(instantiationService.createInstance(RangeHighlightDecorations)); + } + + highlight(setting: ISetting, fix: boolean = false) { + this.volatileHighlighter.removeHighlightRange(); + this.fixedHighlighter.removeHighlightRange(); + + const highlighter = fix ? this.fixedHighlighter : this.volatileHighlighter; + highlighter.highlightRange({ + range: setting.valueRange, + resource: this.editor.getModel().uri + }, this.editor); + + this.editor.revealLinesInCenterIfOutsideViewport(setting.valueRange.startLineNumber, setting.valueRange.endLineNumber - 1); + } + + clear(fix: boolean = false): void { + this.volatileHighlighter.removeHighlightRange(); + if (fix) { + this.fixedHighlighter.removeHighlightRange(); + } + } +} + +const DefaultSettingsEditorCommand = EditorCommand.bindToContribution((editor: editorCommon.ICommonCodeEditor) => editor.getContribution(DefaultSettingsEditorContribution.ID)); CommonEditorRegistry.registerEditorCommand(new DefaultSettingsEditorCommand({ id: DEFAULT_EDITOR_COMMAND_COLLAPSE_ALL, diff --git a/src/vs/workbench/parts/preferences/browser/preferencesService.ts b/src/vs/workbench/parts/preferences/browser/preferencesService.ts index b9b45f8167fa..24d871f3a585 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesService.ts @@ -10,8 +10,7 @@ import URI from 'vs/base/common/uri'; import { LinkedMap as Map } from 'vs/base/common/map'; import * as labels from 'vs/base/common/labels'; import { Disposable } from 'vs/base/common/lifecycle'; -import { parseTree, findNodeAtLocation } from 'vs/base/common/json'; -import { toResource, SideBySideEditorInput, EditorInput } from 'vs/workbench/common/editor'; +import { SideBySideEditorInput, EditorInput } from 'vs/workbench/common/editor'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceConfigurationService, WORKSPACE_CONFIG_DEFAULT_PATH } from 'vs/workbench/services/configuration/common/configuration'; @@ -23,9 +22,7 @@ import { IMessageService, Severity, IChoiceService } from 'vs/platform/message/c import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import * as editorCommon from 'vs/editor/common/editorCommon'; -import { IConfigurationEditingService, ConfigurationTarget, IConfigurationValue } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { IPreferencesService, IPreferencesEditorModel } from 'vs/workbench/parts/preferences/common/preferences'; import { SettingsEditorModel, DefaultSettingsEditorModel, DefaultKeybindingsEditorModel } from 'vs/workbench/parts/preferences/common/preferencesModels'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -44,9 +41,6 @@ interface IWorkbenchSettingsConfiguration { export class PreferencesService extends Disposable implements IPreferencesService { - static DEFAULT_SETTINGS_URI: URI = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/settings.json' }); - static DEFAULT_KEY_BINDINGS_URI: URI = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/keybindings.json' }); - _serviceBrand: any; // TODO:@sandy merge these models into editor inputs by extending resource editor model @@ -74,13 +68,16 @@ export class PreferencesService extends Disposable implements IPreferencesServic this.defaultPreferencesEditorModels = new Map(); } + readonly defaultSettingsResource = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/settings.json' }); + readonly defaultKeybindingsResource = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/keybindings.json' }); + createDefaultPreferencesEditorModel(uri: URI): TPromise { const editorModel = this.defaultPreferencesEditorModels.get(uri); if (editorModel) { return TPromise.as(editorModel); } - if (PreferencesService.DEFAULT_SETTINGS_URI.fsPath === uri.fsPath) { + if (this.defaultSettingsResource.fsPath === uri.fsPath) { return TPromise.join([this.extensionService.onReady(), this.fetchMostCommonlyUsedSettings()]) .then(result => { const mostCommonSettings = result[1]; @@ -90,7 +87,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic }); } - if (PreferencesService.DEFAULT_KEY_BINDINGS_URI.fsPath === uri.fsPath) { + if (this.defaultKeybindingsResource.fsPath === uri.fsPath) { const model = this.instantiationService.createInstance(DefaultKeybindingsEditorModel, uri); this.defaultPreferencesEditorModels.set(uri, model); return TPromise.wrap(model); @@ -128,14 +125,14 @@ export class PreferencesService extends Disposable implements IPreferencesServic openWorkspaceSettings(): TPromise { if (!this.contextService.hasWorkspace()) { this.messageService.show(Severity.Info, nls.localize('openFolderFirst', "Open a folder first to create workspace settings")); - return; + return TPromise.as(null); } return this.openSettings(ConfigurationTarget.WORKSPACE); } openGlobalKeybindingSettings(): TPromise { const emptyContents = '// ' + nls.localize('emptyKeybindingsHeader', "Place your key bindings in this file to overwrite the defaults") + '\n[\n]'; - return this.openTwoEditors(PreferencesService.DEFAULT_KEY_BINDINGS_URI, URI.file(this.environmentService.appKeybindingsPath), emptyContents).then(() => null); + return this.openTwoEditors(this.defaultKeybindingsResource, URI.file(this.environmentService.appKeybindingsPath), emptyContents).then(() => null); } private openEditableSettings(configurationTarget: ConfigurationTarget): TPromise { @@ -147,19 +144,6 @@ export class PreferencesService extends Disposable implements IPreferencesServic })); } - public copyConfiguration(configurationValue: IConfigurationValue): void { - const configurationTarget = this.getConfigurationTargetForCurrentActiveEditor(); - if (configurationTarget !== null) { - this.telemetryService.publicLog('defaultSettingsActions.copySetting', { userConfigurationKeys: [configurationValue.key] }); - const editorControl = this.editorService.getActiveEditor().getControl(); - this.configurationEditingService.writeConfiguration(configurationTarget, configurationValue, { writeToBuffer: true, autoSave: true }) - .then(() => { - editorControl.focus(); - editorControl.setSelection(this.getSelectionRange(configurationValue.key, editorControl.getModel())); - }, error => this.messageService.show(Severity.Error, error)); - } - } - private resolveSettingsEditorModel(configurationTarget: ConfigurationTarget): TPromise { const settingsUri = this.getEditableSettingsURI(configurationTarget); if (settingsUri) { @@ -225,12 +209,12 @@ export class PreferencesService extends Disposable implements IPreferencesServic switch (configurationTarget) { case ConfigurationTarget.USER: if (!this.defaultSettingsEditorInputForUser) { - this.defaultSettingsEditorInputForUser = this._register(this.instantiationService.createInstance(DefaultPreferencesEditorInput, PreferencesService.DEFAULT_SETTINGS_URI)); + this.defaultSettingsEditorInputForUser = this._register(this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.defaultSettingsResource)); } return this.defaultSettingsEditorInputForUser; case ConfigurationTarget.WORKSPACE: if (!this.defaultSettingsEditorInputForWorkspace) { - this.defaultSettingsEditorInputForWorkspace = this._register(this.instantiationService.createInstance(DefaultPreferencesEditorInput, PreferencesService.DEFAULT_SETTINGS_URI)); + this.defaultSettingsEditorInputForWorkspace = this._register(this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.defaultSettingsResource)); } return this.defaultSettingsEditorInputForWorkspace; } @@ -271,40 +255,6 @@ export class PreferencesService extends Disposable implements IPreferencesServic }); } - private getConfigurationTargetForCurrentActiveEditor(): ConfigurationTarget { - const activeEditor = this.editorService.getActiveEditor(); - if (activeEditor) { - const file = toResource(activeEditor.input, { supportSideBySide: true, filter: 'file' }); - if (file) { - return this.getConfigurationTarget(file); - } - } - return null; - } - - private getConfigurationTarget(resource: URI): ConfigurationTarget { - if (this.getEditableSettingsURI(ConfigurationTarget.USER).fsPath === resource.fsPath) { - return ConfigurationTarget.USER; - } - const workspaceSettingsUri = this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE); - if (workspaceSettingsUri && workspaceSettingsUri.fsPath === resource.fsPath) { - return ConfigurationTarget.WORKSPACE; - } - return null; - } - - private getSelectionRange(setting: string, model: editorCommon.IModel): editorCommon.IRange { - const tree = parseTree(model.getValue()); - const node = findNodeAtLocation(tree, [setting]); - const position = model.getPositionAt(node.offset); - return { - startLineNumber: position.lineNumber, - startColumn: position.column, - endLineNumber: position.lineNumber, - endColumn: position.column + node.length - }; - } - private fetchMostCommonlyUsedSettings(): TPromise { return TPromise.wrap([ 'editor.fontSize', diff --git a/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts index 3a9ca8db9ba8..a6260610aec7 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts @@ -91,8 +91,11 @@ export class SettingsGroupTitleWidget extends Widget implements IViewZone { } private layout(): void { - this.titleContainer.style.lineHeight = this.editor.getConfiguration().lineHeight + 3 + 'px'; - this.titleContainer.style.fontSize = this.editor.getConfiguration().fontInfo.fontSize + 'px'; + const configuration = this.editor.getConfiguration(); + const layoutInfo = this.editor.getLayoutInfo(); + this.titleContainer.style.width = layoutInfo.contentWidth - layoutInfo.verticalScrollbarWidth + 'px'; + this.titleContainer.style.lineHeight = configuration.lineHeight + 3 + 'px'; + this.titleContainer.style.fontSize = configuration.fontInfo.fontSize + 'px'; const iconSize = this.getIconSize(); this.icon.style.height = `${iconSize}px`; this.icon.style.width = `${iconSize}px`; @@ -344,4 +347,92 @@ export class SettingsCountWidget extends Widget implements IOverlayWidget { preference: null }; } +} + +export class EditPreferenceWidget extends Widget implements IOverlayWidget { + + private static counter: number = 1; + + private _domNode: HTMLElement; + private _visible: boolean; + private _line: number; + private _id: string; + private _preferences: T[]; + + private _onClick: Emitter = new Emitter(); + public get onClick(): Event { return this._onClick.event; } + + private _onMouseOver: Emitter = new Emitter(); + public get onMouseOver(): Event { return this._onMouseOver.event; } + + constructor(private editor: ICodeEditor, + @IContextMenuService contextMenuService: IContextMenuService + ) { + super(); + this._id = 'preferences.editPreferenceWidget' + EditPreferenceWidget.counter++; + this.editor.addOverlayWidget(this); + this._register(this.editor.onDidScrollChange(() => { + if (this._visible) { + this._layout(); + } + })); + } + + public dispose(): void { + this.editor.removeOverlayWidget(this); + super.dispose(); + } + + getId(): string { + return this._id; + } + + getDomNode(): HTMLElement { + if (!this._domNode) { + this._domNode = document.createElement('div'); + this._domNode.style.width = '20px'; + this._domNode.style.height = '20px'; + this._domNode.className = 'edit-preferences-widget hidden'; + this.onclick(this._domNode, e => this._onClick.fire()); + this.onmouseover(this._domNode, e => this._onMouseOver.fire()); + } + return this._domNode; + } + + getPosition(): IOverlayWidgetPosition { + return null; + } + + getLine(): number { + return this._line; + } + + show(line: number, preferences: T[]): void { + this._preferences = preferences; + if (!this._visible || this._line !== line) { + this._line = line; + this._visible = true; + this._layout(); + } + } + + get preferences(): T[] { + return this._preferences; + } + + hide(): void { + if (this._visible) { + this._visible = false; + this._domNode.classList.add('hidden'); + } + } + + private _layout(): void { + const topForLineNumber = this.editor.getTopForLineNumber(this._line); + const editorScrollTop = this.editor.getScrollTop(); + + this._domNode.style.top = `${topForLineNumber - editorScrollTop - 2}px`; + this._domNode.style.left = '0px'; + this._domNode.classList.remove('hidden'); + } } \ No newline at end of file diff --git a/src/vs/workbench/parts/preferences/common/preferences.ts b/src/vs/workbench/parts/preferences/common/preferences.ts index 46ac5b848028..83cafc86be65 100644 --- a/src/vs/workbench/parts/preferences/common/preferences.ts +++ b/src/vs/workbench/parts/preferences/common/preferences.ts @@ -8,7 +8,6 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { LinkedMap as Map } from 'vs/base/common/map'; import { IRange } from 'vs/editor/common/editorCommon'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationValue } from 'vs/workbench/services/configuration/common/configurationEditing'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export interface ISettingsGroup { @@ -20,8 +19,8 @@ export interface ISettingsGroup { } export interface ISettingsSection { - descriptionRange?: IRange; - description?: string; + titleRange?: IRange; + title?: string; settings: ISetting[]; } @@ -31,8 +30,8 @@ export interface ISetting { keyRange: IRange; value: any; valueRange: IRange; - description: string; - descriptionRange: IRange; + description: string[]; + descriptionRanges: IRange[]; } export interface IFilterResult { @@ -49,6 +48,7 @@ export interface IPreferencesEditorModel { export interface ISettingsEditorModel extends IPreferencesEditorModel { settingsGroups: ISettingsGroup[]; groupsTerms: string[]; + getSetting(key: string): ISetting; filterSettings(filter: string): IFilterResult; } @@ -60,14 +60,15 @@ export const IPreferencesService = createDecorator('prefere export interface IPreferencesService { _serviceBrand: any; + defaultSettingsResource: URI; + defaultKeybindingsResource: URI; + createDefaultPreferencesEditorModel(uri: URI): TPromise; resolvePreferencesEditorModel(uri: URI): TPromise; openGlobalSettings(): TPromise; openWorkspaceSettings(): TPromise; openGlobalKeybindingSettings(): TPromise; - - copyConfiguration(configurationValue: IConfigurationValue): void; } export const CONTEXT_DEFAULT_SETTINGS_EDITOR = new RawContextKey('defaultSettingsEditor', false); diff --git a/src/vs/workbench/parts/preferences/common/preferencesModels.ts b/src/vs/workbench/parts/preferences/common/preferencesModels.ts index fb3d68dd0fc9..57ca716dda37 100644 --- a/src/vs/workbench/parts/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/parts/preferences/common/preferencesModels.ts @@ -12,16 +12,15 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/platform'; import { visit, JSONVisitor } from 'vs/base/common/json'; import { IModel, IRange } from 'vs/editor/common/editorCommon'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { IConfigurationNode, IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { ISettingsEditorModel, IKeybindingsEditorModel, ISettingsGroup, ISetting, IFilterResult, ISettingsSection } from 'vs/workbench/parts/preferences/common/preferences'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; -import { IFilter, IMatch, or, matchesContiguousSubString, matchesPrefix, matchesFuzzy, matchesWords } from 'vs/base/common/filters'; +import { IMatch, or, matchesContiguousSubString, matchesPrefix, matchesCamelCase, matchesWords } from 'vs/base/common/filters'; export abstract class AbstractSettingsModel extends Disposable { - static _fuzzyFilter: IFilter = or(matchesPrefix, matchesContiguousSubString, matchesWords, matchesFuzzy); - public get groupsTerms(): string[] { return this.settingsGroups.map(group => '@' + group.id); } @@ -53,7 +52,7 @@ export abstract class AbstractSettingsModel extends Disposable { for (const section of group.sections) { const settings: ISetting[] = []; for (const setting of section.settings) { - const settingMatches = this._findMatchesInSetting(filter, regex, setting); + const settingMatches = this._findMatchesInSetting(filter, setting); if (groupMatched || settingMatches.length > 0) { settings.push(setting); } @@ -61,9 +60,9 @@ export abstract class AbstractSettingsModel extends Disposable { } if (settings.length) { sections.push({ - description: section.description, + title: section.title, settings, - descriptionRange: section.descriptionRange + titleRange: section.titleRange }); } } @@ -88,7 +87,113 @@ export abstract class AbstractSettingsModel extends Disposable { return null; } - protected abstract _findMatchesInSetting(searchString: string, searchRegex: RegExp, setting: ISetting): IRange[]; + public getSetting(key: string): ISetting { + for (const group of this.settingsGroups) { + for (const section of group.sections) { + for (const setting of section.settings) { + if (key === setting.key) { + return setting; + } + } + } + } + return null; + } + + private _findMatchesInSetting(searchString: string, setting: ISetting): IRange[] { + const registry: { [qualifiedKey: string]: IJSONSchema } = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const schema: IJSONSchema = registry[setting.key]; + + let words = searchString.split(' '); + let descriptionMatchingWords: Map = new Map(); + let keyMatchingWords: Map = new Map(); + let valueMatchingWords: Map = new Map(); + const settingKeyAsWords: string = setting.key.split('.').join(' '); + + for (const word of words) { + for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) { + const descriptionMatches = matchesWords(word, setting.description[lineIndex], true); + if (descriptionMatches) { + descriptionMatchingWords.set(word, descriptionMatches.map(match => this.toDescriptionRange(setting, match, lineIndex))); + } + } + + const keyMatches = or(matchesWords, matchesCamelCase)(word, settingKeyAsWords); + if (keyMatches) { + keyMatchingWords.set(word, keyMatches.map(match => this.toKeyRange(setting, match))); + } + + if (schema.type === 'string' || schema.enum) { + const valueMatches = matchesContiguousSubString(word, setting.value); + if (valueMatches) { + valueMatchingWords.set(word, valueMatches.map(match => this.toValueRange(setting, match))); + } else if (schema.enum && schema.enum.some(enumValue => !!matchesContiguousSubString(word, enumValue))) { + valueMatchingWords.set(word, []); + } + } + } + + const descriptionRanges: IRange[] = []; + for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) { + const matches = or(matchesContiguousSubString)(searchString, setting.description[lineIndex]) || []; + descriptionRanges.push(...matches.map(match => this.toDescriptionRange(setting, match, lineIndex))); + } + if (descriptionRanges.length === 0) { + descriptionRanges.push(...this.getRangesForWords(words, descriptionMatchingWords, [keyMatchingWords, valueMatchingWords])); + } + + const keyMatches = or(matchesPrefix, matchesContiguousSubString)(searchString, setting.key); + const keyRanges: IRange[] = keyMatches ? keyMatches.map(match => this.toKeyRange(setting, match)) : this.getRangesForWords(words, keyMatchingWords, [descriptionMatchingWords, valueMatchingWords]); + + let valueRanges: IRange[] = []; + if (typeof setting.value === 'string') { + const valueMatches = or(matchesPrefix, matchesContiguousSubString)(searchString, setting.value); + valueRanges = valueMatches ? valueMatches.map(match => this.toValueRange(setting, match)) : this.getRangesForWords(words, valueMatchingWords, [keyMatchingWords, descriptionMatchingWords]); + } + + return [...descriptionRanges, ...keyRanges, ...valueRanges]; + } + + private getRangesForWords(words: string[], from: Map, others: Map[]): IRange[] { + const result: IRange[] = []; + for (const word of words) { + const ranges = from.get(word); + if (ranges) { + result.push(...ranges); + } else if (others.every(o => !o.has(word))) { + return []; + } + } + return result; + } + + private toKeyRange(setting: ISetting, match: IMatch): IRange { + return { + startLineNumber: setting.keyRange.startLineNumber, + startColumn: setting.keyRange.startColumn + match.start, + endLineNumber: setting.keyRange.startLineNumber, + endColumn: setting.keyRange.startColumn + match.end + }; + } + + private toDescriptionRange(setting: ISetting, match: IMatch, lineIndex: number): IRange { + return { + startLineNumber: setting.descriptionRanges[lineIndex].startLineNumber + lineIndex, + startColumn: setting.descriptionRanges[lineIndex].startColumn + match.start, + endLineNumber: setting.descriptionRanges[lineIndex].startLineNumber + lineIndex, + endColumn: setting.descriptionRanges[lineIndex].startColumn + match.end + }; + } + + private toValueRange(setting: ISetting, match: IMatch): IRange { + return { + startLineNumber: setting.valueRange.startLineNumber, + startColumn: setting.valueRange.startColumn + match.start + 1, + endLineNumber: setting.valueRange.startLineNumber, + endColumn: setting.valueRange.startColumn + match.end + 1 + }; + } + public abstract settingsGroups: ISettingsGroup[]; } @@ -183,7 +288,7 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti // setting started let settingStartPosition = model.getPositionAt(offset); settings.push({ - description: '', + description: [], key: name, keyRange: { startLineNumber: settingStartPosition.lineNumber, @@ -199,7 +304,7 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti }, value: null, valueRange: null, - descriptionRange: null, + descriptionRanges: null, }); } }, @@ -248,6 +353,10 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti }, onLiteralValue: onValue, onError: (error) => { + const setting = settings[settings.length - 1]; + if (!setting.range || !setting.keyRange || !setting.valueRange) { + settings.pop(); + } } }; visit(model.getValue(), visitor); @@ -262,20 +371,6 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti range }] : []; } - - protected _findMatchesInSetting(searchString: string, searchRegex: RegExp, setting: ISetting): IRange[] { - const result: IRange[] = []; - for (let lineNumber = setting.range.startLineNumber; lineNumber <= setting.range.endLineNumber; lineNumber++) { - result.push(...this._findMatchesInLine(searchString, lineNumber)); - } - return result; - } - - private _findMatchesInLine(searchString: string, lineNumber: number): IRange[] { - return this.model.findMatches(searchString, { - startLineNumber: lineNumber, startColumn: this.model.getLineMinColumn(lineNumber), endLineNumber: lineNumber, endColumn: this.model.getLineMaxColumn(lineNumber), - }, false, false, false); - } } export class DefaultSettingsEditorModel extends AbstractSettingsModel implements ISettingsEditorModel { @@ -383,7 +478,7 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements result.push(settingsGroup); } } else { - settingsGroup.sections[settingsGroup.sections.length - 1].description = config.title; + settingsGroup.sections[settingsGroup.sections.length - 1].title = config.title; } } if (config.properties) { @@ -394,8 +489,8 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements const configurationSettings: ISetting[] = Object.keys(config.properties).map((key) => { const prop = config.properties[key]; const value = prop.default; - const description = prop.description || ''; - return { key, value, description, range: null, keyRange: null, valueRange: null, descriptionRange: null }; + const description = (prop.description || '').split('\n'); + return { key, value, description, range: null, keyRange: null, valueRange: null, descriptionRanges: [] }; }); settingsGroup.sections[settingsGroup.sections.length - 1].settings.push(...configurationSettings); } @@ -449,16 +544,20 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements this._contentByLines.push(''); let groupStart = this._contentByLines.length + 1; for (const section of group.sections) { - if (section.description) { + if (section.title) { let sectionTitleStart = this._contentByLines.length + 1; - this.addDescription(section.description, this.indent, this._contentByLines); - section.descriptionRange = { startLineNumber: sectionTitleStart, startColumn: 1, endLineNumber: this._contentByLines.length, endColumn: this._contentByLines[this._contentByLines.length - 1].length }; + this.addDescription([section.title], this.indent, this._contentByLines); + section.titleRange = { startLineNumber: sectionTitleStart, startColumn: 1, endLineNumber: this._contentByLines.length, endColumn: this._contentByLines[this._contentByLines.length - 1].length }; } for (const setting of section.settings) { const settingStart = this._contentByLines.length + 1; - this.addDescription(setting.description, this.indent, this._contentByLines); - setting.descriptionRange = { startLineNumber: settingStart, startColumn: 1, endLineNumber: this._contentByLines.length, endColumn: this._contentByLines[this._contentByLines.length - 1].length }; + setting.descriptionRanges = []; + const descriptionPreValue = this.indent + '// '; + for (const line of setting.description) { + this._contentByLines.push(descriptionPreValue + line); + setting.descriptionRanges.push({ startLineNumber: this._contentByLines.length, startColumn: this._contentByLines[this._contentByLines.length - 1].indexOf(line) + 1, endLineNumber: this._contentByLines.length, endColumn: this._contentByLines[this._contentByLines.length - 1].length }); + } let preValueConent = this.indent; const keyString = JSON.stringify(setting.key); @@ -489,41 +588,13 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements return lastSetting; } - private addDescription(description: string, indent: string, result: string[]) { - const multiLines = description.split('\n'); - for (const line of multiLines) { + private addDescription(description: string[], indent: string, result: string[]) { + for (const line of description) { result.push(indent + '// ' + line); } } - protected _findMatchesInSetting(searchString: string, searchRegex: RegExp, setting: ISetting): IRange[] { - const result: IRange[] = [...this._findMatchesInDescription(searchString, setting)]; - for (let lineNumber = setting.valueRange.startLineNumber; lineNumber <= setting.valueRange.endLineNumber; lineNumber++) { - result.push(...this._findMatchesInLine(searchRegex, lineNumber)); - } - return result; - } - - private _findMatchesInDescription(searchString: string, setting: ISetting): IRange[] { - const result: IRange[] = []; - for (let lineNumber = setting.descriptionRange.startLineNumber; lineNumber <= setting.descriptionRange.endLineNumber; lineNumber++) { - const content = this._contentByLines[lineNumber - 1]; - const matches: IMatch[] = AbstractSettingsModel._fuzzyFilter(searchString, content); - if (matches) { - result.push(...matches.map(match => { - return { - startLineNumber: lineNumber, - startColumn: match.start + 1, - endLineNumber: lineNumber, - endColumn: match.end + 1 - }; - })); - } - } - return result; - } - - private _findMatchesInLine(searchRegex: RegExp, lineNumber: number): IRange[] { + /*private _findMatchesInLine(searchRegex: RegExp, lineNumber: number): IRange[] { const result: IRange[] = []; const text = this._contentByLines[lineNumber - 1]; var m: RegExpExecArray; @@ -541,7 +612,7 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements } } while (m); return result; - } + }*/ } export class DefaultKeybindingsEditorModel implements IKeybindingsEditorModel { diff --git a/src/vs/workbench/parts/search/browser/media/searchviewlet.css b/src/vs/workbench/parts/search/browser/media/searchviewlet.css index b4f7cb01317e..11a0c7099229 100644 --- a/src/vs/workbench/parts/search/browser/media/searchviewlet.css +++ b/src/vs/workbench/parts/search/browser/media/searchviewlet.css @@ -42,18 +42,15 @@ .search-viewlet .search-widget .replace-container { margin-top: 6px; position: relative; + display: inline-flex; + height: 25px; } .search-viewlet .search-widget .replace-container.disabled { display: none; } -.search-viewlet .search-widget .replace-container > * { - display: inline-block; -} - .search-viewlet .search-widget .replace-container .monaco-action-bar { - position: absolute; margin-left: 3px; } diff --git a/src/vs/workbench/parts/search/browser/search.contribution.ts b/src/vs/workbench/parts/search/browser/search.contribution.ts index 49de2d67ff0a..08e9598402a3 100644 --- a/src/vs/workbench/parts/search/browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/browser/search.contribution.ts @@ -126,6 +126,9 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(searchActions.FocusAct registry.registerWorkbenchAction(new SyncActionDescriptor(searchActions.FindInFilesAction, Constants.FindInFilesActionId, nls.localize('findInFiles', "Find in Files"), { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_F }, Constants.SearchInputBoxFocussedKey.toNegated()), 'Find in Files'); +registry.registerWorkbenchAction(new SyncActionDescriptor(searchActions.FocusNextSearchResultAction, searchActions.FocusNextSearchResultAction.ID, searchActions.FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }), ''); +registry.registerWorkbenchAction(new SyncActionDescriptor(searchActions.FocusPreviousSearchResultAction, searchActions.FocusPreviousSearchResultAction.ID, searchActions.FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }), ''); + registry.registerWorkbenchAction(new SyncActionDescriptor(searchActions.ReplaceInFilesAction, searchActions.ReplaceInFilesAction.ID, searchActions.ReplaceInFilesAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_H }), 'Replace in Files'); registry.registerWorkbenchAction(new SyncActionDescriptor(searchActions.CloseReplaceAction, Constants.CloseReplaceWidgetActionId, '', { primary: KeyCode.Escape }, ContextKeyExpr.and(Constants.SearchViewletVisibleKey, Constants.ReplaceInputBoxFocussedKey)), ''); diff --git a/src/vs/workbench/parts/search/browser/searchActions.ts b/src/vs/workbench/parts/search/browser/searchActions.ts index 66fed1dbee6b..c514ad5bab91 100644 --- a/src/vs/workbench/parts/search/browser/searchActions.ts +++ b/src/vs/workbench/parts/search/browser/searchActions.ts @@ -266,6 +266,36 @@ export class ClearSearchResultsAction extends Action { } } +export class FocusNextSearchResultAction extends Action { + public static ID = 'search.action.focusNextSearchResult'; + public static LABEL = nls.localize('FocusNextSearchResult.label', "Focus next search result"); + + constructor(id: string, label: string, @IViewletService private viewletService: IViewletService) { + super(id, label); + } + + public run(): TPromise { + return this.viewletService.openViewlet(Constants.VIEWLET_ID).then((searchViewlet: SearchViewlet) => { + searchViewlet.selectNextResult(); + }); + } +} + +export class FocusPreviousSearchResultAction extends Action { + public static ID = 'search.action.focusPreviousSearchResult'; + public static LABEL = nls.localize('FocusPreviousSearchResult.label', "Focus previous search result"); + + constructor(id: string, label: string, @IViewletService private viewletService: IViewletService) { + super(id, label); + } + + public run(): TPromise { + return this.viewletService.openViewlet(Constants.VIEWLET_ID).then((searchViewlet: SearchViewlet) => { + searchViewlet.selectPreviousResult(); + }); + } +} + export abstract class AbstractSearchAndReplaceAction extends Action { /** diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 255eae24ba1d..1fcd97fd7735 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -410,7 +410,7 @@ export class SearchViewlet extends Viewlet { } let sideBySide = (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey)); - let focusEditor = (keyboard && (originalEvent).keyCode === KeyCode.Enter) || doubleClick; + let focusEditor = (keyboard && (originalEvent).keyCode === KeyCode.Enter) || doubleClick || event.payload.focusEditor; if (element instanceof Match) { let selectedMatch: Match = element; @@ -449,6 +449,60 @@ export class SearchViewlet extends Viewlet { } } + public selectNextResult(): void { + const eventPayload = { focusEditor: true }; + const [selected]: FileMatchOrMatch[] = this.tree.getSelection(); + const navigator = this.tree.getNavigator(selected, /*subTreeOnly=*/false); + let next = navigator.next(); + + if (!next) { + return; + } + + // Expand and go past FileMatch nodes + if (!(next instanceof Match)) { + if (!this.tree.isExpanded(next)) { + this.tree.expand(next); + } + + // Select the FileMatch's first child + next = navigator.next(); + } + + // Reveal the newly selected element + this.tree.setFocus(next, eventPayload); + this.tree.setSelection([next], eventPayload); + this.tree.reveal(next); + } + + public selectPreviousResult(): void { + const eventPayload = { focusEditor: true }; + const [selected]: FileMatchOrMatch[] = this.tree.getSelection(); + const navigator = this.tree.getNavigator(selected, /*subTreeOnly=*/false); + + let prev = navigator.previous(); + if (!prev) { + return; + } + + // Expand and go past FileMatch nodes + if (!(prev instanceof Match)) { + prev = navigator.previous(); + if (!(prev instanceof Match)) { + // There is a second non-Match result, which must be a collapsed FileMatch. + // Expand it then select its last child. + navigator.next(); + this.tree.expand(prev); + prev = navigator.previous(); + } + } + + // Reveal the newly selected element + this.tree.setFocus(prev, eventPayload); + this.tree.setSelection([prev], eventPayload); + this.tree.reveal(prev); + } + public setVisible(visible: boolean): TPromise { let promise: TPromise; this.viewletVisible.set(visible); diff --git a/src/vs/workbench/parts/search/common/constants.ts b/src/vs/workbench/parts/search/common/constants.ts index 8b3a46f966d4..42daf7da501f 100644 --- a/src/vs/workbench/parts/search/common/constants.ts +++ b/src/vs/workbench/parts/search/common/constants.ts @@ -9,6 +9,7 @@ export const VIEWLET_ID = 'workbench.view.search'; export const FindInFilesActionId = 'workbench.action.findInFiles'; export const FocusActiveEditorActionId = 'search.action.focusActiveEditor'; + export const ToggleCaseSensitiveActionId = 'toggleSearchCaseSensitive'; export const ToggleWholeWordActionId = 'toggleSearchWholeWord'; export const ToggleRegexActionId = 'toggleSearchRegex'; diff --git a/src/vs/workbench/parts/search/common/searchModel.ts b/src/vs/workbench/parts/search/common/searchModel.ts index 9847b83b1f00..e2ba6cde2ed5 100644 --- a/src/vs/workbench/parts/search/common/searchModel.ts +++ b/src/vs/workbench/parts/search/common/searchModel.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as timer from 'vs/base/common/timer'; import paths = require('vs/base/common/paths'); import objects = require('vs/base/common/objects'); import strings = require('vs/base/common/strings'); @@ -544,14 +543,12 @@ export class SearchModel extends Disposable { this._searchResult.query = this._searchQuery.contentPattern; this._replacePattern = new ReplacePattern(this._replaceString, this._searchQuery.contentPattern); - const timerEvent = timer.start(timer.Topic.WORKBENCH, 'Search'); this.currentRequest = this.searchService.search(this._searchQuery); const onDone = fromPromise(this.currentRequest); const onDoneStopwatch = stopwatch(onDone); const start = Date.now(); - onDone(() => timerEvent.stop()); onDoneStopwatch(duration => this.telemetryService.publicLog('searchResultsFinished', { duration })); const progressEmitter = new Emitter(); diff --git a/src/vs/workbench/parts/search/test/common/searchModel.test.ts b/src/vs/workbench/parts/search/test/common/searchModel.test.ts index 3a5417c1ff82..ca52028f2615 100644 --- a/src/vs/workbench/parts/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/parts/search/test/common/searchModel.test.ts @@ -9,7 +9,6 @@ import * as sinon from 'sinon'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { DeferredPPromise } from 'vs/base/test/common/utils'; import { PPromise } from 'vs/base/common/winjs.base'; -import { nullEvent } from 'vs/base/common/timer'; import { SearchModel } from 'vs/workbench/parts/search/common/searchModel'; import URI from 'vs/base/common/uri'; import { IFileMatch, ILineMatch, ISearchService, ISearchComplete, ISearchProgressItem, IUncachedSearchStats } from 'vs/platform/search/common/search'; @@ -20,6 +19,27 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; +const nullEvent = new class { + + public id: number; + public topic: string; + public name: string; + public description: string; + public data: any; + + public startTime: Date; + public stopTime: Date; + + public stop(): void { + return; + } + + public timeTaken(): number { + return -1; + } +}; + + suite('SearchModel', () => { let instantiationService: TestInstantiationService; @@ -292,4 +312,4 @@ suite('SearchModel', () => { return instantiationService.createInstance(ModelServiceImpl); } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css b/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css index f27caed5b945..f6c97a0c0b4d 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css @@ -181,17 +181,17 @@ /* Base selection colors */ -.monaco-workbench .panel.integrated-terminal .xterm *::selection { +.monaco-workbench .panel.integrated-terminal .xterm ::selection { color: #FFF; background-color: rgba(51, 51, 51, 0.996); } -.vs-dark .monaco-workbench .panel.integrated-terminal .xterm *::selection { +.vs-dark .monaco-workbench .panel.integrated-terminal .xterm ::selection { color: #1e1e1e; background-color: rgba(204, 204, 204, 0.996); } -.hc-black .monaco-workbench .panel.integrated-terminal .xterm *::selection { +.hc-black .monaco-workbench .panel.integrated-terminal .xterm ::selection { color: #000; background-color: rgba(255, 255, 255, 0.996); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts index c0949423672a..4aa82872e94e 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts @@ -227,7 +227,7 @@ export class SwitchTerminalInstanceAction extends Action { } public run(item?: string): TPromise { - if (!item) { + if (!item || !item.split) { return TPromise.as(null); } const selectedTerminalIndex = parseInt(item.split(':')[0], 10) - 1; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts index 113024a23dbd..7d05f7e29aa5 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IConfiguration, DefaultConfig } from 'vs/editor/common/config/defaultConfig'; +import { IConfiguration as IEditorConfiguration, DefaultConfig } from 'vs/editor/common/config/defaultConfig'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITerminalConfiguration, ITerminalConfigHelper, ITerminalFont, IShell } from 'vs/workbench/parts/terminal/common/terminal'; import { Platform } from 'vs/base/common/platform'; @@ -91,16 +91,16 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { this._charMeasureElement = document.createElement('div'); this.panelContainer.appendChild(this._charMeasureElement); } - let style = this._charMeasureElement.style; + const style = this._charMeasureElement.style; style.display = 'block'; style.fontFamily = fontFamily; style.fontSize = fontSize + 'px'; - style.height = Math.floor(lineHeight * fontSize) + 'px'; + style.lineHeight = lineHeight.toString(10); this._charMeasureElement.innerText = 'X'; - let rect = this._charMeasureElement.getBoundingClientRect(); + const rect = this._charMeasureElement.getBoundingClientRect(); style.display = 'none'; - let charWidth = Math.ceil(rect.width); - let charHeight = Math.ceil(rect.height); + const charWidth = rect.width; + const charHeight = rect.height; return { fontFamily, fontSize: fontSize + 'px', @@ -115,37 +115,46 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { * terminal.integrated.fontSize, terminal.integrated.lineHeight configuration properties */ public getFont(): ITerminalFont { - let terminalConfig = this._configurationService.getConfiguration().terminal.integrated; - let editorConfig = this._configurationService.getConfiguration(); + const config = this._configurationService.getConfiguration(); + const editorConfig = (config).editor; + const terminalConfig = (config).terminal.integrated; - let fontFamily = terminalConfig.fontFamily || editorConfig.editor.fontFamily; + const fontFamily = terminalConfig.fontFamily || editorConfig.fontFamily; let fontSize = this._toInteger(terminalConfig.fontSize, 0); if (fontSize <= 0) { fontSize = DefaultConfig.editor.fontSize; } let lineHeight = terminalConfig.lineHeight <= 0 ? DEFAULT_LINE_HEIGHT : terminalConfig.lineHeight; + if (!lineHeight) { + lineHeight = DEFAULT_LINE_HEIGHT; + } return this._measureFont(fontFamily, fontSize, lineHeight); } public getFontLigaturesEnabled(): boolean { - let terminalConfig = this._configurationService.getConfiguration().terminal.integrated; - return terminalConfig.fontLigatures; + const terminalConfig = this._configurationService.getConfiguration(); + return terminalConfig.terminal.integrated.fontLigatures; } public getCursorBlink(): boolean { - let terminalConfig = this._configurationService.getConfiguration().terminal.integrated; - return terminalConfig.cursorBlinking; + const terminalConfig = this._configurationService.getConfiguration(); + return terminalConfig.terminal.integrated.cursorBlinking; } public getRightClickCopyPaste(): boolean { - let config = this._configurationService.getConfiguration(); + const config = this._configurationService.getConfiguration(); return config.terminal.integrated.rightClickCopyPaste; } + public getCommandsToSkipShell(): string[] { + const config = this._configurationService.getConfiguration(); + return config.terminal.integrated.commandsToSkipShell; + } + public getShell(): IShell { - let config = this._configurationService.getConfiguration(); - let shell: IShell = { + const config = this._configurationService.getConfiguration(); + const shell: IShell = { executable: '', args: [] }; @@ -190,9 +199,4 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { } return r; } - - public getCommandsToSkipShell(): string[] { - let config = this._configurationService.getConfiguration(); - return config.terminal.integrated.commandsToSkipShell; - } } \ No newline at end of file diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 07181e043b56..f2b76b3f41c2 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -25,9 +25,10 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { TabFocus } from 'vs/editor/common/config/commonEditorConfig'; import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; +/** The amount of time to consider terminal errors to be related to the launch */ +const LAUNCHING_DURATION = 500; + export class TerminalInstance implements ITerminalInstance { - /** The amount of time to consider terminal errors to be related to the launch */ - private static readonly LAUNCHING_DURATION = 500; private static readonly EOL_REGEX = /\r?\n/g; private static _idCounter = 1; @@ -155,8 +156,8 @@ export class TerminalInstance implements ITerminalInstance { }, 0); }); - let xtermHelper: HTMLElement = this._xterm.element.querySelector('.xterm-helpers'); - let focusTrap: HTMLElement = document.createElement('div'); + const xtermHelper: HTMLElement = this._xterm.element.querySelector('.xterm-helpers'); + const focusTrap: HTMLElement = document.createElement('div'); focusTrap.setAttribute('tabindex', '0'); DOM.addClass(focusTrap, 'focus-trap'); focusTrap.addEventListener('focus', function (event: FocusEvent) { @@ -164,7 +165,7 @@ export class TerminalInstance implements ITerminalInstance { while (!DOM.hasClass(currentElement, 'part')) { currentElement = currentElement.parentElement; } - let hidePanelElement = currentElement.querySelector('.hide-panel-action'); + const hidePanelElement = currentElement.querySelector('.hide-panel-action'); hidePanelElement.focus(); }); xtermHelper.insertBefore(focusTrap, this._xterm.textarea); @@ -239,7 +240,7 @@ export class TerminalInstance implements ITerminalInstance { if (!this._xterm) { return; } - let text = window.getSelection().toString(); + const text = window.getSelection().toString(); if (!text || force) { this._xterm.focus(); } @@ -330,11 +331,11 @@ export class TerminalInstance implements ITerminalInstance { } protected _createProcess(workspace: IWorkspace, name: string, shell: IShell) { - let locale = this._configHelper.isSetLocaleVariables() ? platform.locale : undefined; + const locale = this._configHelper.isSetLocaleVariables() ? platform.locale : undefined; if (!shell.executable) { shell = this._configHelper.getShell(); } - let env = TerminalInstance.createTerminalEnv(process.env, shell, this._getCwd(workspace, shell.ignoreCustomCwd), locale); + const env = TerminalInstance.createTerminalEnv(process.env, shell, this._getCwd(workspace, shell.ignoreCustomCwd), locale); this._title = name ? name : ''; this._process = cp.fork('./terminalProcess', [], { env: env, @@ -371,13 +372,13 @@ export class TerminalInstance implements ITerminalInstance { }); setTimeout(() => { this._isLaunching = false; - }, TerminalInstance.LAUNCHING_DURATION); + }, LAUNCHING_DURATION); } // TODO: This should be private/protected // TODO: locale should not be optional public static createTerminalEnv(parentEnv: IStringDictionary, shell: IShell, cwd: string, locale?: string): IStringDictionary { - let env = TerminalInstance._cloneEnv(parentEnv); + const env = TerminalInstance._cloneEnv(parentEnv); env['PTYPID'] = process.pid.toString(); env['PTYSHELL'] = shell.executable; if (shell.args) { @@ -401,7 +402,7 @@ export class TerminalInstance implements ITerminalInstance { } private static _cloneEnv(env: IStringDictionary): IStringDictionary { - let newEnv: IStringDictionary = Object.create(null); + const newEnv: IStringDictionary = Object.create(null); Object.keys(env).forEach((key) => { newEnv[key] = env[key]; }); @@ -441,7 +442,7 @@ export class TerminalInstance implements ITerminalInstance { } public layout(dimension: { width: number, height: number }): void { - let font = this._configHelper.getFont(); + const font = this._configHelper.getFont(); if (!font || !font.charWidth || !font.charHeight) { return; } @@ -454,10 +455,12 @@ export class TerminalInstance implements ITerminalInstance { // Upstream issue: https://github.com/sourcelair/xterm.js/issues/291 this._xterm.emit('scroll', this._xterm.ydisp); } - let leftPadding = parseInt(getComputedStyle(document.querySelector('.terminal-outer-container')).paddingLeft.split('px')[0], 10); - let innerWidth = dimension.width - leftPadding; - let cols = Math.floor(innerWidth / font.charWidth); - let rows = Math.floor(dimension.height / font.charHeight); + const padding = parseInt(getComputedStyle(document.querySelector('.terminal-outer-container')).paddingLeft.split('px')[0], 10); + // Use left padding as right padding, right padding is not defined in CSS just in case + // xterm.js causes an unexpected overflow. + const innerWidth = dimension.width - padding * 2; + const cols = Math.floor(innerWidth / font.charWidth); + const rows = Math.floor(dimension.height / font.charHeight); if (this._xterm) { this._xterm.resize(cols, rows); this._xterm.element.style.width = innerWidth + 'px'; diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index 9da334462284..d622ec34313a 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -562,7 +562,7 @@ export abstract class TextFileService implements ITextFileService { private doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI): TPromise { // create the target file empty if it does not exist already - return this.fileService.resolveFile(target).then(stat => stat, () => null).then(stat => stat || this.fileService.createFile(target)).then(stat => { + return this.fileService.resolveFile(target).then(stat => stat, () => null).then(stat => stat || this.fileService.updateContent(target, '')).then(stat => { // resolve a model for the file (which can be binary if the file is not a text file) return this.models.loadOrCreate(target).then((targetModel: ITextFileEditorModel) => { diff --git a/src/vs/workbench/services/timer/common/timerService.ts b/src/vs/workbench/services/timer/common/timerService.ts index 8366aa6fe641..19d7c77d7bac 100644 --- a/src/vs/workbench/services/timer/common/timerService.ts +++ b/src/vs/workbench/services/timer/common/timerService.ts @@ -19,6 +19,7 @@ export interface IStartupMetrics { version: number; ellapsed: number; timers: { + ellapsedAppReady?: number; ellapsedWindowLoad?: number; ellapsedWindowLoadToRequire: number; ellapsedExtensions: number; @@ -44,6 +45,8 @@ export interface IStartupMetrics { export interface IInitData { start: Date; + appReady: Date; + windowLoad: Date; beforeLoadWorkbenchMain: Date; @@ -69,4 +72,4 @@ export interface ITimerService extends IInitData { restoreEditorsDuration: number; readonly startupMetrics: IStartupMetrics; -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/timer/node/timerService.ts b/src/vs/workbench/services/timer/node/timerService.ts index a0eb9caa6f18..ffec104d14e6 100644 --- a/src/vs/workbench/services/timer/node/timerService.ts +++ b/src/vs/workbench/services/timer/node/timerService.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import * as timer from 'vs/base/common/timer'; import { ITimerService, IStartupMetrics, IInitData, IMemoryInfo } from 'vs/workbench/services/timer/common/timerService'; import * as os from 'os'; @@ -13,21 +12,15 @@ export class TimerService implements ITimerService { public _serviceBrand: any; - public get start(): Date { return this._start; } - private _start: Date; + public readonly start: Date; + public readonly appReady: Date; + public readonly windowLoad: Date; - public get windowLoad(): Date { return this._windowLoad; }; - private _windowLoad: Date; + public readonly beforeLoadWorkbenchMain: Date; + public readonly afterLoadWorkbenchMain: Date; - public get beforeLoadWorkbenchMain(): Date { return this._beforeLoadWorkbenchMain; }; - private _beforeLoadWorkbenchMain: Date; - public get afterLoadWorkbenchMain(): Date { return this._afterLoadWorkbenchMain; }; - private _afterLoadWorkbenchMain: Date; - - public get isInitialStartup(): boolean { return this._isInitialStartup; }; - private _isInitialStartup: boolean; - public get hasAccessibilitySupport(): boolean { return this._hasAccessibilitySupport; }; - private _hasAccessibilitySupport: boolean; + public readonly isInitialStartup: boolean; + public readonly hasAccessibilitySupport: boolean; public beforeDOMContentLoaded: Date; public afterDOMContentLoaded: Date; @@ -51,18 +44,15 @@ export class TimerService implements ITimerService { private _startupMetrics: IStartupMetrics; constructor(initData: IInitData, private isEmptyWorkbench: boolean) { - this._start = initData.start; + this.start = initData.start; + this.appReady = initData.appReady; + this.windowLoad = initData.windowLoad; - this._windowLoad = initData.windowLoad; + this.beforeLoadWorkbenchMain = initData.beforeLoadWorkbenchMain; + this.afterLoadWorkbenchMain = initData.afterLoadWorkbenchMain; - this._beforeLoadWorkbenchMain = initData.beforeLoadWorkbenchMain; - this._afterLoadWorkbenchMain = initData.afterLoadWorkbenchMain; - - this._isInitialStartup = initData.isInitialStartup; - this._hasAccessibilitySupport = initData.hasAccessibilitySupport; - - // forward start time to time keeper - timer.TimeKeeper.PARSE_TIME = initData.isInitialStartup ? initData.start : initData.windowLoad; + this.isInitialStartup = initData.isInitialStartup; + this.hasAccessibilitySupport = initData.hasAccessibilitySupport; } public computeStartupMetrics(): void { @@ -120,7 +110,8 @@ export class TimerService implements ITimerService { }; if (initialStartup) { - this._startupMetrics.timers.ellapsedWindowLoad = Math.round(this.windowLoad.getTime() - this.start.getTime()); + this._startupMetrics.timers.ellapsedAppReady = Math.round(this.appReady.getTime() - this.start.getTime()); + this._startupMetrics.timers.ellapsedWindowLoad = Math.round(this.windowLoad.getTime() - this.appReady.getTime()); } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/test/electron-browser/quickopen.perf.test.ts b/src/vs/workbench/test/electron-browser/quickopen.perf.test.ts index 52845c580bb5..1d325306e0a7 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.test.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.test.ts @@ -22,7 +22,6 @@ import { SearchService } from 'vs/workbench/services/search/node/searchService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { TestEnvironmentService, TestEditorService, TestEditorGroupService } from 'vs/workbench/test/workbenchTestServices'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import * as Timer from 'vs/base/common/timer'; import { TPromise } from 'vs/base/common/winjs.base'; import URI from 'vs/base/common/uri'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; @@ -31,6 +30,23 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; + +namespace Timer { + export interface ITimerEvent { + id: number; + topic: string; + name: string; + description: string; + data: any; + + startTime: Date; + stopTime: Date; + + stop(stopTime?: Date): void; + timeTaken(): number; + } +} + declare var __dirname: string; // Checkout sources to run against: