diff --git a/.travis.yml b/.travis.yml index 02d0fb00dcc..41c1e2c94d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,8 +40,8 @@ install: script: - gulp hygiene --silent - gulp electron --silent - - gulp compile --silent - - gulp optimize-vscode --silent + - gulp compile --silent --max_old_space_size=4096 + - gulp optimize-vscode --silent --max_old_space_size=4096 - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./scripts/test.sh --reporter dot --coverage; else ./scripts/test.sh --reporter dot; fi - ./scripts/test-integration.sh diff --git a/extensions/markdown/syntaxes/markdown.tmLanguage b/extensions/markdown/syntaxes/markdown.tmLanguage index 754d0179fb6..38c37bd25f7 100644 --- a/extensions/markdown/syntaxes/markdown.tmLanguage +++ b/extensions/markdown/syntaxes/markdown.tmLanguage @@ -48,10 +48,6 @@ include - - - - #fenced_code_block_css @@ -126,6 +122,10 @@ include #fenced_code_block_c + + include + #fenced_code_block_cpp + include #fenced_code_block_diff @@ -252,7 +252,7 @@ heading begin - (?:^|\G)(#{1,6})\s*(?=[\S[^#]]) + (?:^|\G)[ ]{0,3}(#{1,6})\s*(?=[\S[^#]]) captures 1 @@ -563,10 +563,7 @@ while - - - - (^|\G)(?!\s*$|#|[ ]{0,3}((([*_][ ]{0,2}\2){2,}[ \t]*$\n?)|([*+-]([ ]{1,3}|\t)))|\s*\[.+?\]:|>) + (^|\G)((?=\s*[-=]{3,}\s*$)|[ ]{4,}(?=\S)) fenced_code_block_css @@ -964,7 +961,6 @@ punctuation.definition.markdown - patterns @@ -1560,7 +1556,6 @@ punctuation.definition.markdown - patterns @@ -1578,6 +1573,57 @@ + fenced_code_block_cpp + + begin + (^|\G)(\s*)([`~]{3,})\s*((cpp|c\+\+|cxx)(\s+.*)?$) + name + markup.fenced_code.block.markdown + end + (^|\G)(\2|\s{0,3})(\3)\s*$ + beginCaptures + + 3 + + name + punctuation.definition.markdown + + 5 + + name + fenced_code.block.language + + 6 + + name + fenced_code.block.language.attributes + + + endCaptures + + 3 + + name + punctuation.definition.markdown + + + patterns + + + begin + (^|\G)(\s*)(.*) + while + (^|\G)(?!\s*([`~]{3,})\s*$) + patterns + + + include + source.cpp + + + + + fenced_code_block_diff begin diff --git a/extensions/typescript/src/features/completionItemProvider.ts b/extensions/typescript/src/features/completionItemProvider.ts index 0f1c282d850..499e0eff94f 100644 --- a/extensions/typescript/src/features/completionItemProvider.ts +++ b/extensions/typescript/src/features/completionItemProvider.ts @@ -198,7 +198,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP const preText = document.getText(new Range( new Position(position.line, 0), new Position(position.line, position.character - 1))); - enableDotCompletions = preText.match(/[a-z_$]\s*$/ig) !== null; + enableDotCompletions = preText.match(/[a-z_$\)\]\}]\s*$/ig) !== null; } for (let i = 0; i < body.length; i++) { diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 8d541e33c20..3f293d99d29 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -333,9 +333,9 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" }, "node-pty": { - "version": "0.4.1", - "from": "node-pty@0.4.1", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-0.4.1.tgz", + "version": "0.6.0-beta2", + "from": "node-pty@0.6.0-beta2", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-0.6.0-beta2.tgz", "dependencies": { "extend": { "version": "1.2.1", @@ -432,7 +432,7 @@ "xterm": { "version": "2.2.3", "from": "git+https://github.com/Tyriar/xterm.js.git#vscode-release/1.9", - "resolved": "git+https://github.com/Tyriar/xterm.js.git#36c63323c3f940636e799ae6e0168b2dfd7a3d21" + "resolved": "git+https://github.com/Tyriar/xterm.js.git#745a40e15a2a6387df56340625e3c504e84826cf" }, "yauzl": { "version": "2.3.1", diff --git a/package.json b/package.json index 3b4dca5b90f..58b7ea64d49 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "iconv-lite": "0.4.15", "minimist": "1.2.0", "native-keymap": "0.3.0", - "node-pty": "0.4.1", + "node-pty": "0.6.0-beta2", "semver": "4.3.6", "vscode-debugprotocol": "1.15.0", "vscode-textmate": "3.1.0", diff --git a/resources/win32/bin/code.sh b/resources/win32/bin/code.sh index 1281a1f8c38..251d718cfee 100644 --- a/resources/win32/bin/code.sh +++ b/resources/win32/bin/code.sh @@ -6,6 +6,14 @@ NAME="@@NAME@@" VSCODE_PATH="$(dirname "$(dirname "$(realpath "$0")")")" ELECTRON="$VSCODE_PATH/$NAME.exe" +if grep -q Microsoft /proc/version; then + # If running under WSL don't pass cli.js to Electron as environment vars + # cannot be transferred from WSL to Windows + # See: https://github.com/Microsoft/BashOnWindows/issues/1363 + # https://github.com/Microsoft/BashOnWindows/issues/1494 + "$ELECTRON" "$@" + exit $? +fi if [ "$(expr substr $(uname -s) 1 9)" == "CYGWIN_NT" ]; then CLI=$(cygpath -m "$VSCODE_PATH/resources/app/out/cli.js") else diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index 9a1e56d073b..b9b8d37bb71 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import types = require('vs/base/common/types'); import * as Platform from 'vs/base/common/platform'; import Event, { Emitter } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -129,37 +128,9 @@ export const canUseTranslate3d = !isFirefox; export const enableEmptySelectionClipboard = isWebKit; -/** - * Returns if the browser supports CSS 3 animations. - */ -export function hasCSSAnimationSupport() { - if (this._hasCSSAnimationSupport === true || this._hasCSSAnimationSupport === false) { - return this._hasCSSAnimationSupport; - } - - let supported = false; - let element = document.createElement('div'); - let properties = ['animationName', 'webkitAnimationName', 'msAnimationName', 'MozAnimationName', 'OAnimationName']; - for (let i = 0; i < properties.length; i++) { - let property = properties[i]; - if (!types.isUndefinedOrNull(element.style[property]) || element.style.hasOwnProperty(property)) { - supported = true; - break; - } - } - - if (supported) { - this._hasCSSAnimationSupport = true; - } else { - this._hasCSSAnimationSupport = false; - } - - return this._hasCSSAnimationSupport; -} - export function supportsExecCommand(command: string): boolean { return ( (isIE || Platform.isNative) && document.queryCommandSupported(command) ); -} \ No newline at end of file +} diff --git a/src/vs/base/browser/builder.css b/src/vs/base/browser/builder.css index 1c9feba33d7..9d428896b67 100644 --- a/src/vs/base/browser/builder.css +++ b/src/vs/base/browser/builder.css @@ -2,8 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* ---------- Builder ---------- */ -.hidden { + +.builder-hidden { display: none !important; visibility: hidden !important; } diff --git a/src/vs/base/browser/builder.ts b/src/vs/base/browser/builder.ts index 4f586a24a2e..0f6307e1d87 100644 --- a/src/vs/base/browser/builder.ts +++ b/src/vs/base/browser/builder.ts @@ -1280,8 +1280,8 @@ export class Builder implements IDisposable { * Shows the current element of the builder. */ public show(): Builder { - if (this.hasClass('hidden')) { - this.removeClass('hidden'); + if (this.hasClass('builder-hidden')) { + this.removeClass('builder-hidden'); } this.attr('aria-hidden', 'false'); @@ -1319,8 +1319,8 @@ export class Builder implements IDisposable { * Hides the current element of the builder. */ public hide(): Builder { - if (!this.hasClass('hidden')) { - this.addClass('hidden'); + if (!this.hasClass('builder-hidden')) { + this.addClass('builder-hidden'); } this.attr('aria-hidden', 'true'); @@ -1334,7 +1334,7 @@ export class Builder implements IDisposable { * Returns true if the current element of the builder is hidden. */ public isHidden(): boolean { - return this.hasClass('hidden') || this.currentElement.style.display === 'none'; + return this.hasClass('builder-hidden') || this.currentElement.style.display === 'none'; } /** @@ -1345,7 +1345,7 @@ export class Builder implements IDisposable { // Cancel any pending showDelayed() invocation this.cancelVisibilityPromise(); - this.swapClass('builder-visible', 'hidden'); + this.swapClass('builder-visible', 'builder-hidden'); if (this.isHidden()) { this.attr('aria-hidden', 'true'); diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 567e9d85db4..9ac2e1f1dfa 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -235,21 +235,7 @@ export let addStandardDisposableListener: IAddStandardDisposableListenerSignatur wrapHandler = _wrapAsStandardKeyboardEvent(handler); } - node.addEventListener(type, wrapHandler, useCapture || false); - return { - dispose: function () { - if (!wrapHandler) { - // Already removed - return; - } - node.removeEventListener(type, wrapHandler, useCapture || false); - - // Prevent leakers from holding on to the dom node or handler func - wrapHandler = null; - node = null; - handler = null; - } - }; + return addDisposableListener(node, type, wrapHandler, useCapture); }; export function addDisposableNonBubblingMouseOutListener(node: Element, handler: (event: MouseEvent) => void): IDisposable { diff --git a/src/vs/base/browser/ui/progressbar/progressbar.ts b/src/vs/base/browser/ui/progressbar/progressbar.ts index b110122c37d..70fb8421bc6 100644 --- a/src/vs/base/browser/ui/progressbar/progressbar.ts +++ b/src/vs/base/browser/ui/progressbar/progressbar.ts @@ -8,10 +8,8 @@ import 'vs/css!./progressbar'; import { TPromise, ValueCallback } from 'vs/base/common/winjs.base'; import assert = require('vs/base/common/assert'); -import browser = require('vs/base/browser/browser'); import { Builder, $ } from 'vs/base/browser/builder'; import DOM = require('vs/base/browser/dom'); -import uuid = require('vs/base/common/uuid'); import { IDisposable, dispose } from 'vs/base/common/lifecycle'; const css_done = 'done'; @@ -33,7 +31,6 @@ export class ProgressBar { private bit: HTMLElement; private totalWork: number; private animationStopToken: ValueCallback; - private currentProgressToken: string; constructor(builder: Builder) { this.toUnbind = []; @@ -130,55 +127,9 @@ export class ProgressBar { this.element.addClass(css_active); this.element.addClass(css_infinite); - if (!browser.hasCSSAnimationSupport()) { - - // Use a generated token to avoid race conditions from reentrant calls to this function - let currentProgressToken = uuid.v4().asHex(); - this.currentProgressToken = currentProgressToken; - - this.manualInfinite(currentProgressToken); - } - return this; } - private manualInfinite(currentProgressToken: string): void { - this.bit.style.width = '5%'; - this.bit.style.display = 'inherit'; - - let counter = 0; - let animationFn: () => void = () => { - TPromise.timeout(50).then(() => { - - // Return if another manualInfinite() call was made - if (currentProgressToken !== this.currentProgressToken) { - return; - } - - // Animation done - else if (this.element.hasClass(css_done)) { - this.bit.style.display = 'none'; - this.bit.style.left = '0'; - } - - // Wait until progress bar becomes visible - else if (this.element.isHidden()) { - animationFn(); - } - - // Continue Animation until done - else { - counter = (counter + 1) % 95; - this.bit.style.left = counter + '%'; - animationFn(); - } - }); - }; - - // Start Animation - animationFn(); - } - /** * Tells the progress bar the total number of work. Use in combination with workedVal() to let * the progress bar show the actual progress based on the work that is done. diff --git a/src/vs/base/common/comparers.ts b/src/vs/base/common/comparers.ts index 7a77f168718..ea4209ebee7 100644 --- a/src/vs/base/common/comparers.ts +++ b/src/vs/base/common/comparers.ts @@ -7,27 +7,10 @@ import scorer = require('vs/base/common/scorer'); import strings = require('vs/base/common/strings'); -const FileNameMatch = /^([^.]*)(\.(.*))?$/; +const FileNameComparer = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); export function compareFileNames(one: string, other: string): number { - let oneMatch = FileNameMatch.exec(one.toLowerCase()); - let otherMatch = FileNameMatch.exec(other.toLowerCase()); - - let oneName = oneMatch[1] || ''; - let oneExtension = oneMatch[3] || ''; - - let otherName = otherMatch[1] || ''; - let otherExtension = otherMatch[3] || ''; - - if (oneName !== otherName) { - return oneName < otherName ? -1 : 1; - } - - if (oneExtension === otherExtension) { - return 0; - } - - return oneExtension < otherExtension ? -1 : 1; + return FileNameComparer.compare(one || '', other || ''); } export function compareAnything(one: string, other: string, lookFor: string): number { diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index 04858fafbf3..781725e6e0a 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -7,8 +7,8 @@ import URI from 'vs/base/common/uri'; import platform = require('vs/base/common/platform'); import types = require('vs/base/common/types'); -import strings = require('vs/base/common/strings'); -import paths = require('vs/base/common/paths'); +import { nativeSep, isEqualOrParent, normalize } from 'vs/base/common/paths'; +import { endsWith, ltrim } from 'vs/base/common/strings'; export interface ILabelProvider { @@ -44,19 +44,19 @@ export function getPathLabel(resource: URI | string, basePathProvider?: URI | st const basepath = basePathProvider && getPath(basePathProvider); - if (basepath && paths.isEqualOrParent(absolutePath, basepath)) { + if (basepath && isEqualOrParent(absolutePath, basepath)) { if (basepath === absolutePath) { return ''; // no label if pathes are identical } - return paths.normalize(strings.ltrim(absolutePath.substr(basepath.length), paths.nativeSep), true); + return normalize(ltrim(absolutePath.substr(basepath.length), nativeSep), true); } if (platform.isWindows && absolutePath && absolutePath[1] === ':') { - return paths.normalize(absolutePath.charAt(0).toUpperCase() + absolutePath.slice(1), true); // convert c:\something => C:\something + return normalize(absolutePath.charAt(0).toUpperCase() + absolutePath.slice(1), true); // convert c:\something => C:\something } - return paths.normalize(absolutePath, true); + return normalize(absolutePath, true); } function getPath(arg1: URI | string | IWorkspaceProvider): string { @@ -74,4 +74,61 @@ function getPath(arg1: URI | string | IWorkspaceProvider): string { } return (arg1).fsPath; +} + +/** + * Shortens the paths but keeps them easy to distinguish. + * Replaces not important parts with ellipsis. + * Every shorten path matches only one original path and vice versa. + */ +export function shorten(paths: string[]): string[] { + const ellipsis = '\u2026'; + const shortenedPaths: string[] = new Array(paths.length); + + // for every path + let match = false; + for (let pathIndex = 0; pathIndex < paths.length; pathIndex++) { + const segments: string[] = paths[pathIndex].split(nativeSep); + match = true; + + // pick the first shortest subpath found + for (let subpathLength = 1; match && subpathLength <= segments.length; subpathLength++) { + for (let start = segments.length - subpathLength; match && start >= 0; start--) { + match = false; + const subpath = segments.slice(start, start + subpathLength).join(nativeSep); + + // that is unique to any other path + for (let otherPathIndex = 0; !match && otherPathIndex < paths.length; otherPathIndex++) { + + // suffix subpath treated specially as we consider no match 'x' and 'x/...' + if (otherPathIndex !== pathIndex && paths[otherPathIndex].indexOf(subpath) > -1) { + const isSubpathEnding: boolean = (start + subpathLength === segments.length); + const isOtherPathEnding: boolean = endsWith(paths[otherPathIndex], subpath); + + match = !isSubpathEnding || isOtherPathEnding; + } + } + + // found unique subpath + if (!match) { + let result = subpath; + if (start + subpathLength < segments.length) { + result = result + nativeSep + ellipsis; + } + + if (start > 0) { + result = ellipsis + nativeSep + result; + } + + shortenedPaths[pathIndex] = result; + } + } + } + + if (match) { + shortenedPaths[pathIndex] = paths[pathIndex]; // use full path if no unique subpaths found + } + } + + return shortenedPaths; } \ No newline at end of file diff --git a/src/vs/base/common/paths.ts b/src/vs/base/common/paths.ts index 352ae28a8ec..11e39ca9fc1 100644 --- a/src/vs/base/common/paths.ts +++ b/src/vs/base/common/paths.ts @@ -393,4 +393,4 @@ export const isAbsoluteRegex = /^((\/|[a-zA-Z]:\\)[^\(\)<>\\'\"\[\]]+)/; */ export function isAbsolute(path: string): boolean { return isAbsoluteRegex.test(path); -} +} \ No newline at end of file diff --git a/src/vs/base/test/browser/browser.test.ts b/src/vs/base/test/browser/browser.test.ts index 6992e882bf4..e0ce8cd3114 100644 --- a/src/vs/base/test/browser/browser.test.ts +++ b/src/vs/base/test/browser/browser.test.ts @@ -6,43 +6,9 @@ import * as assert from 'assert'; import { isWindows, isMacintosh } from 'vs/base/common/platform'; -import * as browser from 'vs/base/browser/browser'; suite('Browsers', () => { test('all', function () { assert(!(isWindows && isMacintosh)); - - let isOpera = browser.isOpera || navigator.userAgent.indexOf('OPR') >= 0; - let isIE = browser.isIE; - let isFirefox = browser.isFirefox; - let isWebKit = browser.isWebKit; - let isChrome = browser.isChrome; - let isSafari = browser.isSafari; - - let hasCSSAnimations = browser.hasCSSAnimationSupport(); - - let browserCount = 0; - if (isOpera) { - browserCount++; - } - if (isIE) { - browserCount++; - } - if (isFirefox) { - browserCount++; - assert(hasCSSAnimations); - } - if (isWebKit) { - browserCount++; - assert(hasCSSAnimations); - } - if (isChrome) { - browserCount++; - assert(hasCSSAnimations); - } - if (isSafari) { - browserCount++; - assert(hasCSSAnimations); - } }); }); diff --git a/src/vs/base/test/browser/builder.test.ts b/src/vs/base/test/browser/builder.test.ts index 6e122e08f4b..99c3f4ec4ee 100644 --- a/src/vs/base/test/browser/builder.test.ts +++ b/src/vs/base/test/browser/builder.test.ts @@ -792,14 +792,14 @@ suite('Builder', () => { b.div(); b.show(); - assert(!b.hasClass('hidden')); + assert(!b.hasClass('builder-hidden')); assert(!b.isHidden()); b.toggleVisibility(); assert(!b.isHidden()); assert(b.hasClass('builder-visible')); b.toggleVisibility(); b.hide(); - assert(b.hasClass('hidden')); + assert(b.hasClass('builder-hidden')); assert(b.isHidden()); }); @@ -808,10 +808,10 @@ suite('Builder', () => { b.div().hide(); b.showDelayed(20); - assert(b.hasClass('hidden')); + assert(b.hasClass('builder-hidden')); TPromise.timeout(30).then(() => { - assert(!b.hasClass('hidden')); + assert(!b.hasClass('builder-hidden')); done(); }); }); @@ -821,12 +821,12 @@ suite('Builder', () => { b.div().hide(); b.showDelayed(20); - assert(b.hasClass('hidden')); + assert(b.hasClass('builder-hidden')); b.hide(); // Should cancel the visibility promise TPromise.timeout(30).then(() => { - assert(b.hasClass('hidden')); + assert(b.hasClass('builder-hidden')); done(); }); }); diff --git a/src/vs/base/test/common/comparers.test.ts b/src/vs/base/test/common/comparers.test.ts index b1b475303e0..2f7b645a405 100644 --- a/src/vs/base/test/common/comparers.test.ts +++ b/src/vs/base/test/common/comparers.test.ts @@ -12,10 +12,16 @@ suite('Comparers', () => { test('compareFileNames', () => { + assert(compareFileNames(null, null) === 0, 'null should be equal'); + assert(compareFileNames(null, 'abc') < 0, 'null should be come before real values'); assert(compareFileNames('', '') === 0, 'empty should be equal'); assert(compareFileNames('abc', 'abc') === 0, 'equal names should be equal'); assert(compareFileNames('.abc', '.abc') === 0, 'equal full names should be equal'); - assert(compareFileNames('.env', '.env.example') < 0); - assert(compareFileNames('.env.example', '.gitattributes') < 0); + assert(compareFileNames('.env', '.env.example') < 0, 'filenames with extensions should come after those without'); + assert(compareFileNames('.env.example', '.gitattributes') < 0, 'filenames starting with dots and with extensions should still sort properly'); + assert(compareFileNames('1', '1') === 0, 'numerically equal full names should be equal'); + assert(compareFileNames('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal'); + assert(compareFileNames('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order'); + assert(compareFileNames('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long'); }); }); diff --git a/src/vs/base/test/common/labels.test.ts b/src/vs/base/test/common/labels.test.ts new file mode 100644 index 00000000000..ad3a39beb78 --- /dev/null +++ b/src/vs/base/test/common/labels.test.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as assert from 'assert'; +// import labels = require('vs/base/common/labels'); + +suite('Labels', () => { + test('shorten', () => { + assert.ok(true); + + // nothing to shorten + // assert.deepEqual(labels.shorten(['a']), ['a']); + // assert.deepEqual(labels.shorten(['a', 'b']), ['a', 'b']); + // assert.deepEqual(labels.shorten(['a', 'b', 'c']), ['a', 'b', 'c']); + + // // completely different paths + // assert.deepEqual(labels.shorten(['a\\b', 'c\\d', 'e\\f']), ['…\\b', '…\\d', '…\\f']); + + // // same beginning + // assert.deepEqual(labels.shorten(['a', 'a\\b']), ['a', '…\\b']); + // assert.deepEqual(labels.shorten(['a\\b', 'a\\b\\c']), ['…\\b', '…\\c']); + // assert.deepEqual(labels.shorten(['a', 'a\\b', 'a\\b\\c']), ['a', '…\\b', '…\\c']); + // assert.deepEqual(labels.shorten(['x:\\a\\b', 'x:\\a\\c']), ['…\\b', '…\\c'], 'TODO: drive letter (or schema) should be preserved'); + // assert.deepEqual(labels.shorten(['\\\\a\\b', '\\\\a\\c']), ['…\\b', '…\\c'], 'TODO: root uri should be preserved'); + + // // same ending + // assert.deepEqual(labels.shorten(['a', 'b\\a']), ['a', 'b\\…']); + // assert.deepEqual(labels.shorten(['a\\b\\c', 'd\\b\\c']), ['a\\…', 'd\\…']); + // assert.deepEqual(labels.shorten(['a\\b\\c\\d', 'f\\b\\c\\d']), ['a\\…', 'f\\…']); + // assert.deepEqual(labels.shorten(['d\\e\\a\\b\\c', 'd\\b\\c']), ['…\\a\\…', 'd\\b\\…']); + // assert.deepEqual(labels.shorten(['a\\b\\c\\d', 'a\\f\\b\\c\\d']), ['a\\b\\…', '…\\f\\…']); + // assert.deepEqual(labels.shorten(['a\\b\\a', 'b\\b\\a']), ['a\\b\\…', 'b\\b\\…']); + // assert.deepEqual(labels.shorten(['d\\f\\a\\b\\c', 'h\\d\\b\\c']), ['…\\a\\…', 'h\\…']); + // assert.deepEqual(labels.shorten(['a\\b\\c', 'x:\\0\\a\\b\\c']), ['a\\b\\c', '…\\0\\…'], 'TODO: drive letter (or schema) should be always preserved'); + // assert.deepEqual(labels.shorten(['x:\\a\\b', 'y:\\a\\b']), ['x:\\…', 'y:\\…']); + // assert.deepEqual(labels.shorten(['\\\\x\\b', '\\\\y\\b']), ['…\\x\\…', '…\\y\\…'], 'TODO: \\\\x instead of …\\x'); + + // // same in the middle + // assert.deepEqual(labels.shorten(['a\\b\\c', 'd\\b\\e']), ['…\\c', '…\\e']); + + // // case-sensetive + // assert.deepEqual(labels.shorten(['a\\b\\c', 'd\\b\\C']), ['…\\c', '…\\C']); + + // assert.deepEqual(labels.shorten(['a', 'a\\b', 'a\\b\\c', 'd\\b\\c', 'd\\b']), ['a', 'a\\b', 'a\\b\\c', 'd\\b\\c', 'd\\b']); + // assert.deepEqual(labels.shorten(['a', 'a\\b', 'b']), ['a', 'a\\b', 'b']); + // assert.deepEqual(labels.shorten(['', 'a', 'b', 'b\\c', 'a\\c']), ['', 'a', 'b', 'b\\c', 'a\\c']); + // assert.deepEqual(labels.shorten(['src\\vs\\workbench\\parts\\execution\\electron-browser', 'src\\vs\\workbench\\parts\\execution\\electron-browser\\something', 'src\\vs\\workbench\\parts\\terminal\\electron-browser']), ['…\\execution\\electron-browser', '…\\something', '…\\terminal\\…']); + }); +}); \ No newline at end of file diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index ef6a310c847..de09965d895 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -48,7 +48,8 @@ import { RequestService } from 'vs/platform/request/node/requestService'; import { IURLService } from 'vs/platform/url/common/url'; import { URLChannel } from 'vs/platform/url/common/urlIpc'; import { URLService } from 'vs/platform/url/electron-main/urlService'; -import { ITelemetryService, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc'; import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 612ff3f0393..edb452cbc23 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -20,7 +20,8 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ import { IExtensionManagementService, IExtensionGalleryService, IExtensionManifest, IGalleryExtension, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; -import { ITelemetryService, combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; import { IRequestService } from 'vs/platform/request/node/request'; diff --git a/src/vs/code/node/sharedProcessMain.ts b/src/vs/code/node/sharedProcessMain.ts index a8a2ecd6dc6..d6ca3fa783c 100644 --- a/src/vs/code/node/sharedProcessMain.ts +++ b/src/vs/code/node/sharedProcessMain.ts @@ -22,7 +22,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; import { IRequestService } from 'vs/platform/request/node/request'; import { RequestService } from 'vs/platform/request/node/requestService'; -import { ITelemetryService, combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc'; import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; diff --git a/src/vs/editor/browser/controller/keyboardHandler.ts b/src/vs/editor/browser/controller/keyboardHandler.ts index 070e69cb887..89ceea1c90a 100644 --- a/src/vs/editor/browser/controller/keyboardHandler.ts +++ b/src/vs/editor/browser/controller/keyboardHandler.ts @@ -157,7 +157,7 @@ export class KeyboardHandler extends ViewEventHandler implements IDisposable { } public focusTextArea(): void { - this.textAreaHandler.writePlaceholderAndSelectTextAreaSync(); + this.textAreaHandler.focusTextArea(); } public onConfigurationChanged(e: editorCommon.IConfigurationChangedEvent): boolean { diff --git a/src/vs/editor/browser/editor.all.ts b/src/vs/editor/browser/editor.all.ts index 18a65b277a0..c8a31237358 100644 --- a/src/vs/editor/browser/editor.all.ts +++ b/src/vs/editor/browser/editor.all.ts @@ -35,7 +35,6 @@ import 'vs/editor/contrib/rename/browser/rename'; import 'vs/editor/contrib/smartSelect/common/smartSelect'; import 'vs/editor/contrib/snippet/common/snippet'; import 'vs/editor/contrib/snippet/browser/snippet'; -import 'vs/editor/contrib/suggest/common/snippetCompletion'; import 'vs/editor/contrib/suggest/browser/suggestController'; import 'vs/editor/contrib/suggest/browser/tabCompletion'; import 'vs/editor/contrib/toggleTabFocusMode/common/toggleTabFocusMode'; diff --git a/src/vs/editor/browser/standalone/colorizer.ts b/src/vs/editor/browser/standalone/colorizer.ts index a185df8f084..cc370e16139 100644 --- a/src/vs/editor/browser/standalone/colorizer.ts +++ b/src/vs/editor/browser/standalone/colorizer.ts @@ -9,9 +9,8 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IModel } from 'vs/editor/common/editorCommon'; import { TokenizationRegistry, ITokenizationSupport } from 'vs/editor/common/modes'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { renderLine, RenderLineInput } from 'vs/editor/common/viewLayout/viewLineRenderer'; +import { renderViewLine, RenderLineInput } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; -import { LineParts } from 'vs/editor/common/core/lineParts'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; import * as strings from 'vs/base/common/strings'; import { IStandaloneColorService } from 'vs/editor/common/services/standaloneColorService'; @@ -96,14 +95,16 @@ export class Colorizer { } public static colorizeLine(line: string, tokens: ViewLineToken[], tabSize: number = 4): string { - let renderResult = renderLine(new RenderLineInput( + let renderResult = renderViewLine(new RenderLineInput( line, + 0, + tokens, + [], tabSize, 0, -1, 'none', - false, - new LineParts(tokens, line.length + 1) + false )); return renderResult.output; } @@ -126,14 +127,16 @@ function _fakeColorize(lines: string[], tabSize: number): string { for (let i = 0, length = lines.length; i < length; i++) { let line = lines[i]; - let renderResult = renderLine(new RenderLineInput( + let renderResult = renderViewLine(new RenderLineInput( line, + 0, + [new ViewLineToken(line.length, '')], + [], tabSize, 0, -1, 'none', - false, - new LineParts([], line.length + 1) + false )); html = html.concat(renderResult.output); @@ -152,14 +155,16 @@ function _actualColorize(lines: string[], tabSize: number, tokenizationSupport: let line = lines[i]; let tokenizeResult = tokenizationSupport.tokenize2(line, state, 0); let lineTokens = new LineTokens(colorMap, tokenizeResult.tokens, line); - let renderResult = renderLine(new RenderLineInput( + let renderResult = renderViewLine(new RenderLineInput( line, + 0, + lineTokens.inflate(), + [], tabSize, 0, -1, 'none', - false, - new LineParts(lineTokens.inflate(), line.length + 1) + false )); html = html.concat(renderResult.output); diff --git a/src/vs/editor/browser/standalone/simpleServices.ts b/src/vs/editor/browser/standalone/simpleServices.ts index 7215702e5a6..2e43cc59605 100644 --- a/src/vs/editor/browser/standalone/simpleServices.ts +++ b/src/vs/editor/browser/standalone/simpleServices.ts @@ -10,9 +10,7 @@ import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { IConfigurationService, IConfigurationServiceEvent, IConfigurationValue, getConfigurationValue, IConfigurationKeys } from 'vs/platform/configuration/common/configuration'; import { IEditor, IEditorInput, IEditorOptions, IEditorService, IResourceInput, Position } from 'vs/platform/editor/common/editor'; -import { AbstractExtensionService, ActivatedExtension } from 'vs/platform/extensions/common/abstractExtensionService'; -import { IExtensionDescription, IExtensionService } from 'vs/platform/extensions/common/extensions'; -import { ICommandService, ICommand, ICommandHandler } from 'vs/platform/commands/common/commands'; +import { ICommandService, ICommand, ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService'; import { KeybindingResolver, IOSupport } from 'vs/platform/keybinding/common/keybindingResolver'; import { IKeybindingEvent, IKeybindingItem, KeybindingSource } from 'vs/platform/keybinding/common/keybinding'; @@ -23,7 +21,6 @@ import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { Selection } from 'vs/editor/common/core/selection'; import Event, { Emitter } from 'vs/base/common/event'; import { getDefaultValues as getDefaultConfiguration } from 'vs/platform/configuration/common/model'; -import { CommandService } from 'vs/platform/commands/common/commandService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; import { ITextModelResolverService, ITextModelContentProvider, ITextEditorModel } from 'vs/editor/common/services/resolverService'; @@ -31,6 +28,10 @@ import { IDisposable, IReference, ImmortalReference } from 'vs/base/common/lifec import * as dom from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { values } from 'vs/base/common/collections'; +import { MenuId, MenuRegistry, ICommandAction, IMenu, IMenuService } from 'vs/platform/actions/common/actions'; +import { Menu } from 'vs/platform/actions/common/menu'; +import { ITelemetryService, ITelemetryExperiments, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; export class SimpleEditor implements IEditor { @@ -263,16 +264,14 @@ export class SimpleMessageService implements IMessageService { } } -export class StandaloneCommandService extends CommandService { +export class StandaloneCommandService implements ICommandService { + _serviceBrand: any; + private readonly _instantiationService: IInstantiationService; private _dynamicCommands: { [id: string]: ICommand; }; - constructor( - instantiationService: IInstantiationService, - extensionService: IExtensionService - ) { - super(instantiationService, extensionService); - + constructor(instantiationService: IInstantiationService) { + this._instantiationService = instantiationService; this._dynamicCommands = Object.create(null); } @@ -280,8 +279,18 @@ export class StandaloneCommandService extends CommandService { this._dynamicCommands[id] = command; } - protected _getCommand(id: string): ICommand { - return super._getCommand(id) || this._dynamicCommands[id]; + public executeCommand(id: string, ...args: any[]): TPromise { + const command = (CommandsRegistry.getCommand(id) || this._dynamicCommands[id]); + if (!command) { + return TPromise.wrapError(new Error(`command '${id}' not found`)); + } + + try { + const result = this._instantiationService.invokeFunction.apply(this._instantiationService, [command.handler].concat(args)); + return TPromise.as(result); + } catch (err) { + return TPromise.wrapError(err); + } } } @@ -353,38 +362,6 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { } } -export class SimpleExtensionService extends AbstractExtensionService { - - constructor() { - super(true); - } - - protected _showMessage(severity: Severity, msg: string): void { - switch (severity) { - case Severity.Error: - console.error(msg); - break; - case Severity.Warning: - console.warn(msg); - break; - case Severity.Info: - console.info(msg); - break; - default: - console.log(msg); - } - } - - protected _createFailedExtension(): ActivatedExtension { - throw new Error('unexpected'); - } - - protected _actualActivateExtension(extensionDescription: IExtensionDescription): TPromise { - throw new Error('unexpected'); - } - -} - export class SimpleConfigurationService implements IConfigurationService { _serviceBrand: any; @@ -418,3 +395,40 @@ export class SimpleConfigurationService implements IConfigurationService { return { default: [], user: [] }; } } + +export class SimpleMenuService implements IMenuService { + + _serviceBrand: any; + + private readonly _commandService: ICommandService; + + constructor(commandService: ICommandService) { + this._commandService = commandService; + } + + public createMenu(id: MenuId, contextKeyService: IContextKeyService): IMenu { + return new Menu(id, TPromise.as(true), this._commandService, contextKeyService); + } + + public getCommandActions(): ICommandAction[] { + return values(MenuRegistry.commands); + } +} + +export class StandaloneTelemetryService implements ITelemetryService { + _serviceBrand: void; + + public isOptedIn = false; + + public publicLog(eventName: string, data?: any): TPromise { + return TPromise.as(null); + } + + public getTelemetryInfo(): TPromise { + return null; + } + + public getExperiments(): ITelemetryExperiments { + return null; + } +} diff --git a/src/vs/editor/browser/standalone/standaloneLanguages.ts b/src/vs/editor/browser/standalone/standaloneLanguages.ts index 14750f8b094..985eb430033 100644 --- a/src/vs/editor/browser/standalone/standaloneLanguages.ts +++ b/src/vs/editor/browser/standalone/standaloneLanguages.ts @@ -7,7 +7,6 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { onWillActivate } from 'vs/platform/extensions/common/extensionsRegistry'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { IMonarchLanguage } from 'vs/editor/common/modes/monarch/monarchTypes'; import { ILanguageExtensionPoint } from 'vs/editor/common/services/modeService'; @@ -47,9 +46,8 @@ export function getLanguages(): ILanguageExtensionPoint[] { * @event */ export function onLanguage(languageId: string, callback: () => void): IDisposable { - const desired = 'onLanguage:' + languageId; - let disposable = onWillActivate.event((activationEvent) => { - if (activationEvent === desired) { + let disposable = StaticServices.modeService.get().onDidCreateMode((mode) => { + if (mode.getId() === languageId) { // stop listening disposable.dispose(); // invoke actual listener diff --git a/src/vs/editor/browser/standalone/standaloneServices.ts b/src/vs/editor/browser/standalone/standaloneServices.ts index c2430823fce..fbe6b0ea7ba 100644 --- a/src/vs/editor/browser/standalone/standaloneServices.ts +++ b/src/vs/editor/browser/standalone/standaloneServices.ts @@ -10,7 +10,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; -import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { createDecorator, IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -22,23 +21,23 @@ import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IMessageService } from 'vs/platform/message/common/message'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { IStorageService, NullStorageService } from 'vs/platform/storage/common/storage'; -import { ITelemetryService, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { EditorWorkerServiceImpl } from 'vs/editor/common/services/editorWorkerServiceImpl'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { MainThreadModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { CodeEditorServiceImpl } from 'vs/editor/browser/services/codeEditorServiceImpl'; import { - SimpleConfigurationService, SimpleMessageService, SimpleExtensionService, - StandaloneKeybindingService, StandaloneCommandService, SimpleProgressService + SimpleConfigurationService, SimpleMenuService, SimpleMessageService, + SimpleProgressService, StandaloneCommandService, StandaloneKeybindingService, + StandaloneTelemetryService } from 'vs/editor/browser/standalone/simpleServices'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import { IMenuService } from 'vs/platform/actions/common/actions'; -import { MenuService } from 'vs/platform/actions/common/menuService'; import { IStandaloneColorService } from 'vs/editor/common/services/standaloneColorService'; import { StandaloneColorServiceImpl } from 'vs/editor/browser/services/standaloneColorServiceImpl'; @@ -119,19 +118,17 @@ export module StaticServices { resource: URI.from({ scheme: 'inmemory', authority: 'model', path: '/' }) })); - export const telemetryService = define(ITelemetryService, () => NullTelemetryService); + export const telemetryService = define(ITelemetryService, () => new StandaloneTelemetryService()); export const configurationService = define(IConfigurationService, () => new SimpleConfigurationService()); export const messageService = define(IMessageService, () => new SimpleMessageService()); - export const extensionService = define(IExtensionService, () => new SimpleExtensionService()); - export const markerService = define(IMarkerService, () => new MarkerService()); - export const modeService = define(IModeService, (o) => new MainThreadModeServiceImpl(instantiationService.get(o), extensionService.get(o), configurationService.get(o))); + export const modeService = define(IModeService, (o) => new ModeServiceImpl()); - export const modelService = define(IModelService, (o) => new ModelServiceImpl(markerService.get(o), configurationService.get(o), messageService.get(o))); + export const modelService = define(IModelService, (o) => new ModelServiceImpl(markerService.get(o), configurationService.get(o))); export const editorWorkerService = define(IEditorWorkerService, (o) => new EditorWorkerServiceImpl(modelService.get(o), configurationService.get(o))); @@ -157,7 +154,6 @@ export class DynamicStandaloneServices extends Disposable { this._instantiationService = _instantiationService; const configurationService = this.get(IConfigurationService); - const extensionService = this.get(IExtensionService); const messageService = this.get(IMessageService); const telemetryService = this.get(ITelemetryService); @@ -175,7 +171,7 @@ export class DynamicStandaloneServices extends Disposable { let contextKeyService = ensure(IContextKeyService, () => this._register(new ContextKeyService(configurationService))); - let commandService = ensure(ICommandService, () => new StandaloneCommandService(this._instantiationService, extensionService)); + let commandService = ensure(ICommandService, () => new StandaloneCommandService(this._instantiationService)); ensure(IKeybindingService, () => this._register(new StandaloneKeybindingService(contextKeyService, commandService, messageService, domElement))); @@ -183,7 +179,7 @@ export class DynamicStandaloneServices extends Disposable { ensure(IContextMenuService, () => this._register(new ContextMenuService(domElement, telemetryService, messageService, contextViewService))); - ensure(IMenuService, () => new MenuService(extensionService, commandService)); + ensure(IMenuService, () => new SimpleMenuService(commandService)); } public get(serviceId: ServiceIdentifier): T { diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 2766ef2f051..cd57262869d 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -673,7 +673,9 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp this.keyboardHandler.focusTextArea(); // IE does not trigger the focus event immediately, so we must help it a little bit - this._setHasFocus(true); + if (document.activeElement === this.textArea) { + this._setHasFocus(true); + } } public isFocused(): boolean { diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 7f278afa2f0..14bebc00d2a 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -7,8 +7,8 @@ import * as browser from 'vs/base/browser/browser'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/styleMutator'; import { IConfigurationChangedEvent } from 'vs/editor/common/editorCommon'; -import { createLineParts } from 'vs/editor/common/viewLayout/viewLineParts'; -import { renderLine, RenderLineInput, RenderLineOutput, CharacterMapping } from 'vs/editor/common/viewLayout/viewLineRenderer'; +import { Decoration } from 'vs/editor/common/viewLayout/viewLineParts'; +import { renderViewLine, RenderLineInput, RenderLineOutput, CharacterMapping } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { ClassNames } from 'vs/editor/browser/editorBrowser'; import { IVisibleLineData } from 'vs/editor/browser/view/viewLayer'; import { RangeUtil } from 'vs/editor/browser/viewParts/lines/rangeUtil'; @@ -97,24 +97,19 @@ export class ViewLine implements IVisibleLineData { } this._isMaybeInvalid = false; - let newLineParts = createLineParts( - lineNumber, - this._context.model.getLineMinColumn(lineNumber), - this._context.model.getLineContent(lineNumber), - this._context.model.getTabSize(), - this._context.model.getLineTokens(lineNumber), - inlineDecorations, - this._renderWhitespace - ); + const model = this._context.model; + const actualInlineDecorations = Decoration.filter(inlineDecorations, lineNumber, model.getLineMinColumn(lineNumber), model.getLineMaxColumn(lineNumber)); let renderLineInput = new RenderLineInput( - this._context.model.getLineContent(lineNumber), - this._context.model.getTabSize(), + model.getLineContent(lineNumber), + model.getLineMinColumn(lineNumber) - 1, + model.getLineTokens(lineNumber), + actualInlineDecorations, + model.getTabSize(), this._spaceWidth, this._stopRenderingLineAfter, this._renderWhitespace, - this._renderControlCharacters, - newLineParts + this._renderControlCharacters ); if (this._renderedViewLine && this._renderedViewLine.input.equals(renderLineInput)) { @@ -122,11 +117,14 @@ export class ViewLine implements IVisibleLineData { return false; } + let isWhitespaceOnly = /^\s*$/.test(renderLineInput.lineContent); + this._renderedViewLine = createRenderedLine( this._renderedViewLine ? this._renderedViewLine.domNode : null, renderLineInput, this._context.model.mightContainRTL(), - renderLine(renderLineInput) + isWhitespaceOnly, + renderViewLine(renderLineInput) ); return true; } @@ -183,18 +181,18 @@ class RenderedViewLine { */ private _pixelOffsetCache: number[]; - constructor(domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, renderLineOutput: RenderLineOutput) { + constructor(domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, isWhitespaceOnly: boolean, renderLineOutput: RenderLineOutput) { this.domNode = domNode; this.input = renderLineInput; this.html = renderLineOutput.output; this._characterMapping = renderLineOutput.characterMapping; - this._isWhitespaceOnly = renderLineOutput.isWhitespaceOnly; + this._isWhitespaceOnly = isWhitespaceOnly; this._cachedWidth = -1; this._pixelOffsetCache = null; if (!modelContainsRTL) { this._pixelOffsetCache = []; - for (let column = 0, maxLineColumn = this.input.lineParts.maxLineColumn; column <= maxLineColumn; column++) { + for (let column = 0, len = this._characterMapping.length; column <= len; column++) { this._pixelOffsetCache[column] = -1; } } @@ -376,17 +374,17 @@ class WebKitRenderedViewLine extends RenderedViewLine { } } -const createRenderedLine: (domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, renderLineOutput: RenderLineOutput) => RenderedViewLine = (function () { +const createRenderedLine: (domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, isWhitespaceOnly: boolean, renderLineOutput: RenderLineOutput) => RenderedViewLine = (function () { if (browser.isWebKit) { return createWebKitRenderedLine; } return createNormalRenderedLine; })(); -function createWebKitRenderedLine(domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, renderLineOutput: RenderLineOutput): RenderedViewLine { - return new WebKitRenderedViewLine(domNode, renderLineInput, modelContainsRTL, renderLineOutput); +function createWebKitRenderedLine(domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, isWhitespaceOnly: boolean, renderLineOutput: RenderLineOutput): RenderedViewLine { + return new WebKitRenderedViewLine(domNode, renderLineInput, modelContainsRTL, isWhitespaceOnly, renderLineOutput); } -function createNormalRenderedLine(domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, renderLineOutput: RenderLineOutput): RenderedViewLine { - return new RenderedViewLine(domNode, renderLineInput, modelContainsRTL, renderLineOutput); +function createNormalRenderedLine(domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, isWhitespaceOnly: boolean, renderLineOutput: RenderLineOutput): RenderedViewLine { + return new RenderedViewLine(domNode, renderLineInput, modelContainsRTL, isWhitespaceOnly, renderLineOutput); } diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index ea6e7b708a4..8a423b577ab 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -19,11 +19,11 @@ import { DefaultConfig } from 'vs/editor/common/config/defaultConfig'; import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; -import { createLineParts } from 'vs/editor/common/viewLayout/viewLineParts'; -import { renderLine, RenderLineInput } from 'vs/editor/common/viewLayout/viewLineRenderer'; +import { Decoration } from 'vs/editor/common/viewLayout/viewLineParts'; +import { renderViewLine, RenderLineInput } from 'vs/editor/common/viewLayout/viewLineRenderer'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { CodeEditor } from 'vs/editor/browser/codeEditor'; -import { ViewLineToken, ViewLineTokens } from 'vs/editor/common/core/viewLineToken'; +import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; import { Configuration } from 'vs/editor/browser/config/configuration'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; @@ -1885,17 +1885,18 @@ class InlineViewZonesComputer extends ViewZonesComputer { private renderOriginalLine(count: number, originalModel: editorCommon.IModel, config: editorCommon.InternalEditorOptions, tabSize: number, lineNumber: number, decorations: InlineDecoration[]): string[] { let lineContent = originalModel.getLineContent(lineNumber); - let lineTokens = new ViewLineTokens([new ViewLineToken(0, '')], 0, lineContent.length); - let parts = createLineParts(lineNumber, 1, lineContent, tabSize, lineTokens, decorations, config.viewInfo.renderWhitespace); + let actualDecorations = Decoration.filter(decorations, lineNumber, 1, lineContent.length + 1); - let r = renderLine(new RenderLineInput( + let r = renderViewLine(new RenderLineInput( lineContent, + 0, + [new ViewLineToken(lineContent.length, '')], + actualDecorations, tabSize, config.fontInfo.spaceWidth, config.viewInfo.stopRenderingLineAfter, config.viewInfo.renderWhitespace, - config.viewInfo.renderControlCharacters, - parts + config.viewInfo.renderControlCharacters )); let myResult: string[] = []; diff --git a/src/vs/editor/common/controller/textAreaHandler.ts b/src/vs/editor/common/controller/textAreaHandler.ts index 1015da7d3fb..a385e1c96a8 100644 --- a/src/vs/editor/common/controller/textAreaHandler.ts +++ b/src/vs/editor/common/controller/textAreaHandler.ts @@ -118,7 +118,7 @@ export class TextAreaHandler extends Disposable { // In IE we cannot set .value when handling 'compositionstart' because the entire composition will get canceled. if (!this.Browser.isEdgeOrIE) { - this.setTextAreaState('compositionstart', this.textAreaState.toEmpty()); + this.setTextAreaState('compositionstart', this.textAreaState.toEmpty(), false); } this._onCompositionStart.fire({ @@ -200,13 +200,13 @@ export class TextAreaHandler extends Disposable { } else { if (this.textArea.getSelectionStart() !== this.textArea.getSelectionEnd()) { // Clean up the textarea, to get a clean paste - this.setTextAreaState('paste', this.textAreaState.toEmpty()); + this.setTextAreaState('paste', this.textAreaState.toEmpty(), false); } this._nextCommand = ReadFromTextArea.Paste; } })); - this._writePlaceholderAndSelectTextArea('ctor'); + this._writePlaceholderAndSelectTextArea('ctor', false); } public dispose(): void { @@ -227,24 +227,24 @@ export class TextAreaHandler extends Disposable { } this.hasFocus = isFocused; if (this.hasFocus) { - this._writePlaceholderAndSelectTextArea('focusgain'); + this._writePlaceholderAndSelectTextArea('focusgain', false); } } public setCursorSelections(primary: Range, secondary: Range[]): void { this.selection = primary; this.selections = [primary].concat(secondary); - this._writePlaceholderAndSelectTextArea('selection changed'); + this._writePlaceholderAndSelectTextArea('selection changed', false); } // --- end event handlers - private setTextAreaState(reason: string, textAreaState: TextAreaState): void { + private setTextAreaState(reason: string, textAreaState: TextAreaState, forceFocus: boolean): void { if (!this.hasFocus) { textAreaState = textAreaState.resetSelection(); } - textAreaState.applyToTextArea(reason, this.textArea, this.hasFocus); + textAreaState.applyToTextArea(reason, this.textArea, this.hasFocus || forceFocus); this.textAreaState = textAreaState; } @@ -281,18 +281,18 @@ export class TextAreaHandler extends Disposable { }); } - public writePlaceholderAndSelectTextAreaSync(): void { - this._writePlaceholderAndSelectTextArea('focusTextArea'); + public focusTextArea(): void { + this._writePlaceholderAndSelectTextArea('focusTextArea', true); } - private _writePlaceholderAndSelectTextArea(reason: string): void { + private _writePlaceholderAndSelectTextArea(reason: string, forceFocus: boolean): void { if (!this.textareaIsShownAtCursor) { // Do not write to the textarea if it is visible. if (this.Browser.isIPad) { // Do not place anything in the textarea for the iPad - this.setTextAreaState(reason, this.textAreaState.toEmpty()); + this.setTextAreaState(reason, this.textAreaState.toEmpty(), forceFocus); } else { - this.setTextAreaState(reason, this.textAreaState.fromEditorSelection(this.model, this.selection)); + this.setTextAreaState(reason, this.textAreaState.fromEditorSelection(this.model, this.selection), forceFocus); } } } @@ -304,7 +304,7 @@ export class TextAreaHandler extends Disposable { if (e.canUseTextData()) { e.setTextData(whatToCopy); } else { - this.setTextAreaState('copy or cut', this.textAreaState.fromText(whatToCopy)); + this.setTextAreaState('copy or cut', this.textAreaState.fromText(whatToCopy), false); } if (this.Browser.enableEmptySelectionClipboard) { diff --git a/src/vs/editor/common/core/lineParts.ts b/src/vs/editor/common/core/lineParts.ts deleted file mode 100644 index 784049905bc..00000000000 --- a/src/vs/editor/common/core/lineParts.ts +++ /dev/null @@ -1,26 +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 { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; - -export class LineParts { - _linePartsBrand: void; - - public readonly parts: ViewLineToken[]; - public readonly maxLineColumn: number; - - constructor(parts: ViewLineToken[], maxLineColumn: number) { - this.parts = parts; - this.maxLineColumn = maxLineColumn; - } - - public equals(other: LineParts): boolean { - return ( - this.maxLineColumn === other.maxLineColumn - && ViewLineToken.equalsArray(this.parts, other.parts) - ); - } -} diff --git a/src/vs/editor/common/core/lineTokens.ts b/src/vs/editor/common/core/lineTokens.ts index f0030ed7cb1..b1cca283dd1 100644 --- a/src/vs/editor/common/core/lineTokens.ts +++ b/src/vs/editor/common/core/lineTokens.ts @@ -122,6 +122,10 @@ export class LineTokens { return this._text; } + public getLineLength(): number { + return this._textLength; + } + public getTokenStartOffset(tokenIndex: number): number { return this._tokens[(tokenIndex << 1)]; } @@ -190,10 +194,10 @@ export class LineTokens { } public inflate(): ViewLineToken[] { - return TokenMetadata.inflateArr(this._tokens); + return TokenMetadata.inflateArr(this._tokens, this._textLength); } - public sliceAndInflate(startOffset: number, endOffset: number, deltaStartIndex: number): ViewLineToken[] { - return TokenMetadata.sliceAndInflate(this._tokens, startOffset, endOffset, deltaStartIndex); + public sliceAndInflate(startOffset: number, endOffset: number, deltaOffset: number): ViewLineToken[] { + return TokenMetadata.sliceAndInflate(this._tokens, startOffset, endOffset, deltaOffset, this._textLength); } } diff --git a/src/vs/editor/common/core/viewLineToken.ts b/src/vs/editor/common/core/viewLineToken.ts index 661483207df..03643e032b4 100644 --- a/src/vs/editor/common/core/viewLineToken.ts +++ b/src/vs/editor/common/core/viewLineToken.ts @@ -10,66 +10,35 @@ export class ViewLineToken { _viewLineTokenBrand: void; - public readonly startIndex: number; + /** + * last char index of this token (not inclusive). + */ + public readonly endIndex: number; public readonly type: string; - constructor(startIndex: number, type: string) { - this.startIndex = startIndex | 0;// @perf + constructor(endIndex: number, type: string) { + this.endIndex = endIndex; this.type = type; } - public equals(other: ViewLineToken): boolean { + private static _equals(a: ViewLineToken, b: ViewLineToken): boolean { return ( - this.startIndex === other.startIndex - && this.type === other.type + a.endIndex === b.endIndex + && a.type === b.type ); } - public static equalsArray(a: ViewLineToken[], b: ViewLineToken[]): boolean { - let aLen = a.length; - let bLen = b.length; + public static equalsArr(a: ViewLineToken[], b: ViewLineToken[]): boolean { + const aLen = a.length; + const bLen = b.length; if (aLen !== bLen) { return false; } for (let i = 0; i < aLen; i++) { - if (!a[i].equals(b[i])) { + if (!this._equals(a[i], b[i])) { return false; } } return true; } } - -export class ViewLineTokens { - _viewLineTokensBrand: void; - - private _lineTokens: ViewLineToken[]; - private _fauxIndentLength: number; - private _textLength: number; - - constructor(lineTokens: ViewLineToken[], fauxIndentLength: number, textLength: number) { - this._lineTokens = lineTokens; - this._fauxIndentLength = fauxIndentLength | 0; - this._textLength = textLength | 0; - } - - public getTokens(): ViewLineToken[] { - return this._lineTokens; - } - - public getFauxIndentLength(): number { - return this._fauxIndentLength; - } - - public getTextLength(): number { - return this._textLength; - } - - public equals(other: ViewLineTokens): boolean { - return ( - this._fauxIndentLength === other._fauxIndentLength - && this._textLength === other._textLength - && ViewLineToken.equalsArray(this._lineTokens, other._lineTokens) - ); - } -} diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 1bc532e7c83..f8155b33a4a 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -243,10 +243,9 @@ export interface IEditorOptions { */ roundedSelection?: boolean; /** - * Theme to be used for rendering. Consists of two parts, the UI theme and the syntax theme, - * separated by a space. - * The current available UI themes are: 'vs' (default), 'vs-dark', 'hc-black' - * The syntax themes are contributed. The default is 'default-theme' + * Theme to be used for rendering. + * The current out-of-the-box available themes are: 'vs' (default), 'vs-dark', 'hc-black'. + * You can create custom themes via `monaco.editor.defineTheme`. */ theme?: string; /** @@ -1130,9 +1129,8 @@ export interface IModelDecorationOptions { className?: string; /** * Message to be rendered when hovering over the glyph margin decoration. - * @internal */ - glyphMarginHoverMessage?: string; + glyphMarginHoverMessage?: MarkedString | MarkedString[]; /** * Array of MarkedString to render as the decoration message. */ diff --git a/src/vs/editor/common/model/textModelWithDecorations.ts b/src/vs/editor/common/model/textModelWithDecorations.ts index 654172d5344..58d135c4ac7 100644 --- a/src/vs/editor/common/model/textModelWithDecorations.ts +++ b/src/vs/editor/common/model/textModelWithDecorations.ts @@ -834,8 +834,8 @@ export class ModelDecorationOptions implements editorCommon.IModelDecorationOpti stickiness: editorCommon.TrackedRangeStickiness; className: string; - glyphMarginHoverMessage: string; hoverMessage: MarkedString | MarkedString[]; + glyphMarginHoverMessage: MarkedString | MarkedString[]; isWholeLine: boolean; showInOverviewRuler: string; overviewRuler: editorCommon.IModelDecorationOverviewRulerOptions; @@ -849,8 +849,8 @@ export class ModelDecorationOptions implements editorCommon.IModelDecorationOpti constructor(options: editorCommon.IModelDecorationOptions) { this.stickiness = options.stickiness || editorCommon.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges; this.className = options.className ? cleanClassName(options.className) : strings.empty; - this.glyphMarginHoverMessage = options.glyphMarginHoverMessage || strings.empty; this.hoverMessage = options.hoverMessage || []; + this.glyphMarginHoverMessage = options.glyphMarginHoverMessage || strings.empty; this.isWholeLine = options.isWholeLine || false; this.overviewRuler = _normalizeOverviewRulerOptions(options.overviewRuler, options.showInOverviewRuler); this.glyphMarginClassName = options.glyphMarginClassName ? cleanClassName(options.glyphMarginClassName) : strings.empty; @@ -873,7 +873,6 @@ export class ModelDecorationOptions implements editorCommon.IModelDecorationOpti return ( this.stickiness === other.stickiness && this.className === other.className - && this.glyphMarginHoverMessage === other.glyphMarginHoverMessage && this.isWholeLine === other.isWholeLine && this.showInOverviewRuler === other.showInOverviewRuler && this.glyphMarginClassName === other.glyphMarginClassName @@ -883,6 +882,7 @@ export class ModelDecorationOptions implements editorCommon.IModelDecorationOpti && this.beforeContentClassName === other.beforeContentClassName && this.afterContentClassName === other.afterContentClassName && markedStringsEquals(this.hoverMessage, other.hoverMessage) + && markedStringsEquals(this.glyphMarginHoverMessage, other.glyphMarginHoverMessage) && ModelDecorationOptions._overviewRulerEquals(this.overviewRuler, other.overviewRuler) ); } diff --git a/src/vs/editor/common/model/tokensBinaryEncoding.ts b/src/vs/editor/common/model/tokensBinaryEncoding.ts index 18884f1de39..6876e6eb450 100644 --- a/src/vs/editor/common/model/tokensBinaryEncoding.ts +++ b/src/vs/editor/common/model/tokensBinaryEncoding.ts @@ -71,36 +71,35 @@ export class TokenMetadata { return className; } - public static inflateArr(tokens: Uint32Array): ViewLineToken[] { - let tokenCount = (tokens.length >>> 1); + public static inflateArr(tokens: Uint32Array, lineLength: number): ViewLineToken[] { let result: ViewLineToken[] = []; - for (let i = 0; i < tokenCount; i++) { - let startOffset = tokens[(i << 1)]; + for (let i = 0, len = (tokens.length >>> 1); i < len; i++) { + let endOffset = (i + 1 < len ? tokens[((i + 1) << 1)] : lineLength); let metadata = tokens[(i << 1) + 1]; - result[i] = new ViewLineToken(startOffset, this._getClassNameFromMetadata(metadata)); + result[i] = new ViewLineToken(endOffset, this._getClassNameFromMetadata(metadata)); } + return result; } - public static sliceAndInflate(tokens: Uint32Array, startOffset: number, endOffset: number, deltaStartIndex: number): ViewLineToken[] { + public static sliceAndInflate(tokens: Uint32Array, startOffset: number, endOffset: number, deltaOffset: number, lineLength: number): ViewLineToken[] { let tokenIndex = this.findIndexInSegmentsArray(tokens, startOffset); let result: ViewLineToken[] = [], resultLen = 0; - result[resultLen++] = new ViewLineToken(0, this._getClassNameFromMetadata(tokens[(tokenIndex << 1) + 1])); + for (let i = tokenIndex, len = (tokens.length >>> 1); i < len; i++) { + let tokenStartOffset = tokens[(i << 1)]; - for (let i = tokenIndex + 1, len = (tokens.length >>> 1); i < len; i++) { - let originalStartOffset = tokens[(i << 1)]; - - if (originalStartOffset >= endOffset) { + if (tokenStartOffset >= endOffset) { break; } - let newStartOffset = originalStartOffset - startOffset + deltaStartIndex; + let tokenEndOffset = (i + 1 < len ? tokens[((i + 1) << 1)] : lineLength); + let newEndOffset = tokenEndOffset - startOffset + deltaOffset; let metadata = tokens[(i << 1) + 1]; - result[resultLen++] = new ViewLineToken(newStartOffset, this._getClassNameFromMetadata(metadata)); + result[resultLen++] = new ViewLineToken(newEndOffset, this._getClassNameFromMetadata(metadata)); } return result; diff --git a/src/vs/editor/common/modes/textToHtmlTokenizer.ts b/src/vs/editor/common/modes/textToHtmlTokenizer.ts index 19795f0681a..e81acfc9802 100644 --- a/src/vs/editor/common/modes/textToHtmlTokenizer.ts +++ b/src/vs/editor/common/modes/textToHtmlTokenizer.ts @@ -41,16 +41,11 @@ function _tokenizeToString(text: string, tokenizationSupport: ITokenizationSuppo let viewLineTokens = lineTokens.inflate(); let startOffset = 0; - let className = viewLineTokens[0].type; - - for (let j = 1, lenJ = viewLineTokens.length; j < lenJ; j++) { - let viewLineToken = viewLineTokens[j]; - - result += `${strings.escape(line.substring(startOffset, viewLineToken.startIndex))}`; - startOffset = viewLineToken.startIndex; - className = viewLineToken.type; + for (let j = 0, lenJ = viewLineTokens.length; j < lenJ; j++) { + const viewLineToken = viewLineTokens[j]; + result += `${strings.escape(line.substring(startOffset, viewLineToken.endIndex))}`; + startOffset = viewLineToken.endIndex; } - result += `${strings.escape(line.substring(startOffset))}`; currentState = tokenizationResult.endState; } diff --git a/src/vs/editor/common/services/modeServiceImpl.ts b/src/vs/editor/common/services/modeServiceImpl.ts index 924e16c758b..93bfaad7f64 100644 --- a/src/vs/editor/common/services/modeServiceImpl.ts +++ b/src/vs/editor/common/services/modeServiceImpl.ts @@ -7,19 +7,12 @@ import * as nls from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; import Event, { Emitter } from 'vs/base/common/event'; -import * as paths from 'vs/base/common/paths'; import { TPromise } from 'vs/base/common/winjs.base'; -import mime = require('vs/base/common/mime'); -import { IFilesConfiguration } from 'vs/platform/files/common/files'; -import { IExtensionService } from 'vs/platform/extensions/common/extensions'; -import { IExtensionPoint, IExtensionPointUser, ExtensionMessageCollector, ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IExtensionPoint, ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry'; import { IMode, LanguageId, LanguageIdentifier } from 'vs/editor/common/modes'; import { FrankensteinMode } from 'vs/editor/common/modes/abstractMode'; -import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry'; -import { ILanguageExtensionPoint, IValidLanguageExtensionPoint, IModeLookupResult, IModeService } from 'vs/editor/common/services/modeService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILanguageExtensionPoint, IModeLookupResult, IModeService } from 'vs/editor/common/services/modeService'; export const languagesExtPoint: IExtensionPoint = ExtensionsRegistry.registerExtensionPoint('languages', [], { description: nls.localize('vscode.extension.contributes.languages', 'Contributes language declarations.'), @@ -81,58 +74,9 @@ export const languagesExtPoint: IExtensionPoint = Ext } }); -function isUndefinedOrStringArray(value: string[]): boolean { - if (typeof value === 'undefined') { - return true; - } - if (!Array.isArray(value)) { - return false; - } - return value.every(item => typeof item === 'string'); -} - -function isValidLanguageExtensionPoint(value: ILanguageExtensionPoint, collector: ExtensionMessageCollector): boolean { - if (!value) { - collector.error(nls.localize('invalid.empty', "Empty value for `contributes.{0}`", languagesExtPoint.name)); - return false; - } - if (typeof value.id !== 'string') { - collector.error(nls.localize('require.id', "property `{0}` is mandatory and must be of type `string`", 'id')); - return false; - } - if (!isUndefinedOrStringArray(value.extensions)) { - collector.error(nls.localize('opt.extensions', "property `{0}` can be omitted and must be of type `string[]`", 'extensions')); - return false; - } - if (!isUndefinedOrStringArray(value.filenames)) { - collector.error(nls.localize('opt.filenames', "property `{0}` can be omitted and must be of type `string[]`", 'filenames')); - return false; - } - if (typeof value.firstLine !== 'undefined' && typeof value.firstLine !== 'string') { - collector.error(nls.localize('opt.firstLine', "property `{0}` can be omitted and must be of type `string`", 'firstLine')); - return false; - } - if (typeof value.configuration !== 'undefined' && typeof value.configuration !== 'string') { - collector.error(nls.localize('opt.configuration', "property `{0}` can be omitted and must be of type `string`", 'configuration')); - return false; - } - if (!isUndefinedOrStringArray(value.aliases)) { - collector.error(nls.localize('opt.aliases', "property `{0}` can be omitted and must be of type `string[]`", 'aliases')); - return false; - } - if (!isUndefinedOrStringArray(value.mimetypes)) { - collector.error(nls.localize('opt.mimetypes', "property `{0}` can be omitted and must be of type `string[]`", 'mimetypes')); - return false; - } - return true; -} - export class ModeServiceImpl implements IModeService { public _serviceBrand: any; - private _instantiationService: IInstantiationService; - protected _extensionService: IExtensionService; - private _instantiatedModes: { [modeId: string]: IMode; }; private _registry: LanguagesRegistry; @@ -143,19 +87,17 @@ export class ModeServiceImpl implements IModeService { private _onDidCreateMode: Emitter = new Emitter(); public onDidCreateMode: Event = this._onDidCreateMode.event; - constructor( - instantiationService: IInstantiationService, - extensionService: IExtensionService - ) { - this._instantiationService = instantiationService; - this._extensionService = extensionService; - + constructor() { this._instantiatedModes = {}; this._registry = new LanguagesRegistry(); this._registry.onDidAddModes((modes) => this._onDidAddModes.fire(modes)); } + protected _onReady(): TPromise { + return TPromise.as(true); + } + public isRegisteredMode(mimetypeOrModeId: string): boolean { return this._registry.isRegisteredMode(mimetypeOrModeId); } @@ -188,6 +130,16 @@ export class ModeServiceImpl implements IModeService { return this._registry.getModeIdForLanguageNameLowercase(alias); } + public getModeIdByFilenameOrFirstLine(filename: string, firstLine?: string): string { + var modeIds = this._registry.getModeIdsFromFilenameOrFirstLine(filename, firstLine); + + if (modeIds.length > 0) { + return modeIds[0]; + } + + return null; + } + public getModeId(commaSeparatedMimetypesOrCommaSeparatedIds: string): string { var modeIds = this._registry.extractModeIds(commaSeparatedMimetypesOrCommaSeparatedIds); @@ -245,7 +197,23 @@ export class ModeServiceImpl implements IModeService { } } - public getModeIdByLanguageName(languageName: string): string { + public getOrCreateMode(commaSeparatedMimetypesOrCommaSeparatedIds: string): TPromise { + return this._onReady().then(() => { + var modeId = this.getModeId(commaSeparatedMimetypesOrCommaSeparatedIds); + // Fall back to plain text if no mode was found + return this._getOrCreateMode(modeId || 'plaintext'); + }); + } + + public getOrCreateModeByLanguageName(languageName: string): TPromise { + return this._onReady().then(() => { + var modeId = this._getModeIdByLanguageName(languageName); + // Fall back to plain text if no mode was found + return this._getOrCreateMode(modeId || 'plaintext'); + }); + } + + private _getModeIdByLanguageName(languageName: string): string { var modeIds = this._registry.getModeIdsFromLanguageName(languageName); if (modeIds.length > 0) { @@ -255,38 +223,8 @@ export class ModeServiceImpl implements IModeService { return null; } - public getModeIdByFilenameOrFirstLine(filename: string, firstLine?: string): string { - var modeIds = this._registry.getModeIdsFromFilenameOrFirstLine(filename, firstLine); - - if (modeIds.length > 0) { - return modeIds[0]; - } - - return null; - } - - public onReady(): TPromise { - return this._extensionService.onReady(); - } - - public getOrCreateMode(commaSeparatedMimetypesOrCommaSeparatedIds: string): TPromise { - return this.onReady().then(() => { - var modeId = this.getModeId(commaSeparatedMimetypesOrCommaSeparatedIds); - // Fall back to plain text if no mode was found - return this._getOrCreateMode(modeId || 'plaintext'); - }); - } - - public getOrCreateModeByLanguageName(languageName: string): TPromise { - return this.onReady().then(() => { - var modeId = this.getModeIdByLanguageName(languageName); - // Fall back to plain text if no mode was found - return this._getOrCreateMode(modeId || 'plaintext'); - }); - } - public getOrCreateModeByFilenameOrFirstLine(filename: string, firstLine?: string): TPromise { - return this.onReady().then(() => { + return this._onReady().then(() => { var modeId = this.getModeIdByFilenameOrFirstLine(filename, firstLine); // Fall back to plain text if no mode was found return this._getOrCreateMode(modeId || 'plaintext'); @@ -299,87 +237,7 @@ export class ModeServiceImpl implements IModeService { this._instantiatedModes[modeId] = new FrankensteinMode(languageIdentifier); this._onDidCreateMode.fire(this._instantiatedModes[modeId]); - - this._extensionService.activateByEvent(`onLanguage:${modeId}`).done(null, onUnexpectedError); } return this._instantiatedModes[modeId]; } } - -export class MainThreadModeServiceImpl extends ModeServiceImpl { - private _configurationService: IConfigurationService; - private _onReadyPromise: TPromise; - - constructor( - @IInstantiationService instantiationService: IInstantiationService, - @IExtensionService extensionService: IExtensionService, - @IConfigurationService configurationService: IConfigurationService - ) { - super(instantiationService, extensionService); - this._configurationService = configurationService; - - languagesExtPoint.setHandler((extensions: IExtensionPointUser[]) => { - let allValidLanguages: IValidLanguageExtensionPoint[] = []; - - for (let i = 0, len = extensions.length; i < len; i++) { - let extension = extensions[i]; - - if (!Array.isArray(extension.value)) { - extension.collector.error(nls.localize('invalid', "Invalid `contributes.{0}`. Expected an array.", languagesExtPoint.name)); - continue; - } - - for (let j = 0, lenJ = extension.value.length; j < lenJ; j++) { - let ext = extension.value[j]; - if (isValidLanguageExtensionPoint(ext, extension.collector)) { - let configuration = (ext.configuration ? paths.join(extension.description.extensionFolderPath, ext.configuration) : ext.configuration); - allValidLanguages.push({ - id: ext.id, - extensions: ext.extensions, - filenames: ext.filenames, - filenamePatterns: ext.filenamePatterns, - firstLine: ext.firstLine, - aliases: ext.aliases, - mimetypes: ext.mimetypes, - configuration: configuration - }); - } - } - } - - ModesRegistry.registerLanguages(allValidLanguages); - - }); - - this._configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(e.config)); - } - - public onReady(): TPromise { - if (!this._onReadyPromise) { - const configuration = this._configurationService.getConfiguration(); - this._onReadyPromise = this._extensionService.onReady().then(() => { - this.onConfigurationChange(configuration); - - return true; - }); - } - - return this._onReadyPromise; - } - - private onConfigurationChange(configuration: IFilesConfiguration): void { - - // Clear user configured mime associations - mime.clearTextMimes(true /* user configured */); - - // Register based on settings - if (configuration.files && configuration.files.associations) { - Object.keys(configuration.files.associations).forEach(pattern => { - const langId = configuration.files.associations[pattern]; - const mimetype = this.getMimeForMode(langId) || `text/x-${langId}`; - - mime.registerTextMime({ id: langId, mime: mimetype, filepattern: pattern, userConfigured: true }); - }); - } - } -} diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 19223c6bdb2..1c87597e1f7 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -14,7 +14,6 @@ import Severity from 'vs/base/common/severity'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { IMarker, IMarkerService } from 'vs/platform/markers/common/markers'; -import { anonymize } from 'vs/platform/telemetry/common/telemetry'; import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { Model } from 'vs/editor/common/model/model'; @@ -23,7 +22,6 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import * as platform from 'vs/base/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { DEFAULT_INDENTATION, DEFAULT_TRIM_AUTO_WHITESPACE } from 'vs/editor/common/config/defaultConfig'; -import { IMessageService } from 'vs/platform/message/common/message'; import { PLAINTEXT_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/modesRegistry'; function MODEL_ID(resource: URI): string { @@ -179,7 +177,6 @@ export class ModelServiceImpl implements IModelService { private _markerService: IMarkerService; private _markerServiceSubscription: IDisposable; - private _messageService: IMessageService; private _configurationService: IConfigurationService; private _configurationServiceSubscription: IDisposable; @@ -189,8 +186,6 @@ export class ModelServiceImpl implements IModelService { private _modelCreationOptions: editorCommon.ITextModelCreationOptions; - private _hasShownMigrationMessage: boolean; - /** * All the models known in the system. */ @@ -199,7 +194,6 @@ export class ModelServiceImpl implements IModelService { constructor( @IMarkerService markerService: IMarkerService, @IConfigurationService configurationService: IConfigurationService, - @IMessageService messageService: IMessageService ) { this._modelCreationOptions = { tabSize: DEFAULT_INDENTATION.tabSize, @@ -210,8 +204,6 @@ export class ModelServiceImpl implements IModelService { }; this._markerService = markerService; this._configurationService = configurationService; - this._messageService = messageService; - this._hasShownMigrationMessage = false; this._models = {}; @@ -225,21 +217,17 @@ export class ModelServiceImpl implements IModelService { let readConfig = (config: IRawConfig) => { - let shouldShowMigrationMessage = false; - let tabSize = DEFAULT_INDENTATION.tabSize; if (config.editor && typeof config.editor.tabSize !== 'undefined') { let parsedTabSize = parseInt(config.editor.tabSize, 10); if (!isNaN(parsedTabSize)) { tabSize = parsedTabSize; } - shouldShowMigrationMessage = shouldShowMigrationMessage || (config.editor.tabSize === 'auto'); } let insertSpaces = DEFAULT_INDENTATION.insertSpaces; if (config.editor && typeof config.editor.insertSpaces !== 'undefined') { insertSpaces = (config.editor.insertSpaces === 'false' ? false : Boolean(config.editor.insertSpaces)); - shouldShowMigrationMessage = shouldShowMigrationMessage || (config.editor.insertSpaces === 'auto'); } let newDefaultEOL = this._modelCreationOptions.defaultEOL; @@ -267,12 +255,6 @@ export class ModelServiceImpl implements IModelService { defaultEOL: newDefaultEOL, trimAutoWhitespace: trimAutoWhitespace }); - - - if (shouldShowMigrationMessage && !this._hasShownMigrationMessage) { - this._hasShownMigrationMessage = true; - this._messageService.show(Severity.Info, nls.localize('indentAutoMigrate', "Please update your settings: `editor.detectIndentation` replaces `editor.tabSize`: \"auto\" or `editor.insertSpaces`: \"auto\"")); - } }; this._configurationServiceSubscription = this._configurationService.onDidUpdateConfiguration(e => { @@ -362,7 +344,7 @@ export class ModelServiceImpl implements IModelService { if (this._models[modelId]) { // There already exists a model with this id => this is a programmer error - throw new Error('ModelService: Cannot add model ' + anonymize(modelId) + ' because it already exists!'); + throw new Error('ModelService: Cannot add model because it already exists!'); } let modelData = new ModelData(model, (modelData, events) => this._onModelEvents(modelData, events)); diff --git a/src/vs/editor/common/viewLayout/viewLineParts.ts b/src/vs/editor/common/viewLayout/viewLineParts.ts index aa7694aa5e5..5baef520900 100644 --- a/src/vs/editor/common/viewLayout/viewLineParts.ts +++ b/src/vs/editor/common/viewLayout/viewLineParts.ts @@ -4,220 +4,95 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import * as strings from 'vs/base/common/strings'; -import { Range } from 'vs/editor/common/core/range'; -import { ViewLineToken, ViewLineTokens } from 'vs/editor/common/core/viewLineToken'; import { InlineDecoration } from 'vs/editor/common/viewModel/viewModel'; -import { CharCode } from 'vs/base/common/charCode'; -import { LineParts } from 'vs/editor/common/core/lineParts'; +import { Constants } from 'vs/editor/common/core/uint'; -function cmpLineDecorations(a: InlineDecoration, b: InlineDecoration): number { - let r = Range.compareRangesUsingStarts(a.range, b.range); - if (r === 0) { - if (a.inlineClassName < b.inlineClassName) { - return -1; +export class Decoration { + _decorationBrand: void; + + public readonly startColumn: number; + public readonly endColumn: number; + public readonly className: string; + + constructor(startColumn: number, endColumn: number, className: string) { + this.startColumn = startColumn; + this.endColumn = endColumn; + this.className = className; + } + + private static _equals(a: Decoration, b: Decoration): boolean { + return ( + a.startColumn === b.startColumn + && a.endColumn === b.endColumn + && a.className === b.className + ); + } + + public static equalsArr(a: Decoration[], b: Decoration[]): boolean { + let aLen = a.length; + let bLen = b.length; + if (aLen !== bLen) { + return false; } - if (a.inlineClassName > b.inlineClassName) { - return 1; - } - return 0; - } - return r; -} - -export function createLineParts(lineNumber: number, minLineColumn: number, lineContent: string, tabSize: number, lineTokens: ViewLineTokens, rawLineDecorations: InlineDecoration[], renderWhitespace: 'none' | 'boundary' | 'all'): LineParts { - if (renderWhitespace !== 'none') { - rawLineDecorations = insertWhitespaceLineDecorations(lineNumber, lineContent, tabSize, lineTokens.getFauxIndentLength(), renderWhitespace, rawLineDecorations); - } - - if (rawLineDecorations.length > 0) { - rawLineDecorations.sort(cmpLineDecorations); - return createViewLineParts(lineNumber, minLineColumn, lineTokens, lineContent, rawLineDecorations); - } else { - return createFastViewLineParts(lineTokens, lineContent); - } -} - -function trimEmptyTrailingPart(parts: ViewLineToken[], lineContent: string): ViewLineToken[] { - if (parts.length <= 1) { - return parts; - } - var lastPartStartIndex = parts[parts.length - 1].startIndex; - if (lastPartStartIndex < lineContent.length) { - // All is good - return parts; - } - // Remove last line part - return parts.slice(0, parts.length - 1); -} - -function insertOneCustomLineDecoration(dest: InlineDecoration[], lineNumber: number, startColumn: number, endColumn: number, className: string): void { - dest.push(new InlineDecoration(new Range(lineNumber, startColumn, lineNumber, endColumn), className)); -} - -function insertWhitespaceLineDecorations(lineNumber: number, lineContent: string, tabSize: number, fauxIndentLength: number, renderWhitespace: 'none' | 'boundary' | 'all', rawLineDecorations: InlineDecoration[]): InlineDecoration[] { - let lineLength = lineContent.length; - if (lineLength === fauxIndentLength) { - return rawLineDecorations; - } - - let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent); - let lastNonWhitespaceIndex: number; - if (firstNonWhitespaceIndex === -1) { - // The entire line is whitespace - firstNonWhitespaceIndex = lineLength; - lastNonWhitespaceIndex = lineLength; - } else { - lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent); - } - - let sm_endIndex: number[] = []; - let sm_decoration: string[] = []; - - if (fauxIndentLength > 0) { - // add faux indent state - sm_endIndex.push(fauxIndentLength - 1); - sm_decoration.push(null); - } - if (firstNonWhitespaceIndex > fauxIndentLength) { - // add leading whitespace state - sm_endIndex.push(firstNonWhitespaceIndex - 1); - sm_decoration.push('vs-whitespace'); - - } - - let startOfWhitespace = -1; - let hasTab = false; - - for (let i = Math.max(firstNonWhitespaceIndex, fauxIndentLength); i <= lastNonWhitespaceIndex; ++i) { - let currentCharIsTab = lineContent.charCodeAt(i) === CharCode.Tab; - if (currentCharIsTab || lineContent.charCodeAt(i) === CharCode.Space) { - if (currentCharIsTab) { - hasTab = true; - } - if (startOfWhitespace === -1) { - startOfWhitespace = i; - } - } else if (startOfWhitespace !== -1) { - if (renderWhitespace === 'all' || renderWhitespace === 'boundary' && (hasTab || i - startOfWhitespace >= 2)) { - sm_endIndex.push(startOfWhitespace - 1); - sm_decoration.push(null); - - sm_endIndex.push(i - 1); - sm_decoration.push('vs-whitespace'); - } - - startOfWhitespace = -1; - hasTab = false; - } - } - - // add content state - sm_endIndex.push(lastNonWhitespaceIndex); - sm_decoration.push(null); - - // add trailing whitespace state - sm_endIndex.push(lineLength - 1); - sm_decoration.push('vs-whitespace'); - - // add dummy state to avoid array length checks - sm_endIndex.push(lineLength); - sm_decoration.push(null); - - return insertCustomLineDecorationsWithStateMachine(lineNumber, lineContent, tabSize, rawLineDecorations, sm_endIndex, sm_decoration); -} - -function insertCustomLineDecorationsWithStateMachine(lineNumber: number, lineContent: string, tabSize: number, rawLineDecorations: InlineDecoration[], sm_endIndex: number[], sm_decoration: string[]): InlineDecoration[] { - let lineLength = lineContent.length; - let currentStateIndex = 0; - let stateEndIndex = sm_endIndex[currentStateIndex]; - let stateDecoration = sm_decoration[currentStateIndex]; - - let result = rawLineDecorations.slice(0); - let tmpIndent = 0; - let whitespaceStartColumn = 1; - - for (let index = 0; index < lineLength; index++) { - let chCode = lineContent.charCodeAt(index); - - if (chCode === CharCode.Tab) { - tmpIndent = tabSize; - } else { - tmpIndent++; - } - - if (index === stateEndIndex) { - if (stateDecoration !== null) { - insertOneCustomLineDecoration(result, lineNumber, whitespaceStartColumn, index + 2, stateDecoration); - } - whitespaceStartColumn = index + 2; - tmpIndent = tmpIndent % tabSize; - - currentStateIndex++; - stateEndIndex = sm_endIndex[currentStateIndex]; - stateDecoration = sm_decoration[currentStateIndex]; - } else { - if (stateDecoration !== null && tmpIndent >= tabSize) { - insertOneCustomLineDecoration(result, lineNumber, whitespaceStartColumn, index + 2, stateDecoration); - whitespaceStartColumn = index + 2; - tmpIndent = tmpIndent % tabSize; + for (let i = 0; i < aLen; i++) { + if (!Decoration._equals(a[i], b[i])) { + return false; } } + return true; } - return result; -} - -function createFastViewLineParts(lineTokens: ViewLineTokens, lineContent: string): LineParts { - let parts = lineTokens.getTokens(); - parts = trimEmptyTrailingPart(parts, lineContent); - return new LineParts(parts, lineContent.length + 1); -} - -function createViewLineParts(lineNumber: number, minLineColumn: number, lineTokens: ViewLineTokens, lineContent: string, rawLineDecorations: InlineDecoration[]): LineParts { - // lineDecorations might overlap on top of each other, so they need to be normalized - var lineDecorations = LineDecorationsNormalizer.normalize(lineNumber, minLineColumn, rawLineDecorations), - lineDecorationsIndex = 0, - lineDecorationsLength = lineDecorations.length; - - var actualLineTokens = lineTokens.getTokens(), - nextStartOffset: number, - currentTokenEndOffset: number, - currentTokenClassName: string; - - var parts: ViewLineToken[] = []; - - for (var i = 0, len = actualLineTokens.length; i < len; i++) { - nextStartOffset = actualLineTokens[i].startIndex; - currentTokenEndOffset = (i + 1 < len ? actualLineTokens[i + 1].startIndex : lineTokens.getTextLength()); - currentTokenClassName = actualLineTokens[i].type; - - while (lineDecorationsIndex < lineDecorationsLength && lineDecorations[lineDecorationsIndex].startOffset < currentTokenEndOffset) { - if (lineDecorations[lineDecorationsIndex].startOffset > nextStartOffset) { - // the first decorations starts after the token - parts.push(new ViewLineToken(nextStartOffset, currentTokenClassName)); - nextStartOffset = lineDecorations[lineDecorationsIndex].startOffset; - } - - parts.push(new ViewLineToken(nextStartOffset, currentTokenClassName + ' ' + lineDecorations[lineDecorationsIndex].className)); - - if (lineDecorations[lineDecorationsIndex].endOffset >= currentTokenEndOffset) { - // this decoration goes on to the next token - nextStartOffset = currentTokenEndOffset; - break; - } else { - // this decorations stops inside this token - nextStartOffset = lineDecorations[lineDecorationsIndex].endOffset + 1; - lineDecorationsIndex++; - } + public static filter(lineDecorations: InlineDecoration[], lineNumber: number, minLineColumn: number, maxLineColumn: number): Decoration[] { + if (lineDecorations.length === 0) { + return []; } - if (nextStartOffset < currentTokenEndOffset) { - parts.push(new ViewLineToken(nextStartOffset, currentTokenClassName)); + let result: Decoration[] = [], resultLen = 0; + + for (let i = 0, len = lineDecorations.length; i < len; i++) { + let d = lineDecorations[i]; + let range = d.range; + let className = d.inlineClassName; + + if (range.endLineNumber < lineNumber || range.startLineNumber > lineNumber) { + // Ignore decorations that sit outside this line + continue; + } + + if (range.isEmpty()) { + // Ignore empty range decorations + continue; + } + + let startColumn = (range.startLineNumber === lineNumber ? range.startColumn : minLineColumn); + let endColumn = (range.endLineNumber === lineNumber ? range.endColumn : maxLineColumn); + + if (endColumn <= 1) { + // An empty decoration (endColumn === 1) + continue; + } + + result[resultLen++] = new Decoration(startColumn, endColumn, className); } + + return result; } - return new LineParts(parts, lineContent.length + 1); + public static compare(a: Decoration, b: Decoration): number { + if (a.startColumn === b.startColumn) { + if (a.endColumn === b.endColumn) { + if (a.className < b.className) { + return -1; + } + if (a.className > b.className) { + return 1; + } + return 0; + } + return a.endColumn - b.endColumn; + } + return a.startColumn - b.startColumn; + } } export class DecorationSegment { @@ -292,63 +167,36 @@ class Stack { } export class LineDecorationsNormalizer { - /** - * A number that is guaranteed to be larger than the maximum line column - */ - private static MAX_LINE_LENGTH = 10000000; - /** * Normalize line decorations. Overlapping decorations will generate multiple segments */ - public static normalize(lineNumber: number, minLineColumn: number, lineDecorations: InlineDecoration[]): DecorationSegment[] { - - var result: DecorationSegment[] = []; - + public static normalize(lineDecorations: Decoration[]): DecorationSegment[] { if (lineDecorations.length === 0) { - return result; + return []; } - var stack = new Stack(), - nextStartOffset = 0, - d: InlineDecoration, - currentStartOffset: number, - currentEndOffset: number, - i: number, - len: number; + let result: DecorationSegment[] = []; - for (i = 0, len = lineDecorations.length; i < len; i++) { - d = lineDecorations[i]; + let stack = new Stack(); + let nextStartOffset = 0; - if (d.range.endLineNumber < lineNumber || d.range.startLineNumber > lineNumber) { - // Ignore decorations that sit outside this line - continue; - } + for (let i = 0, len = lineDecorations.length; i < len; i++) { + let d = lineDecorations[i]; - if (d.range.startLineNumber === d.range.endLineNumber && d.range.startColumn === d.range.endColumn) { - // Ignore empty range decorations - continue; - } - - currentStartOffset = (d.range.startLineNumber === lineNumber ? d.range.startColumn - 1 : minLineColumn - 1); - currentEndOffset = (d.range.endLineNumber === lineNumber ? d.range.endColumn - 2 : LineDecorationsNormalizer.MAX_LINE_LENGTH - 1); - - if (currentEndOffset < 0) { - // An empty decoration (endColumn === 1) - continue; - } + let currentStartOffset = d.startColumn - 1; + let currentEndOffset = d.endColumn - 2; nextStartOffset = stack.consumeLowerThan(currentStartOffset, nextStartOffset, result); if (stack.count === 0) { nextStartOffset = currentStartOffset; } - stack.insert(currentEndOffset, d.inlineClassName); + stack.insert(currentEndOffset, d.className); } - stack.consumeLowerThan(LineDecorationsNormalizer.MAX_LINE_LENGTH, nextStartOffset, result); + stack.consumeLowerThan(Constants.MAX_SAFE_SMALL_INTEGER, nextStartOffset, result); return result; } } - diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index 58621061fbc..6f27d84803f 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -6,46 +6,66 @@ import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; import { CharCode } from 'vs/base/common/charCode'; -import { LineParts } from 'vs/editor/common/core/lineParts'; +import { Decoration, LineDecorationsNormalizer } from 'vs/editor/common/viewLayout/viewLineParts'; +import * as strings from 'vs/base/common/strings'; + +export const enum RenderWhitespace { + None = 0, + Boundary = 1, + All = 2 +} export class RenderLineInput { - _renderLineInputBrand: void; - lineContent: string; - tabSize: number; - spaceWidth: number; - stopRenderingLineAfter: number; - renderWhitespace: 'none' | 'boundary' | 'all'; - renderControlCharacters: boolean; - lineParts: LineParts; + public readonly lineContent: string; + public readonly fauxIndentLength: number; + public readonly lineTokens: ViewLineToken[]; + public readonly lineDecorations: Decoration[]; + public readonly tabSize: number; + public readonly spaceWidth: number; + public readonly stopRenderingLineAfter: number; + public readonly renderWhitespace: RenderWhitespace; + public readonly renderControlCharacters: boolean; constructor( lineContent: string, + fauxIndentLength: number, + lineTokens: ViewLineToken[], + lineDecorations: Decoration[], tabSize: number, spaceWidth: number, stopRenderingLineAfter: number, renderWhitespace: 'none' | 'boundary' | 'all', renderControlCharacters: boolean, - lineParts: LineParts ) { this.lineContent = lineContent; + this.fauxIndentLength = fauxIndentLength; + this.lineTokens = lineTokens; + this.lineDecorations = lineDecorations; this.tabSize = tabSize; this.spaceWidth = spaceWidth; this.stopRenderingLineAfter = stopRenderingLineAfter; - this.renderWhitespace = renderWhitespace; + this.renderWhitespace = ( + renderWhitespace === 'all' + ? RenderWhitespace.All + : renderWhitespace === 'boundary' + ? RenderWhitespace.Boundary + : RenderWhitespace.None + ); this.renderControlCharacters = renderControlCharacters; - this.lineParts = lineParts; } public equals(other: RenderLineInput): boolean { return ( this.lineContent === other.lineContent + && this.fauxIndentLength === other.fauxIndentLength && this.tabSize === other.tabSize && this.spaceWidth === other.spaceWidth && this.stopRenderingLineAfter === other.stopRenderingLineAfter && this.renderWhitespace === other.renderWhitespace && this.renderControlCharacters === other.renderControlCharacters - && this.lineParts.equals(other.lineParts) + && Decoration.equalsArr(this.lineDecorations, other.lineDecorations) + && ViewLineToken.equalsArr(this.lineTokens, other.lineTokens) ); } } @@ -166,87 +186,269 @@ export class RenderLineOutput { readonly characterMapping: CharacterMapping; readonly output: string; - readonly isWhitespaceOnly: boolean; - constructor(characterMapping: CharacterMapping, output: string, isWhitespaceOnly: boolean) { + constructor(characterMapping: CharacterMapping, output: string) { this.characterMapping = characterMapping; this.output = output; - this.isWhitespaceOnly = isWhitespaceOnly; } } -export function renderLine(input: RenderLineInput): RenderLineOutput { - const lineText = input.lineContent; - const lineTextLength = lineText.length; - const tabSize = input.tabSize; - const spaceWidth = input.spaceWidth; - const actualLineParts = input.lineParts.parts; - const renderWhitespace = input.renderWhitespace; - const renderControlCharacters = input.renderControlCharacters; - const charBreakIndex = (input.stopRenderingLineAfter === -1 ? lineTextLength : input.stopRenderingLineAfter - 1); - - if (lineTextLength === 0) { +export function renderViewLine(input: RenderLineInput): RenderLineOutput { + if (input.lineContent.length === 0) { return new RenderLineOutput( new CharacterMapping(0), // This is basically for IE's hit test to work - ' ', - true + ' ' ); } - if (actualLineParts.length === 0) { - throw new Error('Cannot render non empty line without line parts!'); + return _renderLine(resolveRenderLineInput(input)); +} + +class ResolvedRenderLineInput { + constructor( + public readonly lineContent: string, + public readonly len: number, + public readonly isOverflowing: boolean, + public readonly tokens: ViewLineToken[], + public readonly lineDecorations: Decoration[], + public readonly tabSize: number, + public readonly spaceWidth: number, + public readonly renderWhitespace: RenderWhitespace, + public readonly renderControlCharacters: boolean, + ) { + // + } +} + +function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput { + const lineContent = input.lineContent; + + let isOverflowing: boolean; + let len: number; + + if (input.stopRenderingLineAfter !== -1 && input.stopRenderingLineAfter < lineContent.length) { + isOverflowing = true; + len = input.stopRenderingLineAfter; + } else { + isOverflowing = false; + len = lineContent.length; } - return renderLineActual(lineText, lineTextLength, tabSize, spaceWidth, actualLineParts, renderWhitespace, renderControlCharacters, charBreakIndex); + let tokens = removeOverflowing(input.lineTokens, len); + if (input.renderWhitespace === RenderWhitespace.All || input.renderWhitespace === RenderWhitespace.Boundary) { + tokens = _applyRenderWhitespace(lineContent, len, tokens, input.fauxIndentLength, input.tabSize, input.renderWhitespace === RenderWhitespace.Boundary); + } + if (input.lineDecorations.length > 0) { + tokens = _applyInlineDecorations(lineContent, len, tokens, input.lineDecorations); + } + + return new ResolvedRenderLineInput( + lineContent, + len, + isOverflowing, + tokens, + input.lineDecorations, + input.tabSize, + input.spaceWidth, + input.renderWhitespace, + input.renderControlCharacters + ); } -function isWhitespace(type: string): boolean { - return (type.indexOf('vs-whitespace') >= 0); +function removeOverflowing(tokens: ViewLineToken[], len: number): ViewLineToken[] { + if (tokens.length === 0) { + return tokens; + } + if (tokens[tokens.length - 1].endIndex === len) { + return tokens; + } + let result: ViewLineToken[] = []; + for (let tokenIndex = 0, tokensLen = tokens.length; tokenIndex < tokensLen; tokenIndex++) { + const endIndex = tokens[tokenIndex].endIndex; + if (endIndex === len) { + result[tokenIndex] = tokens[tokenIndex]; + break; + } + if (endIndex > len) { + result[tokenIndex] = new ViewLineToken(len, tokens[tokenIndex].type); + break; + } + result[tokenIndex] = tokens[tokenIndex]; + } + return result; } -function isControlCharacter(characterCode: number): boolean { - return characterCode < 32; -} +function _applyRenderWhitespace(lineContent: string, len: number, tokens: ViewLineToken[], fauxIndentLength: number, tabSize: number, onlyBoundary: boolean): ViewLineToken[] { -const _controlCharacterSequenceConversionStart = 9216; -function controlCharacterToPrintable(characterCode: number): string { - return String.fromCharCode(_controlCharacterSequenceConversionStart + characterCode); -} + let result: ViewLineToken[] = [], resultLen = 0; + let tokenIndex = 0; + let tokenType = tokens[tokenIndex].type; + let tokenEndIndex = tokens[tokenIndex].endIndex; -function renderLineActual(lineText: string, lineTextLength: number, tabSize: number, spaceWidth: number, actualLineParts: ViewLineToken[], renderWhitespace: 'none' | 'boundary' | 'all', renderControlCharacters: boolean, charBreakIndex: number): RenderLineOutput { - lineTextLength = +lineTextLength; - tabSize = +tabSize; - charBreakIndex = +charBreakIndex; + if (fauxIndentLength > 0) { + result[resultLen++] = new ViewLineToken(fauxIndentLength, ''); + } - let charIndex = 0; - let out = ''; - let charOffsetInPart = 0; - let tabsCharDelta = 0; - let isWhitespaceOnly = /^\s*$/.test(lineText); + let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent); + let lastNonWhitespaceIndex: number; + if (firstNonWhitespaceIndex === -1) { + // The entire line is whitespace + firstNonWhitespaceIndex = len; + lastNonWhitespaceIndex = len; + } else { + lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent); + } - let characterMapping = new CharacterMapping(Math.min(lineTextLength, charBreakIndex) + 1); + let tmpIndent = 0; + for (let charIndex = 0; charIndex < fauxIndentLength; charIndex++) { + const chCode = lineContent.charCodeAt(charIndex); + if (chCode === CharCode.Tab) { + tmpIndent = tabSize; + } else { + tmpIndent++; + } + } + tmpIndent = tmpIndent % tabSize; - out += ''; - for (let partIndex = 0, partIndexLen = actualLineParts.length; partIndex < partIndexLen; partIndex++) { - let part = actualLineParts[partIndex]; + let wasInWhitespace = false; + for (let charIndex = fauxIndentLength; charIndex < len; charIndex++) { + const chCode = lineContent.charCodeAt(charIndex); - let parsRendersWhitespace = (renderWhitespace !== 'none' && isWhitespace(part.type)); - - let toCharIndex = lineTextLength; - if (partIndex + 1 < partIndexLen) { - let nextPart = actualLineParts[partIndex + 1]; - toCharIndex = Math.min(lineTextLength, nextPart.startIndex); + let isInWhitespace: boolean; + if (charIndex < firstNonWhitespaceIndex || charIndex > lastNonWhitespaceIndex) { + // in leading or trailing whitespace + isInWhitespace = true; + } else if (chCode === CharCode.Tab) { + // a tab character is rendered both in all and boundary cases + isInWhitespace = true; + } else if (chCode === CharCode.Space) { + // hit a space character + if (onlyBoundary) { + // rendering only boundary whitespace + if (wasInWhitespace) { + isInWhitespace = true; + } else { + const nextChCode = (charIndex + 1 < len ? lineContent.charCodeAt(charIndex + 1) : CharCode.Null); + isInWhitespace = (nextChCode === CharCode.Space || nextChCode === CharCode.Tab); + } + } else { + isInWhitespace = true; + } + } else { + isInWhitespace = false; } + if (wasInWhitespace) { + // was in whitespace token + if (!isInWhitespace || tmpIndent >= tabSize) { + // leaving whitespace token or entering a new indent + result[resultLen++] = new ViewLineToken(charIndex, 'vs-whitespace'); + tmpIndent = tmpIndent % tabSize; + } + } else { + // was in regular token + if (charIndex === tokenEndIndex || (isInWhitespace && charIndex > fauxIndentLength)) { + result[resultLen++] = new ViewLineToken(charIndex, tokenType); + tmpIndent = tmpIndent % tabSize; + } + } + + if (chCode === CharCode.Tab) { + tmpIndent = tabSize; + } else { + tmpIndent++; + } + + wasInWhitespace = isInWhitespace; + + if (charIndex === tokenEndIndex) { + tokenIndex++; + tokenType = tokens[tokenIndex].type; + tokenEndIndex = tokens[tokenIndex].endIndex; + } + } + + if (wasInWhitespace) { + // was in whitespace token + result[resultLen++] = new ViewLineToken(len, 'vs-whitespace'); + } else { + // was in regular token + result[resultLen++] = new ViewLineToken(len, tokenType); + } + + return result; +} + +function _applyInlineDecorations(lineContent: string, len: number, tokens: ViewLineToken[], _lineDecorations: Decoration[]): ViewLineToken[] { + _lineDecorations.sort(Decoration.compare); + const lineDecorations = LineDecorationsNormalizer.normalize(_lineDecorations); + const lineDecorationsLen = lineDecorations.length; + + let lineDecorationIndex = 0; + let result: ViewLineToken[] = [], resultLen = 0, lastResultEndIndex = 0; + for (let tokenIndex = 0, len = tokens.length; tokenIndex < len; tokenIndex++) { + const token = tokens[tokenIndex]; + const tokenEndIndex = token.endIndex; + const tokenType = token.type; + + while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset < tokenEndIndex) { + const lineDecoration = lineDecorations[lineDecorationIndex]; + + if (lineDecoration.startOffset > lastResultEndIndex) { + lastResultEndIndex = lineDecoration.startOffset; + result[resultLen++] = new ViewLineToken(lastResultEndIndex, tokenType); + } + + if (lineDecoration.endOffset + 1 < tokenEndIndex) { + lastResultEndIndex = lineDecoration.endOffset + 1; + result[resultLen++] = new ViewLineToken(lastResultEndIndex, tokenType + ' ' + lineDecoration.className); + lineDecorationIndex++; + } else { + break; + } + } + + if (tokenEndIndex > lastResultEndIndex) { + lastResultEndIndex = tokenEndIndex; + result[resultLen++] = new ViewLineToken(lastResultEndIndex, tokenType); + } + } + + return result; +} + +function _renderLine(input: ResolvedRenderLineInput): RenderLineOutput { + const lineContent = input.lineContent; + const len = input.len; + const isOverflowing = input.isOverflowing; + const tokens = input.tokens; + const tabSize = input.tabSize; + const spaceWidth = input.spaceWidth; + const renderWhitespace = input.renderWhitespace; + const renderControlCharacters = input.renderControlCharacters; + + const characterMapping = new CharacterMapping(len + 1); + + let charIndex = 0; + let tabsCharDelta = 0; + let charOffsetInPart = 0; + + let out = ''; + for (let tokenIndex = 0, tokensLen = tokens.length; tokenIndex < tokensLen; tokenIndex++) { + const token = tokens[tokenIndex]; + const tokenEndIndex = token.endIndex; + const tokenType = token.type; + const tokenRendersWhitespace = (renderWhitespace !== RenderWhitespace.None && (tokenType.indexOf('vs-whitespace') >= 0)); charOffsetInPart = 0; - if (parsRendersWhitespace) { + + if (tokenRendersWhitespace) { let partContentCnt = 0; let partContent = ''; - for (; charIndex < toCharIndex; charIndex++) { - characterMapping.setPartData(charIndex, partIndex, charOffsetInPart); - let charCode = lineText.charCodeAt(charIndex); + for (; charIndex < tokenEndIndex; charIndex++) { + characterMapping.setPartData(charIndex, tokenIndex, charOffsetInPart); + const charCode = lineContent.charCodeAt(charIndex); if (charCode === CharCode.Tab) { let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize; @@ -269,24 +471,16 @@ function renderLineActual(lineText: string, lineTextLength: number, tabSize: num } charOffsetInPart++; - - if (charIndex >= charBreakIndex) { - out += `${partContent}…`; - characterMapping.setPartData(charIndex, partIndex, charOffsetInPart); - return new RenderLineOutput( - characterMapping, - out, - isWhitespaceOnly - ); - } } - out += `${partContent}`; - } else { - out += ``; - for (; charIndex < toCharIndex; charIndex++) { - characterMapping.setPartData(charIndex, partIndex, charOffsetInPart); - let charCode = lineText.charCodeAt(charIndex); + out += `${partContent}`; + + } else { + let partContent = ''; + + for (; charIndex < tokenEndIndex; charIndex++) { + characterMapping.setPartData(charIndex, tokenIndex, charOffsetInPart); + const charCode = lineContent.charCodeAt(charIndex); switch (charCode) { case CharCode.Tab: @@ -294,75 +488,66 @@ function renderLineActual(lineText: string, lineTextLength: number, tabSize: num tabsCharDelta += insertSpacesCount - 1; charOffsetInPart += insertSpacesCount - 1; while (insertSpacesCount > 0) { - out += ' '; + partContent += ' '; insertSpacesCount--; } break; case CharCode.Space: - out += ' '; + partContent += ' '; break; case CharCode.LessThan: - out += '<'; + partContent += '<'; break; case CharCode.GreaterThan: - out += '>'; + partContent += '>'; break; case CharCode.Ampersand: - out += '&'; + partContent += '&'; break; case CharCode.Null: - out += '�'; + partContent += '�'; break; case CharCode.UTF8_BOM: case CharCode.LINE_SEPARATOR_2028: - out += '\ufffd'; + partContent += '\ufffd'; break; case CharCode.CarriageReturn: // zero width space, because carriage return would introduce a line break - out += '​'; + partContent += '​'; break; default: - if (renderControlCharacters && isControlCharacter(charCode)) { - out += controlCharacterToPrintable(charCode); + if (renderControlCharacters && charCode < 32) { + partContent += String.fromCharCode(9216 + charCode); } else { - out += lineText.charAt(charIndex); + partContent += String.fromCharCode(charCode);; } } charOffsetInPart++; - - if (charIndex >= charBreakIndex) { - out += '…'; - characterMapping.setPartData(charIndex, partIndex, charOffsetInPart); - return new RenderLineOutput( - characterMapping, - out, - isWhitespaceOnly - ); - } } - out += ''; - } + out += `${partContent}`; + } } - out += ''; // When getting client rects for the last character, we will position the // text range at the end of the span, insteaf of at the beginning of next span - characterMapping.setPartData(lineTextLength, actualLineParts.length - 1, charOffsetInPart); + characterMapping.setPartData(len, tokens.length - 1, charOffsetInPart); - return new RenderLineOutput( - characterMapping, - out, - isWhitespaceOnly - ); + if (isOverflowing) { + out += ``; + } + + out += ''; + + return new RenderLineOutput(characterMapping, out); } diff --git a/src/vs/editor/common/viewModel/filteredLineTokens.ts b/src/vs/editor/common/viewModel/filteredLineTokens.ts deleted file mode 100644 index 9aa5812f195..00000000000 --- a/src/vs/editor/common/viewModel/filteredLineTokens.ts +++ /dev/null @@ -1,34 +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 { ViewLineTokens } from 'vs/editor/common/core/viewLineToken'; -import { LineTokens } from 'vs/editor/common/core/lineTokens'; - -export class FilteredLineTokens { - /** - * [startOffset; endOffset) (i.e. do not include endOffset) - */ - public static create(original: LineTokens, startOffset: number, endOffset: number, deltaStartIndex: number): ViewLineTokens { - let inflatedTokens = original.sliceAndInflate(startOffset, endOffset, deltaStartIndex); - return new ViewLineTokens( - inflatedTokens, - deltaStartIndex, - endOffset - startOffset + deltaStartIndex - ); - } -} - -export class IdentityFilteredLineTokens { - - public static create(original: LineTokens, textLength: number): ViewLineTokens { - let inflatedTokens = original.inflate(); - return new ViewLineTokens( - inflatedTokens, - 0, - textLength - ); - } -} diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 66edc720e9c..1715ce7ef3c 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -8,10 +8,9 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; -import { FilteredLineTokens, IdentityFilteredLineTokens } from 'vs/editor/common/viewModel/filteredLineTokens'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; import { ILinesCollection } from 'vs/editor/common/viewModel/viewModelImpl'; -import { ViewLineTokens } from 'vs/editor/common/core/viewLineToken'; +import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; export class OutputPosition { _outputPositionBrand: void; @@ -49,7 +48,7 @@ export interface ISplitLine { getOutputLineContent(model: IModel, myLineNumber: number, outputLineIndex: number): string; getOutputLineMinColumn(model: IModel, myLineNumber: number, outputLineIndex: number): number; getOutputLineMaxColumn(model: IModel, myLineNumber: number, outputLineIndex: number): number; - getOutputLineTokens(model: IModel, myLineNumber: number, outputLineIndex: number): ViewLineTokens; + getOutputLineTokens(model: IModel, myLineNumber: number, outputLineIndex: number): ViewLineToken[]; getInputColumnOfOutputPosition(outputLineIndex: number, outputColumn: number): number; getOutputPositionOfInputPosition(deltaLineNumber: number, inputColumn: number): Position; } @@ -87,8 +86,9 @@ class VisibleIdentitySplitLine implements ISplitLine { return model.getLineMaxColumn(myLineNumber); } - public getOutputLineTokens(model: IModel, myLineNumber: number, outputLineIndex: number): ViewLineTokens { - return IdentityFilteredLineTokens.create(model.getLineTokens(myLineNumber, true), model.getLineMaxColumn(myLineNumber) - 1); + public getOutputLineTokens(model: IModel, myLineNumber: number, outputLineIndex: number): ViewLineToken[] { + let lineTokens = model.getLineTokens(myLineNumber, true); + return lineTokens.inflate(); } public getInputColumnOfOutputPosition(outputLineIndex: number, outputColumn: number): number { @@ -133,7 +133,7 @@ class InvisibleIdentitySplitLine implements ISplitLine { throw new Error('Not supported'); } - public getOutputLineTokens(model: IModel, myLineNumber: number, outputLineIndex: number): ViewLineTokens { + public getOutputLineTokens(model: IModel, myLineNumber: number, outputLineIndex: number): ViewLineToken[] { throw new Error('Not supported'); } @@ -223,7 +223,7 @@ export class SplitLine implements ISplitLine { return this.getOutputLineContent(model, myLineNumber, outputLineIndex).length + 1; } - public getOutputLineTokens(model: IModel, myLineNumber: number, outputLineIndex: number): ViewLineTokens { + public getOutputLineTokens(model: IModel, myLineNumber: number, outputLineIndex: number): ViewLineToken[] { if (!this._isVisible) { throw new Error('Not supported'); } @@ -233,7 +233,9 @@ export class SplitLine implements ISplitLine { if (outputLineIndex > 0) { deltaStartIndex = this.wrappedIndentLength; } - return FilteredLineTokens.create(model.getLineTokens(myLineNumber, true), startOffset, endOffset, deltaStartIndex); + + let lineTokens = model.getLineTokens(myLineNumber, true); + return lineTokens.sliceAndInflate(startOffset, endOffset, deltaStartIndex); } public getInputColumnOfOutputPosition(outputLineIndex: number, outputColumn: number): number { @@ -691,7 +693,7 @@ export class SplitLinesCollection implements ILinesCollection { return this.lines[lineIndex].getOutputLineMaxColumn(this.model, lineIndex + 1, remainder); } - public getOutputLineTokens(outputLineNumber: number): ViewLineTokens { + public getOutputLineTokens(outputLineNumber: number): ViewLineToken[] { this._ensureValidState(); outputLineNumber = this._toValidOutputLineNumber(outputLineNumber); let r = this.prefixSumComputer.getIndexOf(outputLineNumber - 1); diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 30c689fee8d..dd4aab6550c 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -6,7 +6,7 @@ import { IEventEmitter } from 'vs/base/common/eventEmitter'; import { IModelDecoration, EndOfLinePreference, IPosition } from 'vs/editor/common/editorCommon'; -import { ViewLineTokens } from 'vs/editor/common/core/viewLineToken'; +import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; @@ -31,7 +31,7 @@ export interface IViewModel extends IEventEmitter { getLineMaxColumn(lineNumber: number): number; getLineFirstNonWhitespaceColumn(lineNumber: number): number; getLineLastNonWhitespaceColumn(lineNumber: number): number; - getLineTokens(lineNumber: number): ViewLineTokens; + getLineTokens(lineNumber: number): ViewLineToken[]; getDecorationsViewportData(startLineNumber: number, endLineNumber: number): IDecorationsViewportData; getLineRenderLineNumber(lineNumber: number): string; /** diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 62eaaf2ee3c..7d00693222e 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -14,7 +14,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { ViewModelCursors } from 'vs/editor/common/viewModel/viewModelCursors'; import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations'; import { ViewModelDecoration, IDecorationsViewportData, IViewModel } from 'vs/editor/common/viewModel/viewModel'; -import { ViewLineTokens } from 'vs/editor/common/core/viewLineToken'; +import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; export interface ILinesCollection { setTabSize(newTabSize: number, emit: (evenType: string, payload: any) => void): boolean; @@ -30,7 +30,7 @@ export interface ILinesCollection { getOutputIndentGuide(outputLineNumber: number): number; getOutputLineMinColumn(outputLineNumber: number): number; getOutputLineMaxColumn(outputLineNumber: number): number; - getOutputLineTokens(outputLineNumber: number): ViewLineTokens; + getOutputLineTokens(outputLineNumber: number): ViewLineToken[]; convertOutputPositionToInputPosition(viewLineNumber: number, viewColumn: number): Position; convertInputPositionToOutputPosition(inputLineNumber: number, inputColumn: number): Position; setHiddenAreas(ranges: editorCommon.IRange[], emit: (evenType: string, payload: any) => void): void; @@ -445,7 +445,7 @@ export class ViewModel extends EventEmitter implements IViewModel { return result + 2; } - public getLineTokens(lineNumber: number): ViewLineTokens { + public getLineTokens(lineNumber: number): ViewLineToken[] { return this.lines.getOutputLineTokens(lineNumber); } diff --git a/src/vs/editor/contrib/find/common/findModel.ts b/src/vs/editor/contrib/find/common/findModel.ts index 337a5a0fbb1..b99cd6865cf 100644 --- a/src/vs/editor/contrib/find/common/findModel.ts +++ b/src/vs/editor/contrib/find/common/findModel.ts @@ -17,7 +17,6 @@ import { ReplaceAllCommand } from './replaceAllCommand'; import { Selection } from 'vs/editor/common/core/selection'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IKeybindings } from 'vs/platform/keybinding/common/keybinding'; -import { SearchParams } from 'vs/editor/common/model/textModelSearch'; export const ToggleCaseSensitiveKeybinding: IKeybindings = { primary: KeyMod.Alt | KeyCode.KEY_C, diff --git a/src/vs/editor/contrib/find/common/replacePattern.ts b/src/vs/editor/contrib/find/common/replacePattern.ts index 1a0bba7527e..125e363a768 100644 --- a/src/vs/editor/contrib/find/common/replacePattern.ts +++ b/src/vs/editor/contrib/find/common/replacePattern.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as strings from 'vs/base/common/strings'; -import { IPatternInfo } from 'vs/platform/search/common/search'; import { CharCode } from 'vs/base/common/charCode'; export class ReplacePattern { diff --git a/src/vs/editor/contrib/hover/browser/modesGlyphHover.ts b/src/vs/editor/contrib/hover/browser/modesGlyphHover.ts index 09e18e1c90d..3180bb7c383 100644 --- a/src/vs/editor/contrib/hover/browser/modesGlyphHover.ts +++ b/src/vs/editor/contrib/hover/browser/modesGlyphHover.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { IModelDecoration, IRange } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { HoverOperation, IHoverComputer } from './hoverOperation'; import { GlyphHoverWidget } from './hoverWidgets'; @@ -16,11 +15,10 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { TPromise } from 'vs/base/common/winjs.base'; import { IModeService } from 'vs/editor/common/services/modeService'; import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; +import { MarkedString } from 'vs/base/common/htmlContent'; export interface IHoverMessage { - value?: string; - range?: IRange; - className?: string; + value: MarkedString; } class MarginComputer implements IHoverComputer { @@ -44,19 +42,35 @@ class MarginComputer implements IHoverComputer { } public computeSync(): IHoverMessage[] { - var result: IHoverMessage[] = [], - lineDecorations = this._editor.getLineDecorations(this._lineNumber), - i: number, - len: number, - d: IModelDecoration; + const hasHoverContent = (contents: MarkedString | MarkedString[]) => { + return contents && (!Array.isArray(contents) || (contents).length > 0); + }; + const toHoverMessage = (contents: MarkedString): IHoverMessage => { + return { + value: contents + }; + }; - for (i = 0, len = lineDecorations.length; i < len; i++) { - d = lineDecorations[i]; + let lineDecorations = this._editor.getLineDecorations(this._lineNumber); - if (d.options.glyphMarginClassName && d.options.glyphMarginHoverMessage) { - result.push({ - value: d.options.glyphMarginHoverMessage - }); + let result: IHoverMessage[] = []; + for (let i = 0, len = lineDecorations.length; i < len; i++) { + let d = lineDecorations[i]; + + if (!d.options.glyphMarginClassName) { + continue; + } + + let hoverMessage = d.options.glyphMarginHoverMessage; + + if (!hasHoverContent(hoverMessage)) { + continue; + } + + if (Array.isArray(hoverMessage)) { + result = result.concat(hoverMessage.map(toHoverMessage)); + } else { + result.push(toHoverMessage(hoverMessage)); } } diff --git a/src/vs/editor/contrib/quickFix/browser/lightBulbWidget.css b/src/vs/editor/contrib/quickFix/browser/lightBulbWidget.css index f94bfa6e862..dbece1317f4 100644 --- a/src/vs/editor/contrib/quickFix/browser/lightBulbWidget.css +++ b/src/vs/editor/contrib/quickFix/browser/lightBulbWidget.css @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - .monaco-editor .lightbulb-glyph { display: flex; align-items: center; @@ -12,6 +11,11 @@ width: 16px; } +.monaco-editor .lightbulb-glyph.hidden { + display: none; + visibility: hidden; +} + .monaco-editor .lightbulb-glyph:hover { cursor: pointer; } diff --git a/src/vs/editor/contrib/smartSelect/test/common/tokenSelectionSupport.test.ts b/src/vs/editor/contrib/smartSelect/test/common/tokenSelectionSupport.test.ts index 7e7ad380507..f0b02563de4 100644 --- a/src/vs/editor/contrib/smartSelect/test/common/tokenSelectionSupport.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/common/tokenSelectionSupport.test.ts @@ -68,7 +68,7 @@ suite('TokenSelectionSupport', () => { let mode: MockJSMode = null; setup(() => { - modelService = new ModelServiceImpl(null, new TestConfigurationService(), null); + modelService = new ModelServiceImpl(null, new TestConfigurationService()); tokenSelectionSupport = new TokenSelectionSupport(modelService); mode = new MockJSMode(); }); diff --git a/src/vs/editor/contrib/suggest/browser/suggestController.ts b/src/vs/editor/contrib/suggest/browser/suggestController.ts index 1b9332f32aa..5cbf0f0c32a 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestController.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestController.ts @@ -105,9 +105,14 @@ export class SuggestController implements IEditorContribution { // Wire up logic to accept a suggestion on certain characters const autoAcceptOracle = new AcceptOnCharacterOracle(editor, this.widget, item => this.onDidSelectItem(item)); this.toDispose.push( + autoAcceptOracle, this.model.onDidCancel(autoAcceptOracle.reset, autoAcceptOracle), this.model.onDidTrigger(autoAcceptOracle.reset, autoAcceptOracle), - autoAcceptOracle + this.model.onDidSuggest(e => { + if (e.completionModel.items.length === 0) { + autoAcceptOracle.reset(); + } + }) ); } 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 7304fc44d36..6143d956fdd 100644 --- a/src/vs/editor/contrib/suggest/test/common/suggestModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/common/suggestModel.test.ts @@ -18,7 +18,8 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; -import { ITelemetryService, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; function createMockEditor(model: Model): MockCodeEditor { const contextKeyService = new MockKeybindingService(); diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts index 906f40bf5d4..2abbab025a6 100644 --- a/src/vs/editor/test/browser/controller/imeTester.ts +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -124,7 +124,7 @@ function doCreateTest(strategy: TextAreaStrategy, description: string, inputStr: cursorOffset = off; cursorLength = len; handler.setCursorSelections(new Range(1, 1 + cursorOffset, 1, 1 + cursorOffset + cursorLength), []); - handler.writePlaceholderAndSelectTextAreaSync(); + handler.focusTextArea(); }; let updateModelAndPosition = (text: string, off: number, len: number) => { diff --git a/src/vs/editor/test/browser/standalone/simpleServices.test.ts b/src/vs/editor/test/browser/standalone/simpleServices.test.ts index d520bfff289..e69842d211f 100644 --- a/src/vs/editor/test/browser/standalone/simpleServices.test.ts +++ b/src/vs/editor/test/browser/standalone/simpleServices.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; -import { SimpleConfigurationService, SimpleMessageService, SimpleExtensionService, StandaloneKeybindingService, StandaloneCommandService } from 'vs/editor/browser/standalone/simpleServices'; +import { SimpleConfigurationService, SimpleMessageService, StandaloneKeybindingService, StandaloneCommandService } from 'vs/editor/browser/standalone/simpleServices'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { Keybinding, KeyCode } from 'vs/base/common/keyCodes'; @@ -32,9 +32,7 @@ suite('StandaloneKeybindingService', () => { let contextKeyService = new ContextKeyService(configurationService); - let extensionService = new SimpleExtensionService(); - - let commandService = new StandaloneCommandService(instantiationService, extensionService); + let commandService = new StandaloneCommandService(instantiationService); let messageService = new SimpleMessageService(); diff --git a/src/vs/editor/test/common/model/model.line.test.ts b/src/vs/editor/test/common/model/model.line.test.ts index 0210b17139e..49675d80ccd 100644 --- a/src/vs/editor/test/common/model/model.line.test.ts +++ b/src/vs/editor/test/common/model/model.line.test.ts @@ -9,15 +9,12 @@ import { LineTokens } from 'vs/editor/common/core/lineTokens'; import { ModelLine, ILineEdit, LineMarker, MarkersTracker } from 'vs/editor/common/model/modelLine'; import { MetadataConsts } from 'vs/editor/common/modes'; import { Position } from 'vs/editor/common/core/position'; +import { TokenMetadata } from 'vs/editor/common/model/tokensBinaryEncoding'; -function assertLineTokens(actual: LineTokens, expected: TestToken[]): void { - let inflatedActual = actual.inflate(); - assert.deepEqual(inflatedActual, expected.map((token) => { - return { - startIndex: token.startOffset, - type: 'mtk' + token.color - }; - }), 'Line tokens are equal'); +function assertLineTokens(_actual: LineTokens, _expected: TestToken[]): void { + let expected = TokenMetadata.inflateArr(TestToken.toTokens(_expected), _actual.getLineLength()); + let actual = _actual.inflate(); + assert.deepEqual(actual, expected); } const NO_TAB_SIZE = 0; diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index 35032616b4a..4f38ca260e6 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -293,18 +293,18 @@ suite('TextModelWithTokens regression tests', () => { let model = Model.createFromString('A model with\ntwo lines'); - assertViewLineTokens(model, 1, true, [new ViewLineToken(0, 'mtk1')]); - assertViewLineTokens(model, 2, true, [new ViewLineToken(0, 'mtk1')]); + assertViewLineTokens(model, 1, true, [new ViewLineToken(12, 'mtk1')]); + assertViewLineTokens(model, 2, true, [new ViewLineToken(9, 'mtk1')]); model.setMode(languageIdentifier1); - assertViewLineTokens(model, 1, true, [new ViewLineToken(0, 'mtk11')]); - assertViewLineTokens(model, 2, true, [new ViewLineToken(0, 'mtk12')]); + assertViewLineTokens(model, 1, true, [new ViewLineToken(12, 'mtk11')]); + assertViewLineTokens(model, 2, true, [new ViewLineToken(9, 'mtk12')]); model.setMode(languageIdentifier2); - assertViewLineTokens(model, 1, false, [new ViewLineToken(0, 'mtk1')]); - assertViewLineTokens(model, 2, false, [new ViewLineToken(0, 'mtk1')]); + assertViewLineTokens(model, 1, false, [new ViewLineToken(12, 'mtk1')]); + assertViewLineTokens(model, 2, false, [new ViewLineToken(9, 'mtk1')]); model.dispose(); registration1.dispose(); diff --git a/src/vs/editor/test/common/viewLayout/viewLineParts.test.ts b/src/vs/editor/test/common/viewLayout/viewLineParts.test.ts index e782cd87527..8057407ddeb 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineParts.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineParts.test.ts @@ -5,12 +5,11 @@ 'use strict'; import * as assert from 'assert'; -import { DecorationSegment, LineDecorationsNormalizer, createLineParts } from 'vs/editor/common/viewLayout/viewLineParts'; +import { DecorationSegment, LineDecorationsNormalizer, Decoration } from 'vs/editor/common/viewLayout/viewLineParts'; import { Range } from 'vs/editor/common/core/range'; -import { RenderLineInput, renderLine } from 'vs/editor/common/viewLayout/viewLineRenderer'; -import { ViewLineToken, ViewLineTokens } from 'vs/editor/common/core/viewLineToken'; +import { RenderLineInput, renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer'; +import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; import { InlineDecoration } from 'vs/editor/common/viewModel/viewModel'; -import { LineParts } from 'vs/editor/common/core/lineParts'; suite('Editor ViewLayout - ViewLineParts', () => { @@ -20,9 +19,9 @@ suite('Editor ViewLayout - ViewLineParts', () => { test('Bug 9827:Overlapping inline decorations can cause wrong inline class to be applied', () => { - var result = LineDecorationsNormalizer.normalize(1, 1, [ - newDecoration(1, 1, 1, 11, 'c1'), - newDecoration(1, 3, 1, 4, 'c2') + var result = LineDecorationsNormalizer.normalize([ + new Decoration(1, 11, 'c1'), + new Decoration(3, 4, 'c2') ]); assert.deepEqual(result, [ @@ -34,9 +33,9 @@ suite('Editor ViewLayout - ViewLineParts', () => { test('issue #3462: no whitespace shown at the end of a decorated line', () => { - var result = LineDecorationsNormalizer.normalize(3, 1, [ - newDecoration(3, 15, 3, 21, 'vs-whitespace'), - newDecoration(3, 20, 3, 21, 'inline-folded'), + var result = LineDecorationsNormalizer.normalize([ + new Decoration(15, 21, 'vs-whitespace'), + new Decoration(20, 21, 'inline-folded'), ]); assert.deepEqual(result, [ @@ -47,128 +46,149 @@ suite('Editor ViewLayout - ViewLineParts', () => { test('issue #3661: Link decoration bleeds to next line when wrapping', () => { - var result = LineDecorationsNormalizer.normalize(3, 12, [ + let result = Decoration.filter([ newDecoration(2, 12, 3, 30, 'detected-link') - ]); + ], 3, 12, 500); assert.deepEqual(result, [ - new DecorationSegment(11, 28, 'detected-link'), + new Decoration(12, 30, 'detected-link'), ]); }); - function testCreateLineParts(lineContent: string, tokens: ViewLineToken[], fauxIndentLength: number, renderWhitespace: 'none' | 'boundary' | 'all', expected: ViewLineToken[]): void { - let lineParts = createLineParts(1, 1, lineContent, 4, new ViewLineTokens(tokens, fauxIndentLength, lineContent.length), [], renderWhitespace); - let actual = lineParts.parts; + function testCreateLineParts(lineContent: string, tokens: ViewLineToken[], fauxIndentLength: number, renderWhitespace: 'none' | 'boundary' | 'all', expected: string): void { + let actual = renderViewLine(new RenderLineInput( + lineContent, + fauxIndentLength, + tokens, + [], + 4, + 10, + -1, + renderWhitespace, + false + )); - assert.deepEqual(actual, expected); + assert.deepEqual(actual.output.split(/> { testCreateLineParts( 'Hello world!', [ - new ViewLineToken(0, '') + new ViewLineToken(12, '') ], 0, 'none', [ - new ViewLineToken(0, '') - ] + '', + 'Hello world!', + '', + ].join('') ); }); test('createLineParts simple two tokens', () => { testCreateLineParts( 'Hello world!', [ - new ViewLineToken(0, 'a'), - new ViewLineToken(6, 'b') + new ViewLineToken(6, 'a'), + new ViewLineToken(12, 'b') ], 0, 'none', [ - new ViewLineToken(0, 'a'), - new ViewLineToken(6, 'b') - ] + '', + 'Hello ', + 'world!', + '', + ].join('') ); }); test('createLineParts render whitespace - 4 leading spaces', () => { testCreateLineParts( ' Hello world! ', [ - new ViewLineToken(0, ''), - new ViewLineToken(4, 'a'), - new ViewLineToken(6, 'b') + new ViewLineToken(4, ''), + new ViewLineToken(6, 'a'), + new ViewLineToken(20, 'b') ], 0, 'boundary', [ - new ViewLineToken(0, ' vs-whitespace'), - new ViewLineToken(4, 'a'), - new ViewLineToken(6, 'b'), - new ViewLineToken(16, 'b vs-whitespace') - ] + '', + '····', + 'He', + 'llo world!', + '····', + '', + ].join('') ); }); test('createLineParts render whitespace - 8 leading spaces', () => { testCreateLineParts( ' Hello world! ', [ - new ViewLineToken(0, ''), - new ViewLineToken(8, 'a'), - new ViewLineToken(10, 'b') + new ViewLineToken(8, ''), + new ViewLineToken(10, 'a'), + new ViewLineToken(28, 'b') ], 0, 'boundary', [ - new ViewLineToken(0, ' vs-whitespace'), - new ViewLineToken(4, ' vs-whitespace'), - new ViewLineToken(8, 'a'), - new ViewLineToken(10, 'b'), - new ViewLineToken(20, 'b vs-whitespace'), - new ViewLineToken(24, 'b vs-whitespace'), - ] + '', + '····', + '····', + 'He', + 'llo world!', + '····', + '····', + '', + ].join('') ); }); test('createLineParts render whitespace - 2 leading tabs', () => { testCreateLineParts( '\t\tHello world!\t', [ - new ViewLineToken(0, ''), - new ViewLineToken(2, 'a'), - new ViewLineToken(4, 'b') + new ViewLineToken(2, ''), + new ViewLineToken(4, 'a'), + new ViewLineToken(15, 'b') ], 0, 'boundary', [ - new ViewLineToken(0, ' vs-whitespace'), - new ViewLineToken(1, ' vs-whitespace'), - new ViewLineToken(2, 'a'), - new ViewLineToken(4, 'b'), - new ViewLineToken(14, 'b vs-whitespace'), - ] + '', + '→   ', + '→   ', + 'He', + 'llo world!', + '→   ', + '', + ].join('') ); }); test('createLineParts render whitespace - mixed leading spaces and tabs', () => { testCreateLineParts( ' \t\t Hello world! \t \t \t ', [ - new ViewLineToken(0, ''), - new ViewLineToken(6, 'a'), - new ViewLineToken(8, 'b') + new ViewLineToken(6, ''), + new ViewLineToken(8, 'a'), + new ViewLineToken(31, 'b') ], 0, 'boundary', [ - new ViewLineToken(0, ' vs-whitespace'), - new ViewLineToken(3, ' vs-whitespace'), - new ViewLineToken(4, ' vs-whitespace'), - new ViewLineToken(6, 'a'), - new ViewLineToken(8, 'b'), - new ViewLineToken(18, 'b vs-whitespace'), - new ViewLineToken(20, 'b vs-whitespace'), - new ViewLineToken(23, 'b vs-whitespace'), - new ViewLineToken(27, 'b vs-whitespace'), - ] + '', + '··→ ', + '→   ', + '··', + 'He', + 'llo world!', + '·→', + '··→ ', + '···→', + '····', + '', + ].join('') ); }); @@ -176,22 +196,24 @@ suite('Editor ViewLayout - ViewLineParts', () => { testCreateLineParts( '\t\t Hello world! \t \t \t ', [ - new ViewLineToken(0, ''), - new ViewLineToken(4, 'a'), - new ViewLineToken(6, 'b') + new ViewLineToken(4, ''), + new ViewLineToken(6, 'a'), + new ViewLineToken(29, 'b') ], 2, 'boundary', [ - new ViewLineToken(0, ''), - new ViewLineToken(2, ' vs-whitespace'), - new ViewLineToken(4, 'a'), - new ViewLineToken(6, 'b'), - new ViewLineToken(16, 'b vs-whitespace'), - new ViewLineToken(18, 'b vs-whitespace'), - new ViewLineToken(21, 'b vs-whitespace'), - new ViewLineToken(25, 'b vs-whitespace'), - ] + '', + '        ', + '··', + 'He', + 'llo world!', + '·→', + '··→ ', + '···→', + '····', + '', + ].join('') ); }); @@ -199,21 +221,23 @@ suite('Editor ViewLayout - ViewLineParts', () => { testCreateLineParts( 'it it it it', [ - new ViewLineToken(0, ''), - new ViewLineToken(6, 'a'), - new ViewLineToken(7, 'b') + new ViewLineToken(6, ''), + new ViewLineToken(7, 'a'), + new ViewLineToken(13, 'b') ], 0, 'boundary', [ - new ViewLineToken(0, ''), - new ViewLineToken(2, ' vs-whitespace'), - new ViewLineToken(4, ''), - new ViewLineToken(6, 'a'), - new ViewLineToken(7, 'b'), - new ViewLineToken(9, 'b vs-whitespace'), - new ViewLineToken(11, 'b'), - ] + '', + 'it', + '··', + 'it', + ' ', + 'it', + '··', + 'it', + '', + ].join('') ); }); @@ -221,37 +245,41 @@ suite('Editor ViewLayout - ViewLineParts', () => { testCreateLineParts( ' Hello world!\t', [ - new ViewLineToken(0, ''), - new ViewLineToken(4, 'a'), - new ViewLineToken(6, 'b') + new ViewLineToken(4, ''), + new ViewLineToken(6, 'a'), + new ViewLineToken(14, 'b') ], 0, 'all', [ - new ViewLineToken(0, ' vs-whitespace'), - new ViewLineToken(1, ''), - new ViewLineToken(4, 'a'), - new ViewLineToken(6, 'b vs-whitespace'), - new ViewLineToken(7, 'b'), - new ViewLineToken(13, 'b vs-whitespace'), - ] + '', + '·', + 'Hel', + 'lo', + '·', + 'world!', + '→  ', + '', + ].join('') ); }); test('createLineParts can handle unsorted inline decorations', () => { - let lineParts = createLineParts( - 1, - 1, + let actual = renderViewLine(new RenderLineInput( 'Hello world', - 4, - new ViewLineTokens([new ViewLineToken(0, '')], 0, 'Hello world'.length), + 0, + [new ViewLineToken(11, '')], [ - new InlineDecoration(new Range(1, 5, 1, 7), 'a'), - new InlineDecoration(new Range(1, 1, 1, 3), 'b'), - new InlineDecoration(new Range(1, 2, 1, 8), 'c'), + new Decoration(5, 7, 'a'), + new Decoration(1, 3, 'b'), + new Decoration(2, 8, 'c'), ], - 'none' - ); + 4, + 10, + -1, + 'none', + false + )); // 01234567890 // Hello world @@ -259,78 +287,80 @@ suite('Editor ViewLayout - ViewLineParts', () => { // bb--------- // -cccccc---- - assert.deepEqual(lineParts.parts, [ - new ViewLineToken(0, ' b'), - new ViewLineToken(1, ' b c'), - new ViewLineToken(2, ' c'), - new ViewLineToken(4, ' a c'), - new ViewLineToken(6, ' c'), - new ViewLineToken(7, ''), - ]); + assert.deepEqual(actual.output, [ + '', + 'H', + 'e', + 'll', + '', + 'w', + 'orld', + '', + ].join('')); }); test('ViewLineParts', () => { - assert.deepEqual(LineDecorationsNormalizer.normalize(1, 1, [ - newDecoration(1, 1, 1, 2, 'c1'), - newDecoration(1, 3, 1, 4, 'c2') + assert.deepEqual(LineDecorationsNormalizer.normalize([ + new Decoration(1, 2, 'c1'), + new Decoration(3, 4, 'c2') ]), [ new DecorationSegment(0, 0, 'c1'), new DecorationSegment(2, 2, 'c2') ]); - assert.deepEqual(LineDecorationsNormalizer.normalize(1, 1, [ - newDecoration(1, 1, 1, 3, 'c1'), - newDecoration(1, 3, 1, 4, 'c2') + assert.deepEqual(LineDecorationsNormalizer.normalize([ + new Decoration(1, 3, 'c1'), + new Decoration(3, 4, 'c2') ]), [ new DecorationSegment(0, 1, 'c1'), new DecorationSegment(2, 2, 'c2') ]); - assert.deepEqual(LineDecorationsNormalizer.normalize(1, 1, [ - newDecoration(1, 1, 1, 4, 'c1'), - newDecoration(1, 3, 1, 4, 'c2') + assert.deepEqual(LineDecorationsNormalizer.normalize([ + new Decoration(1, 4, 'c1'), + new Decoration(3, 4, 'c2') ]), [ new DecorationSegment(0, 1, 'c1'), new DecorationSegment(2, 2, 'c1 c2') ]); - assert.deepEqual(LineDecorationsNormalizer.normalize(1, 1, [ - newDecoration(1, 1, 1, 4, 'c1'), - newDecoration(1, 1, 1, 4, 'c1*'), - newDecoration(1, 3, 1, 4, 'c2') + assert.deepEqual(LineDecorationsNormalizer.normalize([ + new Decoration(1, 4, 'c1'), + new Decoration(1, 4, 'c1*'), + new Decoration(3, 4, 'c2') ]), [ new DecorationSegment(0, 1, 'c1 c1*'), new DecorationSegment(2, 2, 'c1 c1* c2') ]); - assert.deepEqual(LineDecorationsNormalizer.normalize(1, 1, [ - newDecoration(1, 1, 1, 4, 'c1'), - newDecoration(1, 1, 1, 4, 'c1*'), - newDecoration(1, 1, 1, 4, 'c1**'), - newDecoration(1, 3, 1, 4, 'c2') + assert.deepEqual(LineDecorationsNormalizer.normalize([ + new Decoration(1, 4, 'c1'), + new Decoration(1, 4, 'c1*'), + new Decoration(1, 4, 'c1**'), + new Decoration(3, 4, 'c2') ]), [ new DecorationSegment(0, 1, 'c1 c1* c1**'), new DecorationSegment(2, 2, 'c1 c1* c1** c2') ]); - assert.deepEqual(LineDecorationsNormalizer.normalize(1, 1, [ - newDecoration(1, 1, 1, 4, 'c1'), - newDecoration(1, 1, 1, 4, 'c1*'), - newDecoration(1, 1, 1, 4, 'c1**'), - newDecoration(1, 3, 1, 4, 'c2'), - newDecoration(1, 3, 1, 4, 'c2*') + assert.deepEqual(LineDecorationsNormalizer.normalize([ + new Decoration(1, 4, 'c1'), + new Decoration(1, 4, 'c1*'), + new Decoration(1, 4, 'c1**'), + new Decoration(3, 4, 'c2'), + new Decoration(3, 4, 'c2*') ]), [ new DecorationSegment(0, 1, 'c1 c1* c1**'), new DecorationSegment(2, 2, 'c1 c1* c1** c2 c2*') ]); - assert.deepEqual(LineDecorationsNormalizer.normalize(1, 1, [ - newDecoration(1, 1, 1, 4, 'c1'), - newDecoration(1, 1, 1, 4, 'c1*'), - newDecoration(1, 1, 1, 4, 'c1**'), - newDecoration(1, 3, 1, 4, 'c2'), - newDecoration(1, 3, 1, 5, 'c2*') + assert.deepEqual(LineDecorationsNormalizer.normalize([ + new Decoration(1, 4, 'c1'), + new Decoration(1, 4, 'c1*'), + new Decoration(1, 4, 'c1**'), + new Decoration(3, 4, 'c2'), + new Decoration(3, 5, 'c2*') ]), [ new DecorationSegment(0, 1, 'c1 c1* c1**'), new DecorationSegment(2, 2, 'c1 c1* c1** c2 c2*'), @@ -339,7 +369,17 @@ suite('Editor ViewLayout - ViewLineParts', () => { }); function createTestGetColumnOfLinePartOffset(lineContent: string, tabSize: number, parts: ViewLineToken[]): (partIndex: number, partLength: number, offset: number, expected: number) => void { - let renderLineOutput = renderLine(new RenderLineInput(lineContent, tabSize, 10, -1, 'none', false, new LineParts(parts, lineContent.length + 1))); + let renderLineOutput = renderViewLine(new RenderLineInput( + lineContent, + 0, + parts, + [], + tabSize, + 10, + -1, + 'none', + false + )); return (partIndex: number, partLength: number, offset: number, expected: number) => { let charOffset = renderLineOutput.characterMapping.partDataToCharOffset(partIndex, partLength, offset); @@ -353,7 +393,7 @@ suite('Editor ViewLayout - ViewLineParts', () => { 'hello world', 4, [ - new ViewLineToken(0, 'aToken') + new ViewLineToken(11, 'aToken') ] ); testGetColumnOfLinePartOffset(0, 11, 0, 1); @@ -375,12 +415,12 @@ suite('Editor ViewLayout - ViewLineParts', () => { 'var x = 3;', 4, [ - new ViewLineToken(0, 'meta type js storage var expr'), - new ViewLineToken(3, 'meta js var expr'), - new ViewLineToken(4, 'meta js var expr var-single-variable variable'), - new ViewLineToken(5, 'meta js var expr var-single-variable'), - new ViewLineToken(8, 'meta js var expr var-single-variable constant numeric'), - new ViewLineToken(9, ''), + new ViewLineToken(3, 'meta type js storage var expr'), + new ViewLineToken(4, 'meta js var expr'), + new ViewLineToken(5, 'meta js var expr var-single-variable variable'), + new ViewLineToken(8, 'meta js var expr var-single-variable'), + new ViewLineToken(9, 'meta js var expr var-single-variable constant numeric'), + new ViewLineToken(10, ''), ] ); testGetColumnOfLinePartOffset(0, 3, 0, 1); @@ -406,7 +446,7 @@ suite('Editor ViewLayout - ViewLineParts', () => { '\t', 6, [ - new ViewLineToken(0, 'vs-whitespace') + new ViewLineToken(1, 'vs-whitespace') ] ); testGetColumnOfLinePartOffset(0, 6, 0, 1); @@ -423,8 +463,8 @@ suite('Editor ViewLayout - ViewLineParts', () => { '\tfunction', 4, [ - new ViewLineToken(0, ''), - new ViewLineToken(1, 'meta type js function storage'), + new ViewLineToken(1, ''), + new ViewLineToken(9, 'meta type js function storage'), ] ); testGetColumnOfLinePartOffset(0, 4, 0, 1); @@ -448,8 +488,8 @@ suite('Editor ViewLayout - ViewLineParts', () => { '\t\tfunction', 4, [ - new ViewLineToken(0, ''), - new ViewLineToken(2, 'meta type js function storage'), + new ViewLineToken(2, ''), + new ViewLineToken(10, 'meta type js function storage'), ] ); testGetColumnOfLinePartOffset(0, 8, 0, 1); diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 65333a15943..fa0c12db006 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -5,26 +5,27 @@ 'use strict'; import * as assert from 'assert'; -import { renderLine, RenderLineInput, CharacterMapping } from 'vs/editor/common/viewLayout/viewLineRenderer'; +import { renderViewLine, RenderLineInput, CharacterMapping } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; import { CharCode } from 'vs/base/common/charCode'; -import { LineParts } from 'vs/editor/common/core/lineParts'; suite('viewLineRenderer.renderLine', () => { - function createPart(startIndex: number, type: string): ViewLineToken { - return new ViewLineToken(startIndex, type); + function createPart(endIndex: number, type: string): ViewLineToken { + return new ViewLineToken(endIndex, type); } function assertCharacterReplacement(lineContent: string, tabSize: number, expected: string, expectedCharOffsetInPart: number[][]): void { - let _actual = renderLine(new RenderLineInput( + let _actual = renderViewLine(new RenderLineInput( lineContent, + 0, + [new ViewLineToken(lineContent.length, '')], + [], tabSize, 0, -1, 'none', - false, - new LineParts([createPart(0, '')], lineContent.length + 1) + false )); assert.equal(_actual.output, '' + expected + ''); @@ -59,14 +60,16 @@ suite('viewLineRenderer.renderLine', () => { }); function assertParts(lineContent: string, tabSize: number, parts: ViewLineToken[], expected: string, expectedCharOffsetInPart: number[][]): void { - let _actual = renderLine(new RenderLineInput( + let _actual = renderViewLine(new RenderLineInput( lineContent, + 0, + parts, + [], tabSize, 0, -1, 'none', - false, - new LineParts(parts, lineContent.length + 1) + false )); assert.equal(_actual.output, '' + expected + ''); @@ -78,42 +81,41 @@ suite('viewLineRenderer.renderLine', () => { }); test('uses part type', () => { - assertParts('x', 4, [createPart(0, 'y')], 'x', [[0, 1]]); - assertParts('x', 4, [createPart(0, 'aAbBzZ0123456789-cC')], 'x', [[0, 1]]); - assertParts('x', 4, [createPart(0, ' ')], 'x', [[0, 1]]); + assertParts('x', 4, [createPart(1, 'y')], 'x', [[0, 1]]); + assertParts('x', 4, [createPart(1, 'aAbBzZ0123456789-cC')], 'x', [[0, 1]]); + assertParts('x', 4, [createPart(1, ' ')], 'x', [[0, 1]]); }); test('two parts', () => { - assertParts('xy', 4, [createPart(0, 'a'), createPart(1, 'b')], 'xy', [[0], [0, 1]]); - assertParts('xyz', 4, [createPart(0, 'a'), createPart(1, 'b')], 'xyz', [[0], [0, 1, 2]]); - assertParts('xyz', 4, [createPart(0, 'a'), createPart(2, 'b')], 'xyz', [[0, 1], [0, 1]]); + assertParts('xy', 4, [createPart(1, 'a'), createPart(2, 'b')], 'xy', [[0], [0, 1]]); + assertParts('xyz', 4, [createPart(1, 'a'), createPart(3, 'b')], 'xyz', [[0], [0, 1, 2]]); + assertParts('xyz', 4, [createPart(2, 'a'), createPart(3, 'b')], 'xyz', [[0, 1], [0, 1]]); }); test('overflow', () => { - let _actual = renderLine(new RenderLineInput( + let _actual = renderViewLine(new RenderLineInput( 'Hello world!', + 0, + [ + createPart(1, '0'), + createPart(2, '1'), + createPart(3, '2'), + createPart(4, '3'), + createPart(5, '4'), + createPart(6, '5'), + createPart(7, '6'), + createPart(8, '7'), + createPart(9, '8'), + createPart(10, '9'), + createPart(11, '10'), + createPart(12, '11'), + ], + [], 4, 10, 6, 'boundary', - false, - new LineParts( - [ - createPart(0, '0'), - createPart(1, '1'), - createPart(2, '2'), - createPart(3, '3'), - createPart(4, '4'), - createPart(5, '5'), - createPart(6, '6'), - createPart(7, '7'), - createPart(8, '8'), - createPart(9, '9'), - createPart(10, '10'), - createPart(11, '11'), - ], - 'Hello world!'.length + 1 - ) + false )); let expectedOutput = [ @@ -122,7 +124,8 @@ suite('viewLineRenderer.renderLine', () => { 'l', 'l', 'o', - ' …' + ' ', + '' ].join(''); assert.equal(_actual.output, '' + expectedOutput + ''); @@ -132,28 +135,29 @@ suite('viewLineRenderer.renderLine', () => { [0], [0], [0], - [1], + [0, 1], ]); }); test('typical line', () => { let lineText = '\t export class Game { // http://test.com '; let lineParts = [ - createPart(0, 'block meta ts vs-whitespace'), - createPart(5, 'block declaration meta modifier object storage ts'), - createPart(11, 'block declaration meta object ts'), - createPart(12, 'block declaration meta object storage type ts'), - createPart(17, 'block declaration meta object ts'), - createPart(18, 'block class declaration entity meta name object ts'), - createPart(22, 'block declaration meta object ts'), - createPart(23, 'delimiter curly typescript'), - createPart(24, 'block body declaration meta object ts'), - createPart(25, 'block body comment declaration line meta object ts'), - createPart(28, 'block body comment declaration line meta object ts detected-link'), - createPart(43, 'block body comment declaration line meta object ts vs-whitespace'), + createPart(5, 'block meta ts'), + createPart(11, 'block declaration meta modifier object storage ts'), + createPart(12, 'block declaration meta object ts'), + createPart(17, 'block declaration meta object storage type ts'), + createPart(18, 'block declaration meta object ts'), + createPart(22, 'block class declaration entity meta name object ts'), + createPart(23, 'block declaration meta object ts'), + createPart(24, 'delimiter curly typescript'), + createPart(25, 'block body declaration meta object ts'), + createPart(28, 'block body comment declaration line meta object ts'), + createPart(43, 'block body comment declaration line meta object ts detected-link'), + createPart(48, 'block body comment declaration line meta object ts'), ]; let expectedOutput = [ - '→   ····', + '→   ', + '····', 'export', ' ', 'class', @@ -164,10 +168,12 @@ suite('viewLineRenderer.renderLine', () => { ' ', '// ', 'http://test.com', - '·····' + '··', + '···' ].join(''); let expectedOffsetsArr = [ - [0, 4, 5, 6, 7], + [0], + [0, 1, 2, 3], [0, 1, 2, 3, 4, 5], [0], [0, 1, 2, 3, 4], @@ -178,17 +184,20 @@ suite('viewLineRenderer.renderLine', () => { [0], [0, 1, 2], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], - [0, 1, 2, 3, 4, 5], + [0, 1], + [0, 1, 2, 3], ]; - let _actual = renderLine(new RenderLineInput( + let _actual = renderViewLine(new RenderLineInput( lineText, + 0, + lineParts, + [], 4, 10, -1, 'boundary', - false, - new LineParts(lineParts, lineText.length + 1) + false )); assert.equal(_actual.output, '' + expectedOutput + ''); @@ -199,16 +208,16 @@ suite('viewLineRenderer.renderLine', () => { let lineText = '\t\t\tcursorStyle:\t\t\t\t\t\t(prevOpts.cursorStyle !== newOpts.cursorStyle),'; let lineParts = [ - createPart(0, 'block body decl declaration meta method object ts'), // 3 chars - createPart(3, 'block body decl declaration member meta method object ts'), // 12 chars - createPart(15, 'block body decl declaration member meta method object ts'), // 6 chars - createPart(21, 'delimiter paren typescript'), // 1 char - createPart(22, 'block body decl declaration member meta method object ts'), // 21 chars - createPart(43, 'block body comparison decl declaration keyword member meta method object operator ts'), // 2 chars - createPart(45, 'block body comparison decl declaration keyword member meta method object operator ts'), // 1 char - createPart(46, 'block body decl declaration member meta method object ts'), // 20 chars - createPart(66, 'delimiter paren typescript'), // 1 char - createPart(67, 'block body decl declaration meta method object ts'), // 2 chars + createPart(3, 'block body decl declaration meta method object ts'), // 3 chars + createPart(15, 'block body decl declaration member meta method object ts'), // 12 chars + createPart(21, 'block body decl declaration member meta method object ts'), // 6 chars + createPart(22, 'delimiter paren typescript'), // 1 char + createPart(43, 'block body decl declaration member meta method object ts'), // 21 chars + createPart(45, 'block body comparison decl declaration keyword member meta method object operator ts'), // 2 chars + createPart(46, 'block body comparison decl declaration keyword member meta method object operator ts'), // 1 char + createPart(66, 'block body decl declaration member meta method object ts'), // 20 chars + createPart(67, 'delimiter paren typescript'), // 1 char + createPart(68, 'block body decl declaration meta method object ts'), // 2 chars ]; let expectedOutput = [ '            ', @@ -235,14 +244,16 @@ suite('viewLineRenderer.renderLine', () => { [0, 1] // 2 chars ]; - let _actual = renderLine(new RenderLineInput( + let _actual = renderViewLine(new RenderLineInput( lineText, + 0, + lineParts, + [], 4, 10, -1, 'none', - false, - new LineParts(lineParts, lineText.length + 1) + false )); assert.equal(_actual.output, '' + expectedOutput + ''); @@ -253,16 +264,16 @@ suite('viewLineRenderer.renderLine', () => { let lineText = ' \t\t\tcursorStyle:\t\t\t\t\t\t(prevOpts.cursorStyle !== newOpts.cursorStyle),'; let lineParts = [ - createPart(0, 'block body decl declaration meta method object ts'), // 4 chars - createPart(4, 'block body decl declaration member meta method object ts'), // 12 chars - createPart(16, 'block body decl declaration member meta method object ts'), // 6 chars - createPart(22, 'delimiter paren typescript'), // 1 char - createPart(23, 'block body decl declaration member meta method object ts'), // 21 chars - createPart(44, 'block body comparison decl declaration keyword member meta method object operator ts'), // 2 chars - createPart(46, 'block body comparison decl declaration keyword member meta method object operator ts'), // 1 char - createPart(47, 'block body decl declaration member meta method object ts'), // 20 chars - createPart(67, 'delimiter paren typescript'), // 1 char - createPart(68, 'block body decl declaration meta method object ts'), // 2 chars + createPart(4, 'block body decl declaration meta method object ts'), // 4 chars + createPart(16, 'block body decl declaration member meta method object ts'), // 12 chars + createPart(22, 'block body decl declaration member meta method object ts'), // 6 chars + createPart(23, 'delimiter paren typescript'), // 1 char + createPart(44, 'block body decl declaration member meta method object ts'), // 21 chars + createPart(46, 'block body comparison decl declaration keyword member meta method object operator ts'), // 2 chars + createPart(47, 'block body comparison decl declaration keyword member meta method object operator ts'), // 1 char + createPart(67, 'block body decl declaration member meta method object ts'), // 20 chars + createPart(68, 'delimiter paren typescript'), // 1 char + createPart(69, 'block body decl declaration meta method object ts'), // 2 chars ]; let expectedOutput = [ '            ', @@ -289,14 +300,16 @@ suite('viewLineRenderer.renderLine', () => { [0, 1] // 2 chars ]; - let _actual = renderLine(new RenderLineInput( + let _actual = renderViewLine(new RenderLineInput( lineText, + 0, + lineParts, + [], 4, 10, -1, 'none', - false, - new LineParts(lineParts, lineText.length + 1) + false )); assert.equal(_actual.output, '' + expectedOutput + ''); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index b236973e1a6..92a8a5dc3c7 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1154,10 +1154,9 @@ declare module monaco.editor { */ roundedSelection?: boolean; /** - * Theme to be used for rendering. Consists of two parts, the UI theme and the syntax theme, - * separated by a space. - * The current available UI themes are: 'vs' (default), 'vs-dark', 'hc-black' - * The syntax themes are contributed. The default is 'default-theme' + * Theme to be used for rendering. + * The current out-of-the-box available themes are: 'vs' (default), 'vs-dark', 'hc-black'. + * You can create custom themes via `monaco.editor.defineTheme`. */ theme?: string; /** @@ -1613,6 +1612,10 @@ declare module monaco.editor { * CSS class name describing the decoration. */ className?: string; + /** + * Message to be rendered when hovering over the glyph margin decoration. + */ + glyphMarginHoverMessage?: MarkedString | MarkedString[]; /** * Array of MarkedString to render as the decoration message. */ diff --git a/src/vs/platform/actions/common/menu.ts b/src/vs/platform/actions/common/menu.ts new file mode 100644 index 00000000000..621eee3a73a --- /dev/null +++ b/src/vs/platform/actions/common/menu.ts @@ -0,0 +1,139 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import Event, { Emitter } from 'vs/base/common/event'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { MenuId, MenuRegistry, MenuItemAction, IMenu, IMenuItem } from 'vs/platform/actions/common/actions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; + +type MenuItemGroup = [string, IMenuItem[]]; + +export class Menu implements IMenu { + + private _menuGroups: MenuItemGroup[] = []; + private _disposables: IDisposable[] = []; + private _onDidChange = new Emitter(); + + constructor( + id: MenuId, + startupSignal: TPromise, + @ICommandService private _commandService: ICommandService, + @IContextKeyService private _contextKeyService: IContextKeyService + ) { + startupSignal.then(_ => { + const menuItems = MenuRegistry.getMenuItems(id); + const keysFilter = new Set(); + + let group: MenuItemGroup; + menuItems.sort(Menu._compareMenuItems); + + for (let item of menuItems) { + // group by groupId + const groupName = item.group; + if (!group || group[0] !== groupName) { + group = [groupName, []]; + this._menuGroups.push(group); + } + group[1].push(item); + + // keep keys for eventing + Menu._fillInKbExprKeys(item.when, keysFilter); + } + + // subscribe to context changes + this._disposables.push(this._contextKeyService.onDidChangeContext(keys => { + for (let k of keys) { + if (keysFilter.has(k)) { + this._onDidChange.fire(); + return; + } + } + })); + + this._onDidChange.fire(this); + }); + } + + dispose() { + this._disposables = dispose(this._disposables); + this._onDidChange.dispose(); + } + + get onDidChange(): Event { + return this._onDidChange.event; + } + + getActions(arg?: any): [string, MenuItemAction[]][] { + const result: [string, MenuItemAction[]][] = []; + for (let group of this._menuGroups) { + const [id, items] = group; + const activeActions: MenuItemAction[] = []; + for (const item of items) { + if (this._contextKeyService.contextMatchesRules(item.when)) { + const action = new MenuItemAction(item.command, item.alt, arg, this._commandService); + action.order = item.order; //TODO@Ben order is menu item property, not an action property + activeActions.push(action); + } + } + if (activeActions.length > 0) { + result.push([id, activeActions]); + } + } + return result; + } + + private static _fillInKbExprKeys(exp: ContextKeyExpr, set: Set): void { + if (exp) { + for (let key of exp.keys()) { + set.add(key); + } + } + } + + private static _compareMenuItems(a: IMenuItem, b: IMenuItem): number { + + let aGroup = a.group; + let bGroup = b.group; + + if (aGroup !== bGroup) { + + // Falsy groups come last + if (!aGroup) { + return 1; + } else if (!bGroup) { + return -1; + } + + // 'navigation' group comes first + if (aGroup === 'navigation') { + return -1; + } else if (bGroup === 'navigation') { + return 1; + } + + // lexical sort for groups + let value = aGroup.localeCompare(bGroup); + if (value !== 0) { + return value; + } + } + + // sort on priority - default is 0 + let aPrio = a.order || 0; + let bPrio = b.order || 0; + if (aPrio < bPrio) { + return -1; + } else if (aPrio > bPrio) { + return 1; + } + + // sort on titles + return a.command.title.localeCompare(b.command.title); + } +} diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index 729428af7ba..87d79822189 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -5,11 +5,10 @@ 'use strict'; -import Event, { Emitter } from 'vs/base/common/event'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/collections'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { MenuId, MenuRegistry, ICommandAction, MenuItemAction, IMenu, IMenuItem, IMenuService } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { MenuId, MenuRegistry, ICommandAction, IMenu, IMenuService } from 'vs/platform/actions/common/actions'; +import { Menu } from 'vs/platform/actions/common/menu'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -25,137 +24,10 @@ export class MenuService implements IMenuService { } createMenu(id: MenuId, contextKeyService: IContextKeyService): IMenu { - return new Menu(id, this._commandService, contextKeyService, this._extensionService); + return new Menu(id, this._extensionService.onReady(), this._commandService, contextKeyService); } getCommandActions(): ICommandAction[] { return values(MenuRegistry.commands); } } - -type MenuItemGroup = [string, IMenuItem[]]; - -class Menu implements IMenu { - - private _menuGroups: MenuItemGroup[] = []; - private _disposables: IDisposable[] = []; - private _onDidChange = new Emitter(); - - constructor( - id: MenuId, - @ICommandService private _commandService: ICommandService, - @IContextKeyService private _contextKeyService: IContextKeyService, - @IExtensionService private _extensionService: IExtensionService - ) { - this._extensionService.onReady().then(_ => { - - const menuItems = MenuRegistry.getMenuItems(id); - const keysFilter = new Set(); - - let group: MenuItemGroup; - menuItems.sort(Menu._compareMenuItems); - - for (let item of menuItems) { - // group by groupId - const groupName = item.group; - if (!group || group[0] !== groupName) { - group = [groupName, []]; - this._menuGroups.push(group); - } - group[1].push(item); - - // keep keys for eventing - Menu._fillInKbExprKeys(item.when, keysFilter); - } - - // subscribe to context changes - this._disposables.push(this._contextKeyService.onDidChangeContext(keys => { - for (let k of keys) { - if (keysFilter.has(k)) { - this._onDidChange.fire(); - return; - } - } - })); - - this._onDidChange.fire(this); - }); - } - - dispose() { - this._disposables = dispose(this._disposables); - this._onDidChange.dispose(); - } - - get onDidChange(): Event { - return this._onDidChange.event; - } - - getActions(arg?: any): [string, MenuItemAction[]][] { - const result: [string, MenuItemAction[]][] = []; - for (let group of this._menuGroups) { - const [id, items] = group; - const activeActions: MenuItemAction[] = []; - for (const item of items) { - if (this._contextKeyService.contextMatchesRules(item.when)) { - const action = new MenuItemAction(item.command, item.alt, arg, this._commandService); - action.order = item.order; //TODO@Ben order is menu item property, not an action property - activeActions.push(action); - } - } - if (activeActions.length > 0) { - result.push([id, activeActions]); - } - } - return result; - } - - private static _fillInKbExprKeys(exp: ContextKeyExpr, set: Set): void { - if (exp) { - for (let key of exp.keys()) { - set.add(key); - } - } - } - - private static _compareMenuItems(a: IMenuItem, b: IMenuItem): number { - - let aGroup = a.group; - let bGroup = b.group; - - if (aGroup !== bGroup) { - - // Falsy groups come last - if (!aGroup) { - return 1; - } else if (!bGroup) { - return -1; - } - - // 'navigation' group comes first - if (aGroup === 'navigation') { - return -1; - } else if (bGroup === 'navigation') { - return 1; - } - - // lexical sort for groups - let value = aGroup.localeCompare(bGroup); - if (value !== 0) { - return value; - } - } - - // sort on priority - default is 0 - let aPrio = a.order || 0; - let bPrio = b.order || 0; - if (aPrio < bPrio) { - return -1; - } else if (aPrio > bPrio) { - return 1; - } - - // sort on titles - return a.command.title.localeCompare(b.command.title); - } -} diff --git a/src/vs/platform/extensions/common/abstractExtensionService.ts b/src/vs/platform/extensions/common/abstractExtensionService.ts index 47ca864e829..25ae438be94 100644 --- a/src/vs/platform/extensions/common/abstractExtensionService.ts +++ b/src/vs/platform/extensions/common/abstractExtensionService.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import Severity from 'vs/base/common/severity'; import { TPromise } from 'vs/base/common/winjs.base'; import { IExtensionDescription, IExtensionService, IExtensionsStatus, ExtensionPointContribution } from 'vs/platform/extensions/common/extensions'; -import { IExtensionPoint, onWillActivate } from 'vs/platform/extensions/common/extensionsRegistry'; +import { IExtensionPoint } from 'vs/platform/extensions/common/extensionsRegistry'; const hasOwnProperty = Object.hasOwnProperty; @@ -105,7 +105,6 @@ export abstract class AbstractExtensionService imp } private _activateByEvent(activationEvent: string): TPromise { - onWillActivate.fire(activationEvent); let activateExtensions = this._registry.getExtensionDescriptionsForActivationEvent(activationEvent); return this._activateExtensions(activateExtensions, 0); } diff --git a/src/vs/platform/extensions/common/extensionsRegistry.ts b/src/vs/platform/extensions/common/extensionsRegistry.ts index 4cb3ac5b266..101e8de6830 100644 --- a/src/vs/platform/extensions/common/extensionsRegistry.ts +++ b/src/vs/platform/extensions/common/extensionsRegistry.ts @@ -11,13 +11,10 @@ import Severity from 'vs/base/common/severity'; import { IMessage, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { Registry } from 'vs/platform/platform'; -import { Emitter } from 'vs/base/common/event'; const hasOwnProperty = Object.hasOwnProperty; const schemaRegistry = Registry.as(Extensions.JSONContribution); -export const onWillActivate: Emitter = new Emitter(); - export class ExtensionMessageCollector { private _messageHandler: (msg: IMessage) => void; diff --git a/src/vs/platform/telemetry/common/telemetry.ts b/src/vs/platform/telemetry/common/telemetry.ts index 798e9727062..dd6343e6897 100644 --- a/src/vs/platform/telemetry/common/telemetry.ts +++ b/src/vs/platform/telemetry/common/telemetry.ts @@ -5,16 +5,7 @@ 'use strict'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { guessMimeTypes } from 'vs/base/common/mime'; -import paths = require('vs/base/common/paths'); -import URI from 'vs/base/common/uri'; -import { ConfigurationSource, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IKeybindingService, KeybindingSource } from 'vs/platform/keybinding/common/keybinding'; -import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; export const ITelemetryService = createDecorator('telemetryService'); @@ -46,268 +37,3 @@ export interface ITelemetryService { getExperiments(): ITelemetryExperiments; } - -export const defaultExperiments: ITelemetryExperiments = { - showNewUserWatermark: false, - openUntitledFile: true -}; - -export const NullTelemetryService = { - _serviceBrand: undefined, - _experiments: defaultExperiments, - publicLog(eventName: string, data?: any) { - return TPromise.as(null); - }, - isOptedIn: true, - getTelemetryInfo(): TPromise { - return TPromise.as({ - instanceId: 'someValue.instanceId', - sessionId: 'someValue.sessionId', - machineId: 'someValue.machineId' - }); - }, - getExperiments(): ITelemetryExperiments { - return this._experiments; - } -}; - -const beginGettingStartedExp = Date.UTC(2017, 0, 9); -const endGettingStartedExp = Date.UTC(2017, 0, 16); - -export function loadExperiments(contextService: IWorkspaceContextService, storageService: IStorageService, configurationService: IConfigurationService): ITelemetryExperiments { - - const key = 'experiments.randomness'; - let valueString = storageService.get(key); - if (!valueString) { - valueString = Math.random().toString(); - storageService.store(key, valueString); - } - - const random1 = parseFloat(valueString); - let [random2, showNewUserWatermark] = splitRandom(random1); - let [random3, openUntitledFile] = splitRandom(random2); - let [, openGettingStarted] = splitRandom(random3); - - const newUserDuration = 24 * 60 * 60 * 1000; - const firstSessionDate = storageService.get('telemetry.firstSessionDate'); - const isNewUser = !firstSessionDate || Date.now() - Date.parse(firstSessionDate) < newUserDuration; - if (!isNewUser || contextService.hasWorkspace()) { - showNewUserWatermark = defaultExperiments.showNewUserWatermark; - openUntitledFile = defaultExperiments.openUntitledFile; - } - - const isNewSession = !storageService.get('telemetry.lastSessionDate'); - const now = Date.now(); - if (!(isNewSession && now >= beginGettingStartedExp && now < endGettingStartedExp)) { - openGettingStarted = undefined; - } - - return applyOverrides(configurationService, { - showNewUserWatermark, - openUntitledFile, - openGettingStarted - }); -} - -export function applyOverrides(configurationService: IConfigurationService, experiments: ITelemetryExperiments): ITelemetryExperiments { - const config: any = configurationService.getConfiguration('telemetry'); - const experimentsConfig = config && config.experiments || {}; - Object.keys(experiments).forEach(key => { - if (key in experimentsConfig) { - experiments[key] = experimentsConfig[key]; - } - }); - return experiments; -} - -function splitRandom(random: number): [number, boolean] { - const scaled = random * 2; - const i = Math.floor(scaled); - return [scaled - i, i === 1]; -} - -export interface ITelemetryAppender { - log(eventName: string, data: any): void; -} - -export function combinedAppender(...appenders: ITelemetryAppender[]): ITelemetryAppender { - return { log: (e, d) => appenders.forEach(a => a.log(e, d)) }; -} - -export const NullAppender: ITelemetryAppender = { log: () => null }; - -// --- util - -export function anonymize(input: string): string { - if (!input) { - return input; - } - - let r = ''; - for (let i = 0; i < input.length; i++) { - let ch = input[i]; - if (ch >= '0' && ch <= '9') { - r += '0'; - continue; - } - if (ch >= 'a' && ch <= 'z') { - r += 'a'; - continue; - } - if (ch >= 'A' && ch <= 'Z') { - r += 'A'; - continue; - } - r += ch; - } - return r; -} - -export interface URIDescriptor { - mimeType?: string; - ext?: string; - path?: string; -} - -export function telemetryURIDescriptor(uri: URI): URIDescriptor { - const fsPath = uri && uri.fsPath; - return fsPath ? { mimeType: guessMimeTypes(fsPath).join(', '), ext: paths.extname(fsPath), path: anonymize(fsPath) } : {}; -} - -const configurationValueWhitelist = [ - 'window.zoomLevel', - 'editor.fontSize', - 'editor.fontFamily', - 'editor.tabSize', - 'files.autoSave', - 'files.hotExit', - 'typescript.check.tscVersion', - 'editor.renderWhitespace', - 'editor.cursorBlinking', - 'editor.cursorStyle', - 'files.associations', - 'workbench.statusBar.visible', - 'editor.wrappingColumn', - 'editor.insertSpaces', - 'editor.renderIndentGuides', - 'files.trimTrailingWhitespace', - 'git.confirmSync', - 'editor.rulers', - 'workbench.sideBar.location', - 'editor.fontLigatures', - 'editor.wordWrap', - 'editor.lineHeight', - 'editor.detectIndentation', - 'editor.formatOnType', - 'editor.formatOnSave', - 'window.openFilesInNewWindow', - 'javascript.validate.enable', - 'editor.mouseWheelZoom', - 'typescript.check.workspaceVersion', - 'editor.fontWeight', - 'editor.scrollBeyondLastLine', - 'editor.lineNumbers', - 'editor.wrappingIndent', - 'editor.renderControlCharacters', - 'editor.autoClosingBrackets', - 'window.reopenFolders', - 'extensions.autoUpdate', - 'editor.tabCompletion', - 'files.eol', - 'explorer.openEditors.visible', - 'workbench.editor.enablePreview', - 'files.autoSaveDelay', - 'editor.roundedSelection', - 'editor.quickSuggestions', - 'editor.acceptSuggestionOnEnter', - 'workbench.editor.showTabs', - 'files.encoding', - 'editor.quickSuggestionsDelay', - 'editor.snippetSuggestions', - 'editor.selectionHighlight', - 'editor.glyphMargin', - 'php.validate.run', - 'editor.wordSeparators', - 'editor.mouseWheelScrollSensitivity', - 'editor.suggestOnTriggerCharacters', - 'git.enabled', - 'http.proxyStrictSSL', - 'terminal.integrated.fontFamily', - 'editor.overviewRulerLanes', - 'editor.wordBasedSuggestions', - 'editor.hideCursorInOverviewRuler', - 'editor.trimAutoWhitespace', - 'editor.folding', - 'workbench.editor.enablePreviewFromQuickOpen', - 'php.validate.enable', - 'editor.parameterHints', -]; - -export function configurationTelemetry(telemetryService: ITelemetryService, configurationService: IConfigurationService): IDisposable { - return configurationService.onDidUpdateConfiguration(event => { - if (event.source !== ConfigurationSource.Default) { - telemetryService.publicLog('updateConfiguration', { - configurationSource: ConfigurationSource[event.source], - configurationKeys: flattenKeys(event.sourceConfig) - }); - telemetryService.publicLog('updateConfigurationValues', { - configurationSource: ConfigurationSource[event.source], - configurationValues: flattenValues(event.sourceConfig, configurationValueWhitelist) - }); - } - }); -} - -export function lifecycleTelemetry(telemetryService: ITelemetryService, lifecycleService: ILifecycleService): IDisposable { - return lifecycleService.onShutdown(event => { - telemetryService.publicLog('shutdown', { reason: ShutdownReason[event] }); - }); -} - -export function keybindingsTelemetry(telemetryService: ITelemetryService, keybindingService: IKeybindingService): IDisposable { - return keybindingService.onDidUpdateKeybindings(event => { - if (event.source === KeybindingSource.User && event.keybindings) { - telemetryService.publicLog('updateKeybindings', { - bindings: event.keybindings.map(binding => ({ - key: binding.key, - command: binding.command, - when: binding.when, - args: binding.args ? true : undefined - })) - }); - } - }); -} - -function flattenKeys(value: Object): string[] { - if (!value) { - return []; - } - const result: string[] = []; - flatKeys(result, '', value); - return result; -} - -function flatKeys(result: string[], prefix: string, value: Object): void { - if (value && typeof value === 'object' && !Array.isArray(value)) { - Object.keys(value) - .forEach(key => flatKeys(result, prefix ? `${prefix}.${key}` : key, value[key])); - } else { - result.push(prefix); - } -} - -function flattenValues(value: Object, keys: string[]): { [key: string]: any }[] { - if (!value) { - return []; - } - - return keys.reduce((array, key) => { - const v = key.split('.') - .reduce((tmp, k) => tmp && typeof tmp === 'object' ? tmp[k] : undefined, value); - if (typeof v !== 'undefined') { - array.push({ [key]: v }); - } - return array; - }, []); -} \ No newline at end of file diff --git a/src/vs/platform/telemetry/common/telemetryIpc.ts b/src/vs/platform/telemetry/common/telemetryIpc.ts index 7cf10b99e70..90aa9ac4f5d 100644 --- a/src/vs/platform/telemetry/common/telemetryIpc.ts +++ b/src/vs/platform/telemetry/common/telemetryIpc.ts @@ -7,7 +7,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { ITelemetryAppender } from './telemetry'; +import { ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; export interface ITelemetryLog { eventName: string; diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 36007755990..40937bc571c 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -7,7 +7,8 @@ import { localize } from 'vs/nls'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; -import { ITelemetryService, ITelemetryAppender, ITelemetryInfo, ITelemetryExperiments, defaultExperiments } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService, ITelemetryInfo, ITelemetryExperiments } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryAppender, defaultExperiments } from 'vs/platform/telemetry/common/telemetryUtils'; import { optional } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts new file mode 100644 index 00000000000..fbc9f69d620 --- /dev/null +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -0,0 +1,282 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { TPromise } from 'vs/base/common/winjs.base'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { guessMimeTypes } from 'vs/base/common/mime'; +import paths = require('vs/base/common/paths'); +import URI from 'vs/base/common/uri'; +import { ConfigurationSource, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IKeybindingService, KeybindingSource } from 'vs/platform/keybinding/common/keybinding'; +import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { ITelemetryService, ITelemetryExperiments, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; + +export const defaultExperiments: ITelemetryExperiments = { + showNewUserWatermark: false, + openUntitledFile: true +}; + +export const NullTelemetryService = { + _serviceBrand: undefined, + _experiments: defaultExperiments, + publicLog(eventName: string, data?: any) { + return TPromise.as(null); + }, + isOptedIn: true, + getTelemetryInfo(): TPromise { + return TPromise.as({ + instanceId: 'someValue.instanceId', + sessionId: 'someValue.sessionId', + machineId: 'someValue.machineId' + }); + }, + getExperiments(): ITelemetryExperiments { + return this._experiments; + } +}; + +const beginGettingStartedExp = Date.UTC(2017, 0, 9); +const endGettingStartedExp = Date.UTC(2017, 0, 16); + +export function loadExperiments(contextService: IWorkspaceContextService, storageService: IStorageService, configurationService: IConfigurationService): ITelemetryExperiments { + + const key = 'experiments.randomness'; + let valueString = storageService.get(key); + if (!valueString) { + valueString = Math.random().toString(); + storageService.store(key, valueString); + } + + const random1 = parseFloat(valueString); + let [random2, showNewUserWatermark] = splitRandom(random1); + let [random3, openUntitledFile] = splitRandom(random2); + let [, openGettingStarted] = splitRandom(random3); + + const newUserDuration = 24 * 60 * 60 * 1000; + const firstSessionDate = storageService.get('telemetry.firstSessionDate'); + const isNewUser = !firstSessionDate || Date.now() - Date.parse(firstSessionDate) < newUserDuration; + if (!isNewUser || contextService.hasWorkspace()) { + showNewUserWatermark = defaultExperiments.showNewUserWatermark; + openUntitledFile = defaultExperiments.openUntitledFile; + } + + const isNewSession = !storageService.get('telemetry.lastSessionDate'); + const now = Date.now(); + if (!(isNewSession && now >= beginGettingStartedExp && now < endGettingStartedExp)) { + openGettingStarted = undefined; + } + + return applyOverrides(configurationService, { + showNewUserWatermark, + openUntitledFile, + openGettingStarted + }); +} + +export function applyOverrides(configurationService: IConfigurationService, experiments: ITelemetryExperiments): ITelemetryExperiments { + const config: any = configurationService.getConfiguration('telemetry'); + const experimentsConfig = config && config.experiments || {}; + Object.keys(experiments).forEach(key => { + if (key in experimentsConfig) { + experiments[key] = experimentsConfig[key]; + } + }); + return experiments; +} + +function splitRandom(random: number): [number, boolean] { + const scaled = random * 2; + const i = Math.floor(scaled); + return [scaled - i, i === 1]; +} + +export interface ITelemetryAppender { + log(eventName: string, data: any): void; +} + +export function combinedAppender(...appenders: ITelemetryAppender[]): ITelemetryAppender { + return { log: (e, d) => appenders.forEach(a => a.log(e, d)) }; +} + +export const NullAppender: ITelemetryAppender = { log: () => null }; + +// --- util + +export function anonymize(input: string): string { + if (!input) { + return input; + } + + let r = ''; + for (let i = 0; i < input.length; i++) { + let ch = input[i]; + if (ch >= '0' && ch <= '9') { + r += '0'; + continue; + } + if (ch >= 'a' && ch <= 'z') { + r += 'a'; + continue; + } + if (ch >= 'A' && ch <= 'Z') { + r += 'A'; + continue; + } + r += ch; + } + return r; +} + +export interface URIDescriptor { + mimeType?: string; + ext?: string; + path?: string; +} + +export function telemetryURIDescriptor(uri: URI): URIDescriptor { + const fsPath = uri && uri.fsPath; + return fsPath ? { mimeType: guessMimeTypes(fsPath).join(', '), ext: paths.extname(fsPath), path: anonymize(fsPath) } : {}; +} + +const configurationValueWhitelist = [ + 'window.zoomLevel', + 'editor.fontSize', + 'editor.fontFamily', + 'editor.tabSize', + 'files.autoSave', + 'files.hotExit', + 'typescript.check.tscVersion', + 'editor.renderWhitespace', + 'editor.cursorBlinking', + 'editor.cursorStyle', + 'files.associations', + 'workbench.statusBar.visible', + 'editor.wrappingColumn', + 'editor.insertSpaces', + 'editor.renderIndentGuides', + 'files.trimTrailingWhitespace', + 'git.confirmSync', + 'editor.rulers', + 'workbench.sideBar.location', + 'editor.fontLigatures', + 'editor.wordWrap', + 'editor.lineHeight', + 'editor.detectIndentation', + 'editor.formatOnType', + 'editor.formatOnSave', + 'window.openFilesInNewWindow', + 'javascript.validate.enable', + 'editor.mouseWheelZoom', + 'typescript.check.workspaceVersion', + 'editor.fontWeight', + 'editor.scrollBeyondLastLine', + 'editor.lineNumbers', + 'editor.wrappingIndent', + 'editor.renderControlCharacters', + 'editor.autoClosingBrackets', + 'window.reopenFolders', + 'extensions.autoUpdate', + 'editor.tabCompletion', + 'files.eol', + 'explorer.openEditors.visible', + 'workbench.editor.enablePreview', + 'files.autoSaveDelay', + 'editor.roundedSelection', + 'editor.quickSuggestions', + 'editor.acceptSuggestionOnEnter', + 'workbench.editor.showTabs', + 'files.encoding', + 'editor.quickSuggestionsDelay', + 'editor.snippetSuggestions', + 'editor.selectionHighlight', + 'editor.glyphMargin', + 'php.validate.run', + 'editor.wordSeparators', + 'editor.mouseWheelScrollSensitivity', + 'editor.suggestOnTriggerCharacters', + 'git.enabled', + 'http.proxyStrictSSL', + 'terminal.integrated.fontFamily', + 'editor.overviewRulerLanes', + 'editor.wordBasedSuggestions', + 'editor.hideCursorInOverviewRuler', + 'editor.trimAutoWhitespace', + 'editor.folding', + 'workbench.editor.enablePreviewFromQuickOpen', + 'php.validate.enable', + 'editor.parameterHints', +]; + +export function configurationTelemetry(telemetryService: ITelemetryService, configurationService: IConfigurationService): IDisposable { + return configurationService.onDidUpdateConfiguration(event => { + if (event.source !== ConfigurationSource.Default) { + telemetryService.publicLog('updateConfiguration', { + configurationSource: ConfigurationSource[event.source], + configurationKeys: flattenKeys(event.sourceConfig) + }); + telemetryService.publicLog('updateConfigurationValues', { + configurationSource: ConfigurationSource[event.source], + configurationValues: flattenValues(event.sourceConfig, configurationValueWhitelist) + }); + } + }); +} + +export function lifecycleTelemetry(telemetryService: ITelemetryService, lifecycleService: ILifecycleService): IDisposable { + return lifecycleService.onShutdown(event => { + telemetryService.publicLog('shutdown', { reason: ShutdownReason[event] }); + }); +} + +export function keybindingsTelemetry(telemetryService: ITelemetryService, keybindingService: IKeybindingService): IDisposable { + return keybindingService.onDidUpdateKeybindings(event => { + if (event.source === KeybindingSource.User && event.keybindings) { + telemetryService.publicLog('updateKeybindings', { + bindings: event.keybindings.map(binding => ({ + key: binding.key, + command: binding.command, + when: binding.when, + args: binding.args ? true : undefined + })) + }); + } + }); +} + +function flattenKeys(value: Object): string[] { + if (!value) { + return []; + } + const result: string[] = []; + flatKeys(result, '', value); + return result; +} + +function flatKeys(result: string[], prefix: string, value: Object): void { + if (value && typeof value === 'object' && !Array.isArray(value)) { + Object.keys(value) + .forEach(key => flatKeys(result, prefix ? `${prefix}.${key}` : key, value[key])); + } else { + result.push(prefix); + } +} + +function flattenValues(value: Object, keys: string[]): { [key: string]: any }[] { + if (!value) { + return []; + } + + return keys.reduce((array, key) => { + const v = key.split('.') + .reduce((tmp, k) => tmp && typeof tmp === 'object' ? tmp[k] : undefined, value); + if (typeof v !== 'undefined') { + array.push({ [key]: v }); + } + return array; + }, []); +} diff --git a/src/vs/platform/telemetry/node/appInsightsAppender.ts b/src/vs/platform/telemetry/node/appInsightsAppender.ts index 50f19c0778a..a6a3322cbb9 100644 --- a/src/vs/platform/telemetry/node/appInsightsAppender.ts +++ b/src/vs/platform/telemetry/node/appInsightsAppender.ts @@ -8,7 +8,7 @@ import * as appInsights from 'applicationinsights'; import { isObject } from 'vs/base/common/types'; import { safeStringify, mixin } from 'vs/base/common/objects'; import { TPromise } from 'vs/base/common/winjs.base'; -import { ITelemetryAppender } from '../common/telemetry'; +import { ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; let _initialized = false; diff --git a/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts b/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts index 6e6bb729c9e..0f6157ba5e6 100644 --- a/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts +++ b/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts @@ -9,14 +9,14 @@ import { Emitter } from 'vs/base/common/event'; import { TPromise } from 'vs/base/common/winjs.base'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; -import Telemetry = require('vs/platform/telemetry/common/telemetry'); +import { NullAppender, ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; import Errors = require('vs/base/common/errors'); import * as sinon from 'sinon'; import { getConfigurationValue } from 'vs/platform/configuration/common/configuration'; const optInStatusEventName: string = 'optInStatus'; -class TestTelemetryAppender implements Telemetry.ITelemetryAppender { +class TestTelemetryAppender implements ITelemetryAppender { public events: any[]; public isDisposed: boolean; @@ -174,7 +174,7 @@ suite('TelemetryService', () => { test('TelemetryInfo comes from properties', function () { let service = new TelemetryService({ - appender: Telemetry.NullAppender, + appender: NullAppender, commonProperties: TPromise.as({ sessionID: 'one', ['common.instanceId']: 'two', diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 82be13ee78d..f415b76bc13 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -1057,28 +1057,28 @@ declare module 'vscode' { * Scheme is the `http` part of `http://www.msft.com/some/path?query#fragment`. * The part before the first colon. */ - scheme: string; + readonly scheme: string; /** * Authority is the `www.msft.com` part of `http://www.msft.com/some/path?query#fragment`. * The part between the first double slashes and the next slash. */ - authority: string; + readonly authority: string; /** * Path is the `/some/path` part of `http://www.msft.com/some/path?query#fragment`. */ - path: string; + readonly path: string; /** * Query is the `query` part of `http://www.msft.com/some/path?query#fragment`. */ - query: string; + readonly query: string; /** * Fragment is the `fragment` part of `http://www.msft.com/some/path?query#fragment`. */ - fragment: string; + readonly fragment: string; /** * The string representing the corresponding file system path of this Uri. @@ -1087,7 +1087,7 @@ declare module 'vscode' { * uses the platform specific path separator. Will *not* validate the path for * invalid characters and semantics. Will *not* look at the scheme of this Uri. */ - fsPath: string; + readonly fsPath: string; /** * Derive a new Uri from this Uri. @@ -3646,15 +3646,6 @@ declare module 'vscode' { */ export function createOutputChannel(name: string): OutputChannel; - /** - * Set a message to the status bar. This is a short hand for the more powerful - * status bar [items](#window.createStatusBarItem). - * - * @param text The message to show, support icon subtitution as in status bar [items](#StatusBarItem.text). - * @return A disposable which hides the status bar message. - */ - export function setStatusBarMessage(text: string): Disposable; - /** * Set a message to the status bar. This is a short hand for the more powerful * status bar [items](#window.createStatusBarItem). @@ -3675,6 +3666,18 @@ declare module 'vscode' { */ export function setStatusBarMessage(text: string, hideWhenDone: Thenable): Disposable; + /** + * Set a message to the status bar. This is a short hand for the more powerful + * status bar [items](#window.createStatusBarItem). + * + * *Note* that status bar messages stack and that they must be disposed when no + * longer used. + * + * @param text The message to show, support icon subtitution as in status bar [items](#StatusBarItem.text). + * @return A disposable which hides the status bar message. + */ + export function setStatusBarMessage(text: string): Disposable; + /** * Creates a status bar [item](#StatusBarItem). * diff --git a/src/vs/workbench/api/node/mainThreadTerminalService.ts b/src/vs/workbench/api/node/mainThreadTerminalService.ts index 75baf259baa..83b1f50c893 100644 --- a/src/vs/workbench/api/node/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/node/mainThreadTerminalService.ts @@ -5,7 +5,7 @@ 'use strict'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ITerminalService, ITerminalInstance } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService, ITerminalInstance, IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal'; import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; import { TPromise } from 'vs/base/common/winjs.base'; import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape } from './extHost.protocol'; @@ -31,7 +31,14 @@ export class MainThreadTerminalService extends MainThreadTerminalServiceShape { } public $createTerminal(name?: string, shellPath?: string, shellArgs?: string[], waitOnExit?: boolean): TPromise { - return TPromise.as(this.terminalService.createInstance(name, shellPath, shellArgs, waitOnExit, true).id); + const shellLaunchConfig: IShellLaunchConfig = { + name, + executable: shellPath, + args: shellArgs, + waitOnExit, + ignoreConfigurationCwd: true + }; + return TPromise.as(this.terminalService.createInstance(shellLaunchConfig).id); } public $show(terminalId: number, preserveFocus: boolean): void { diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 22d7e8220ce..35120922f07 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -27,7 +27,7 @@ import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/ import { IEditorAction, ICommonCodeEditor, IModelContentChangedEvent, IModelOptionsChangedEvent, IModelLanguageChangedEvent, ICursorPositionChangedEvent, EndOfLineSequence, EditorType, IModel, IDiffEditorModel, IEditor } from 'vs/editor/common/editorCommon'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { TrimTrailingWhitespaceAction } from 'vs/editor/contrib/linesOperations/common/linesOperations'; -import { IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpacesAction, IndentationToTabsAction } from 'vs/workbench/parts/indentation/common/indentation'; +import { IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpacesAction, IndentationToTabsAction } from 'vs/workbench/common/editor/indentation'; import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor'; import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor'; import { IEditor as IBaseEditor, IEditorInput } from 'vs/platform/editor/common/editor'; @@ -42,10 +42,9 @@ import { StyleMutator } from 'vs/base/browser/styleMutator'; import { Selection } from 'vs/editor/common/core/selection'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { TabFocus } from 'vs/editor/common/config/commonEditorConfig'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IExtensionsViewlet, VIEWLET_ID } from 'vs/workbench/parts/extensions/common/extensions'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { getCodeEditor as getEditorWidget } from 'vs/editor/common/services/codeEditorService'; function getCodeEditor(editorWidget: IEditor): ICommonCodeEditor { @@ -680,24 +679,20 @@ function isWritableCodeEditor(e: IBaseEditor): boolean { export class ShowLanguageExtensionsAction extends Action { - static ID = 'workbench.extensions.action.showLanguageExtensions'; + static ID = 'workbench.action.showLanguageExtensions'; constructor( - private extension: string, - @IViewletService private viewletService: IViewletService, + private fileExtension: string, + @ICommandService private commandService: ICommandService, @IExtensionGalleryService galleryService: IExtensionGalleryService ) { - super(ShowLanguageExtensionsAction.ID, nls.localize('showLanguageExtensions', "Search Marketplace Extensions for '{0}'...", extension), null, true); + super(ShowLanguageExtensionsAction.ID, nls.localize('showLanguageExtensions', "Search Marketplace Extensions for '{0}'...", fileExtension)); + this.enabled = galleryService.isEnabled(); } run(): TPromise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - viewlet.search(`ext:${this.extension.replace(/^\./, '')}`); - viewlet.focus(); - }); + return this.commandService.executeCommand('workbench.extensions.action.showLanguageExtensions', this.fileExtension).then(() => void 0); } } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index ac4094b5fe1..d4eb29ba79d 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -12,6 +12,7 @@ import errors = require('vs/base/common/errors'); import DOM = require('vs/base/browser/dom'); import { isMacintosh } from 'vs/base/common/platform'; import { MIME_BINARY } from 'vs/base/common/mime'; +import { shorten } from 'vs/base/common/labels'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { Position, IEditorInput } from 'vs/platform/editor/common/editor'; import { IEditorGroup, toResource } from 'vs/workbench/common/editor'; @@ -37,7 +38,6 @@ import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElemen import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { extractResources } from 'vs/base/browser/dnd'; import { LinkedMap } from 'vs/base/common/map'; -import paths = require('vs/base/common/paths'); interface IEditorInputLabel { editor: IEditorInput; @@ -256,15 +256,10 @@ export class TabsTitleControl extends TitleControl { const labels: IEditorInputLabel[] = []; const mapLabelToDuplicates = new LinkedMap(); - const mapLabelAndDescriptionToDuplicates = new LinkedMap(); // Build labels and descriptions for each editor editors.forEach(editor => { let description = editor.getDescription(); - if (description && description.indexOf(paths.nativeSep) >= 0) { - description = paths.basename(description); // optimize for editors that show paths and build a shorter description to keep tab width small - } - const item: IEditorInputLabel = { editor, name: editor.getName(), @@ -274,31 +269,20 @@ export class TabsTitleControl extends TitleControl { labels.push(item); mapLabelToDuplicates.getOrSet(item.name, []).push(item); - if (item.description) { - mapLabelAndDescriptionToDuplicates.getOrSet(item.name + item.description, []).push(item); - } }); - // Mark label duplicates + // Mark duplicates and shorten their descriptions const labelDuplicates = mapLabelToDuplicates.values(); labelDuplicates.forEach(duplicates => { if (duplicates.length > 1) { - duplicates.forEach(duplicate => { + let shortenedDescriptions = shorten(duplicates.map(duplicate => duplicate.editor.getDescription())); + duplicates.forEach((duplicate, i) => { + duplicate.description = shortenedDescriptions[i]; duplicate.hasAmbiguousName = true; }); } }); - // React to duplicates for combination of label and description - const descriptionDuplicates = mapLabelAndDescriptionToDuplicates.values(); - descriptionDuplicates.forEach(duplicates => { - if (duplicates.length > 1) { - duplicates.forEach(duplicate => { - duplicate.description = duplicate.editor.getDescription(); // fallback to full description if the short description still has duplicates - }); - } - }); - return labels; } diff --git a/src/vs/workbench/parts/indentation/common/indentation.ts b/src/vs/workbench/common/editor/indentation.ts similarity index 68% rename from src/vs/workbench/parts/indentation/common/indentation.ts rename to src/vs/workbench/common/editor/indentation.ts index 2f95969bcf4..607df8c965d 100644 --- a/src/vs/workbench/parts/indentation/common/indentation.ts +++ b/src/vs/workbench/common/editor/indentation.ts @@ -5,11 +5,12 @@ import * as nls from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; -import { ICommonCodeEditor, EditorContextKeys } from 'vs/editor/common/editorCommon'; +import { ICommonCodeEditor, EditorContextKeys, ICommand, ICursorStateComputerData, IEditOperationBuilder, ITokenizedModel } from 'vs/editor/common/editorCommon'; import { editorAction, ServicesAccessor, IActionOptions, EditorAction } from 'vs/editor/common/editorCommonExtensions'; -import { IndentationToSpacesCommand, IndentationToTabsCommand } from 'vs/workbench/parts/indentation/common/indentationCommands'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IModelService } from 'vs/editor/common/services/modelService'; +import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; @editorAction export class IndentationToSpacesAction extends EditorAction { @@ -160,3 +161,61 @@ export class DetectIndentation extends EditorAction { model.detectIndentation(creationOpts.insertSpaces, creationOpts.tabSize); } } + +function getIndentationEditOperations(model: ITokenizedModel, builder: IEditOperationBuilder, tabSize: number, tabsToSpaces: boolean): void { + if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) { + // Model is empty + return; + } + + let spaces = ''; + for (let i = 0; i < tabSize; i++) { + spaces += ' '; + } + + const content = model.getLinesContent(); + for (let i = 0; i < content.length; i++) { + let lastIndentationColumn = model.getLineFirstNonWhitespaceColumn(i + 1); + if (lastIndentationColumn === 0) { + lastIndentationColumn = model.getLineMaxColumn(i + 1); + } + + const text = (tabsToSpaces ? content[i].substr(0, lastIndentationColumn).replace(/\t/ig, spaces) : + content[i].substr(0, lastIndentationColumn).replace(new RegExp(spaces, 'gi'), '\t')) + + content[i].substr(lastIndentationColumn); + + builder.addEditOperation(new Range(i + 1, 1, i + 1, model.getLineMaxColumn(i + 1)), text); + } +} + +export class IndentationToSpacesCommand implements ICommand { + + private selectionId: string; + + constructor(private selection: Selection, private tabSize: number) { } + + public getEditOperations(model: ITokenizedModel, builder: IEditOperationBuilder): void { + this.selectionId = builder.trackSelection(this.selection); + getIndentationEditOperations(model, builder, this.tabSize, true); + } + + public computeCursorState(model: ITokenizedModel, helper: ICursorStateComputerData): Selection { + return helper.getTrackedSelection(this.selectionId); + } +} + +export class IndentationToTabsCommand implements ICommand { + + private selectionId: string; + + constructor(private selection: Selection, private tabSize: number) { } + + public getEditOperations(model: ITokenizedModel, builder: IEditOperationBuilder): void { + this.selectionId = builder.trackSelection(this.selection); + getIndentationEditOperations(model, builder, this.tabSize, false); + } + + public computeCursorState(model: ITokenizedModel, helper: ICursorStateComputerData): Selection { + return helper.getTrackedSelection(this.selectionId); + } +} diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index 4bb9cfe2147..9de6e754f45 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -8,7 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { EditorInput, ITextEditorModel } from 'vs/workbench/common/editor'; import URI from 'vs/base/common/uri'; import { IReference } from 'vs/base/common/lifecycle'; -import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetry'; +import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetryUtils'; import { ITextModelResolverService } from 'vs/editor/common/services/resolverService'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledEditorInput.ts index 919cd4ee181..8d037cb8adf 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledEditorInput.ts @@ -17,7 +17,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import Event, { Emitter } from 'vs/base/common/event'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetry'; +import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetryUtils'; /** * An editor input to be used for untitled text buffers. diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index ad907944351..2340dc31350 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -22,7 +22,8 @@ import pkg from 'vs/platform/node/package'; import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; import { Workbench, IWorkbenchStartedInfo } from 'vs/workbench/electron-browser/workbench'; import { StorageService, inMemoryLocalStorageInstance } from 'vs/platform/storage/common/storageService'; -import { ITelemetryService, NullTelemetryService, configurationTelemetry, loadExperiments, lifecycleTelemetry } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService, configurationTelemetry, loadExperiments, lifecycleTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc'; import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { IdleMonitor, UserStatus } from 'vs/platform/telemetry/browser/idleMonitor'; @@ -68,7 +69,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { CommandService } from 'vs/platform/commands/common/commandService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; -import { MainThreadModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { WorkbenchModeServiceImpl } from 'vs/workbench/services/mode/common/workbenchModeService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { CrashReporter } from 'vs/workbench/electron-browser/crashReporter'; @@ -346,7 +347,7 @@ export class WorkbenchShell { serviceCollection.set(IMarkerService, new SyncDescriptor(MarkerService)); - serviceCollection.set(IModeService, new SyncDescriptor(MainThreadModeServiceImpl)); + serviceCollection.set(IModeService, new SyncDescriptor(WorkbenchModeServiceImpl)); serviceCollection.set(IModelService, new SyncDescriptor(ModelServiceImpl)); diff --git a/src/vs/workbench/electron-browser/workbench.main.ts b/src/vs/workbench/electron-browser/workbench.main.ts index 51a158d0b31..a535aa73334 100644 --- a/src/vs/workbench/electron-browser/workbench.main.ts +++ b/src/vs/workbench/electron-browser/workbench.main.ts @@ -82,7 +82,6 @@ import 'vs/workbench/parts/emmet/browser/emmet.browser.contribution'; import 'vs/workbench/parts/emmet/node/emmet.contribution'; // Code Editor enhacements -import 'vs/workbench/parts/indentation/common/indentation'; import 'vs/workbench/parts/codeEditor/codeEditor.contribution'; import 'vs/workbench/parts/execution/electron-browser/execution.contribution'; diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index ea687355b40..ab71366d4b7 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -276,6 +276,7 @@ export enum State { export interface IDebugConfiguration { allowBreakpointsEverywhere: boolean; openExplorerOnEnd: boolean; + inlineValues: boolean; } export interface IGlobalConfig { 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 055036a7108..70ae4cd04b9 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts @@ -176,6 +176,11 @@ configurationRegistry.registerConfiguration({ type: 'boolean', description: nls.localize({ comment: ['This is the description for a setting'], key: 'openExplorerOnEnd' }, "Automatically open explorer view on the end of a debug session"), default: false + }, + 'debug.inlineValues': { + type: 'boolean', + description: nls.localize({ comment: ['This is the description for a setting'], key: 'inlineValues' }, "Show variable values inline in editor while debugging"), + default: false } } }); diff --git a/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts b/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts index 824e53dc115..917641ad7f3 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts @@ -17,21 +17,26 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ICodeEditor, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; import { IModelDecorationOptions, MouseTargetType, IModelDeltaDecoration, TrackedRangeStickiness, IPosition } from 'vs/editor/common/editorCommon'; +import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { DebugHoverWidget } from 'vs/workbench/parts/debug/electron-browser/debugHover'; import { RemoveBreakpointAction, EditConditionalBreakpointAction, EnableBreakpointAction, DisableBreakpointAction, AddConditionalBreakpointAction } from 'vs/workbench/parts/debug/browser/debugActions'; -import { IDebugEditorContribution, IDebugService, State, IBreakpoint, EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, IStackFrame } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugEditorContribution, IDebugService, State, IBreakpoint, EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, IStackFrame, IDebugConfiguration } from 'vs/workbench/parts/debug/common/debug'; import { BreakpointWidget } from 'vs/workbench/parts/debug/browser/breakpointWidget'; import { FloatingClickWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; +import { toNameValueMap, getDecorations, getWordToLineNumbersMap } from 'vs/workbench/parts/debug/electron-browser/debugInlineValues'; const HOVER_DELAY = 300; const LAUNCH_JSON_REGEX = /launch\.json$/; +const REMOVE_DECORATORS_DEBOUNCE_INTERVAL = 100; // If we receive a break in this interval, don't reset decorators as it causes a UI flash. +const INLINE_DECORATOR_KEY = 'inlineDecorator'; @editorContribution export class DebugEditorContribution implements IDebugEditorContribution { @@ -45,6 +50,8 @@ export class DebugEditorContribution implements IDebugEditorContribution { private breakpointHintDecoration: string[]; private breakpointWidget: BreakpointWidget; private breakpointWidgetVisible: IContextKey; + private removeDecorationsTimeoutId = 0; + private wordToLineNumbersMap: Map; private configurationWidget: FloatingClickWidget; @@ -55,7 +62,9 @@ export class DebugEditorContribution implements IDebugEditorContribution { @IInstantiationService private instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @ICommandService private commandService: ICommandService, - @ITelemetryService private telemetryService: ITelemetryService + @ICodeEditorService private codeEditorService: ICodeEditorService, + @ITelemetryService private telemetryService: ITelemetryService, + @IConfigurationService private configurationService: IConfigurationService ) { this.breakpointHintDecoration = []; this.hoverWidget = new DebugHoverWidget(this.editor, this.debugService, this.instantiationService); @@ -65,6 +74,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.registerListeners(); this.breakpointWidgetVisible = CONTEXT_BREAKPOINT_WIDGET_VISIBLE.bindTo(contextKeyService); this.updateConfigurationWidgetVisibility(); + this.codeEditorService.registerDecorationType(INLINE_DECORATOR_KEY, {}); } private getContextMenuActions(breakpoint: IBreakpoint, uri: uri, lineNumber: number): TPromise { @@ -200,6 +210,47 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.editor.updateOptions({ hover: true }); this.hideHoverWidget(); } + + if (this.configurationService.getConfiguration('debug').inlineValues) { + this.updateInlineDecorators(sf); + } + } + + private updateInlineDecorators(stackFrame: IStackFrame): void { + // Since step over, step out is a fast continue + break. Continue clears stack. + // This means we'll get a null stackFrame followed quickly by a valid stackFrame. + // Removing all decorators and adding them again causes a noticeable UI flash due to relayout and paint. + // We want to only remove inline decorations if a null stackFrame isn't followed by a valid stackFrame in a short interval. + clearTimeout(this.removeDecorationsTimeoutId); + if (!stackFrame) { + this.removeDecorationsTimeoutId = setTimeout(() => { + this.editor.removeDecorations(INLINE_DECORATOR_KEY); + this.wordToLineNumbersMap = null; + }, REMOVE_DECORATORS_DEBOUNCE_INTERVAL); + return; + } + + // URI has changed, invalidate the editorWordRangeMap so its re-computed for the current model + if (stackFrame.source.uri.toString() !== this.editor.getModel().uri.toString()) { + this.wordToLineNumbersMap = null; + } + + stackFrame.getScopes() + // Get all top level children in the scope chain + .then(scopes => TPromise.join(scopes.map(scope => scope.getChildren()))) + .then(children => { + const editorModel = this.editor.getModel(); + // Compute name-value map for all variables in scope chain + const expressions = [].concat.apply([], children); + const nameValueMap = toNameValueMap(expressions); + // Build wordRangeMap if not already computed for the editor model + if (!this.wordToLineNumbersMap) { + this.wordToLineNumbersMap = getWordToLineNumbersMap(editorModel); + } + // Compute decorators from nameValueMap and wordRangeMap and apply to editor + const decorators = getDecorations(nameValueMap, this.wordToLineNumbersMap); + this.editor.setDecorations(INLINE_DECORATOR_KEY, decorators); + }); } private hideHoverWidget(): void { diff --git a/src/vs/workbench/parts/debug/electron-browser/debugInlineValues.ts b/src/vs/workbench/parts/debug/electron-browser/debugInlineValues.ts new file mode 100644 index 00000000000..ddb2864ee93 --- /dev/null +++ b/src/vs/workbench/parts/debug/electron-browser/debugInlineValues.ts @@ -0,0 +1,140 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDecorationOptions, IModel } from 'vs/editor/common/editorCommon'; +import { StandardTokenType } from 'vs/editor/common/modes'; +import { IExpression } from 'vs/workbench/parts/debug/common/debug'; + +export const MAX_INLINE_VALUE_LENGTH = 50; // Max string length of each inline 'x = y' string. If exceeded ... is added +export const MAX_INLINE_DECORATOR_LENGTH = 150; // Max string length of each inline decorator when debugging. If exceeded ... is added +export const MAX_NUM_INLINE_VALUES = 100; // JS Global scope can have 700+ entries. We want to limit ourselves for perf reasons +export const MAX_TOKENIZATION_LINE_LEN = 500; // If line is too long, then inline values for the line are skipped +// LanguageConfigurationRegistry.getWordDefinition() return regexes that allow spaces and punctuation characters for languages like python +// Using that approach is not viable so we are using a simple regex to look for word tokens. +export const WORD_REGEXP = /[\$\_A-Za-z][\$\_A-Za-z0-9]*/g; + +export function toNameValueMap(expressions: IExpression[]): Map { + const result = new Map(); + let valueCount = 0; + + for (let expr of expressions) { + // Put ellipses in value if its too long. Preserve last char e.g "longstr…" or {a:true, b:true, …} + let value = expr.value; + if (value && value.length > MAX_INLINE_VALUE_LENGTH) { + value = value.substr(0, MAX_INLINE_VALUE_LENGTH) + '…' + value[value.length - 1]; + } + + result.set(expr.name, value); + + // Limit the size of map. Too large can have a perf impact + if (++valueCount >= MAX_NUM_INLINE_VALUES) { + break; + } + } + + return result; +} + +export function getDecorations(nameValueMap: Map, wordToLineNumbersMap: Map): IDecorationOptions[] { + const lineToNamesMap: Map = new Map(); + const decorations: IDecorationOptions[] = []; + + // Compute unique set of names on each line + nameValueMap.forEach((value, name) => { + if (wordToLineNumbersMap.has(name)) { + for (let lineNumber of wordToLineNumbersMap.get(name)) { + if (!lineToNamesMap.has(lineNumber)) { + lineToNamesMap.set(lineNumber, []); + } + + lineToNamesMap.get(lineNumber).push(name); + } + } + }); + + // Compute decorators for each line + lineToNamesMap.forEach((names, line) => { + // Wrap with 1em unicode space for readability + const contentText = '\u2003' + names.map(name => `${name} = ${nameValueMap.get(name)}`).join(', ') + '\u2003'; + decorations.push(createDecoration(line, contentText)); + }); + + return decorations; +} + +function createDecoration(lineNumber: number, contentText: string): IDecorationOptions { + const margin = '10px'; + const backgroundColor = 'rgba(255, 200, 0, 0.2)'; + const lightForegroundColor = 'rgba(0, 0, 0, 0.5)'; + const darkForegroundColor = 'rgba(255, 255, 255, 0.5)'; + + // If decoratorText is too long, trim and add ellipses. This could happen for minified files with everything on a single line + if (contentText.length > MAX_INLINE_DECORATOR_LENGTH) { + contentText = contentText.substr(0, MAX_INLINE_DECORATOR_LENGTH) + '...'; + } + + return { + range: { + startLineNumber: lineNumber, + endLineNumber: lineNumber, + startColumn: Number.MAX_VALUE, + endColumn: Number.MAX_VALUE + }, + renderOptions: { + dark: { + after: { + contentText, + backgroundColor, + color: darkForegroundColor, + margin + } + }, + light: { + after: { + contentText, + backgroundColor, + color: lightForegroundColor, + margin + } + } + } + }; +} + +export function getWordToLineNumbersMap(model: IModel): Map { + const result = new Map(); + + // For every word in every line, map its ranges for fast lookup + for (let lineNumber = 1, len = model.getLineCount(); lineNumber <= len; ++lineNumber) { + const lineContent = model.getLineContent(lineNumber); + + // If line is too long then skip the line + if (lineContent.length > MAX_TOKENIZATION_LINE_LEN) { + continue; + } + + const lineTokens = model.getLineTokens(lineNumber); + for (let token = lineTokens.firstToken(); !!token; token = token.next()) { + const tokenStr = lineContent.substring(token.startOffset, token.endOffset); + + // Token is a word and not a comment + if (token.tokenType === StandardTokenType.Other) { + WORD_REGEXP.lastIndex = 0; // We assume tokens will usually map 1:1 to words if they match + const wordMatch = WORD_REGEXP.exec(tokenStr); + + if (wordMatch) { + const word = wordMatch[0]; + if (!result.has(word)) { + result.set(word, []); + } + + result.get(word).push(lineNumber); + } + } + } + } + + return result; +} diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index 947db833ebc..59150906ba0 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -523,7 +523,7 @@ export class DebugService implements debug.IDebugService { this.telemetryService.publicLog('debugService/addReplExpression'); return this.model.addReplExpression(this.viewModel.focusedProcess, this.viewModel.focusedStackFrame, name) // Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some. - .then(() => this.focusStackFrameAndEvaluate(this.viewModel.focusedStackFrame)); + .then(() => this.focusStackFrameAndEvaluate(this.viewModel.focusedStackFrame, this.viewModel.focusedProcess)); } public removeReplExpressions(): void { @@ -537,7 +537,7 @@ export class DebugService implements debug.IDebugService { public renameWatchExpression(id: string, newName: string): TPromise { return this.model.renameWatchExpression(this.viewModel.focusedProcess, this.viewModel.focusedStackFrame, id, newName) // Evaluate all watch expressions and fetch variables again since watch expression evaluation might have changed some. - .then(() => this.focusStackFrameAndEvaluate(this.viewModel.focusedStackFrame)); + .then(() => this.focusStackFrameAndEvaluate(this.viewModel.focusedStackFrame, this.viewModel.focusedProcess)); } public moveWatchExpression(id: string, position: number): void { @@ -779,7 +779,7 @@ export class DebugService implements debug.IDebugService { this.lastTaskEvent = null; }); - if (filteredTasks[0].isWatching) { + if (filteredTasks[0].isBackground) { return new TPromise((c, e) => this.taskService.addOneTimeDisposableListener(TaskServiceEvents.Inactive, () => c(null))); } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts b/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts index 4cba885380f..7578afeb9b9 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts @@ -559,6 +559,9 @@ export class CallStackRenderer implements IRenderer { private renderStackFrame(stackFrame: debug.IStackFrame, data: IStackFrameTemplateData): void { stackFrame.source.deemphasize ? dom.addClass(data.stackFrame, 'disabled') : dom.removeClass(data.stackFrame, 'disabled'); data.file.title = stackFrame.source.raw.path || stackFrame.source.name; + if (stackFrame.source.raw.origin) { + data.file.title += `\n${stackFrame.source.raw.origin}`; + } 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/electron-browser/terminalSupport.ts b/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts index 6f6340a3e55..651944976f6 100644 --- a/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts +++ b/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts @@ -24,7 +24,7 @@ export class TerminalSupport { let delay = 0; if (!TerminalSupport.integratedTerminalInstance) { - TerminalSupport.integratedTerminalInstance = terminalService.createInstance(args.title || nls.localize('debuggee', "debuggee")); + TerminalSupport.integratedTerminalInstance = terminalService.createInstance({ executable: args.title || nls.localize('debuggee', "debuggee") }); delay = 2000; // delay sendText so that the newly created terminal is ready. } if (!TerminalSupport.terminalDisposedListener) { diff --git a/src/vs/workbench/parts/debug/test/electron-browser/debugInlineValues.test.ts b/src/vs/workbench/parts/debug/test/electron-browser/debugInlineValues.test.ts new file mode 100644 index 00000000000..93f3930ce80 --- /dev/null +++ b/src/vs/workbench/parts/debug/test/electron-browser/debugInlineValues.test.ts @@ -0,0 +1,189 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +// import { Model as EditorModel } from 'vs/editor/common/model/model'; +// import { IModel } from 'vs/editor/common/editorCommon'; +import { StandardTokenType } from 'vs/editor/common/modes'; +// import { LineTokens } from 'vs/editor/common/core/lineTokens'; +import { IExpression } from 'vs/workbench/parts/debug/common/debug'; +import * as inlineValues from 'vs/workbench/parts/debug/electron-browser/debugInlineValues'; + +// Test data +// const testLine = 'function doit(everything, is, awesome, awesome, when, youre, part, of, a, team){}'; +const testNameValueMap = new Map(); + +setup(() => { + testNameValueMap.set('everything', '{emmet: true, batman: true, legoUniverse: true}'); + testNameValueMap.set('is', '15'); + testNameValueMap.set('awesome', '"aweeeeeeeeeeeeeeeeeeeeeeeeeeeeeeesome…"'); + testNameValueMap.set('when', 'true'); + testNameValueMap.set('youre', '"Yes I mean you"'); + testNameValueMap.set('part', '"𝄞 ♪ ♫"'); +}); + +suite('Debug - Inline Value Decorators', () => { + test('getNameValueMapFromScopeChildren trims long values', () => { + const expressions = [ + createExpression('hello', 'world'), + createExpression('blah', createLongString()) + ]; + + const nameValueMap = inlineValues.toNameValueMap(expressions); + const expectedNameValueMap = new Map(); + expectedNameValueMap.set('hello', 'world'); + expectedNameValueMap.set('blah', '"blah blah blah blah blah blah blah blah blah blah…"'); + + // Ensure blah is capped and ellipses added + assert.deepEqual(nameValueMap, expectedNameValueMap); + }); + + test('getNameValueMapFromScopeChildren caps scopes to a MAX_NUM_INLINE_VALUES limit', () => { + const scopeChildren: IExpression[][] = new Array(5); + const expectedNameValueMap: Map = new Map(); + + // 10 Stack Frames with a 100 scope expressions each + // JS Global Scope has 700+ expressions so this is close to a real world scenario + for (let i = 0; i < scopeChildren.length; i++) { + const expressions = new Array(50); + + for (let j = 0; j < expressions.length; ++j) { + const name = `name${i}.${j}`; + const val = `val${i}.${j}`; + expressions[j] = createExpression(name, val); + + if ((i * expressions.length + j) < inlineValues.MAX_NUM_INLINE_VALUES) { + expectedNameValueMap.set(name, val); + } + } + + scopeChildren[i] = expressions; + } + + const expressions = [].concat.apply([], scopeChildren); + const nameValueMap = inlineValues.toNameValueMap(expressions); + + assert.deepEqual(nameValueMap, expectedNameValueMap); + }); + + // test('getDecorators returns correct decorator afterText', () => { + // const lineContent = 'console.log(everything, part, part);'; // part shouldn't be duplicated + // const lineNumber = 1; + // const wordToLinesMap = getWordToLineMap(lineNumber, lineContent); + // const decorators = inlineValues.getDecorations(testNameValueMap, wordToLinesMap); + // const expectedDecoratorText = ' everything = {emmet: true, batman: true, legoUniverse: true}, part = "𝄞 ♪ ♫" '; + // assert.equal(decorators[0].renderOptions.dark.after.contentText, expectedDecoratorText); + // }); + + // test('getEditorWordRangeMap ignores comments and long lines', () => { + // const expectedWords = 'function, doit, everything, is, awesome, when, youre, part, of, a, team'.split(', '); + // const editorModel = EditorModel.createFromString(`/** Copyright comment */\n \n${testLine}\n// Test comment\n${createLongString()}\n`); + // mockEditorModelLineTokens(editorModel); + + // const wordRangeMap = inlineValues.getWordToLineNumbersMap(editorModel); + // const words = Object.keys(wordRangeMap); + // assert.deepEqual(words, expectedWords); + // }); +}); + +// Test helpers + +function createExpression(name: string, value: string): IExpression { + return { + name, + value, + getId: () => name, + hasChildren: false, + getChildren: null + }; +} + +function createLongString(): string { + let longStr = ''; + for (let i = 0; i < 100; ++i) { + longStr += 'blah blah blah '; + } + return `"${longStr}"`; +} + +// Simple word range creator that maches wordRegex throughout string +// function getWordToLineMap(lineNumber: number, lineContent: string): Map { +// const result = new Map(); +// const wordRegexp = inlineValues.WORD_REGEXP; +// wordRegexp.lastIndex = 0; // Reset matching + +// while (true) { +// const wordMatch = wordRegexp.exec(lineContent); +// if (!wordMatch) { +// break; +// } +// const word = wordMatch[0]; + +// if (!result.has(word)) { +// result.set(word, []); +// } + +// result.get(word).push(lineNumber); +// } + +// return result; +// } + +interface MockToken { + tokenType: StandardTokenType; + startOffset: number; + endOffset: number; +} + +// // Simple tokenizer that separates comments from words +// function mockLineTokens(lineContent: string): LineTokens { +// const tokens: MockToken[] = []; + +// if (lineContent.match(/^\s*\/(\/|\*)/)) { +// tokens.push({ +// tokenType: StandardTokenType.Comment, +// startOffset: 0, +// endOffset: lineContent.length +// }); +// } +// // Tokenizer should ignore pure whitespace token +// else if (lineContent.match(/^\s+$/)) { +// tokens.push({ +// tokenType: StandardTokenType.Other, +// startOffset: 0, +// endOffset: lineContent.length +// }); +// } +// else { +// const wordRegexp = inlineValues.WORD_REGEXP; +// wordRegexp.lastIndex = 0; + +// while (true) { +// const wordMatch = wordRegexp.exec(lineContent); +// if (!wordMatch) { +// break; +// } + +// tokens.push({ +// tokenType: StandardTokenType.String, +// startOffset: wordMatch.index, +// endOffset: wordMatch.index + wordMatch[0].length +// }); +// } +// } + +// return { +// getLineContent: (): string => lineContent, +// getTokenCount: (): number => tokens.length, +// getTokenStartOffset: (tokenIndex: number): number => tokens[tokenIndex].startOffset, +// getTokenEndOffset: (tokenIndex: number): number => tokens[tokenIndex].endOffset, +// getStandardTokenType: (tokenIndex: number): StandardTokenType => tokens[tokenIndex].tokenType +// }; +// }; + +// function mockEditorModelLineTokens(editorModel: IModel): void { +// const linesContent = editorModel.getLinesContent(); +// editorModel.getLineTokens = (lineNumber: number): LineTokens => mockLineTokens(linesContent[lineNumber - 1]); +// } diff --git a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts index eba4ecb0047..e9280041341 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts @@ -18,7 +18,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/parts/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/parts/extensions/common/extensionsFileTemplate'; import { LocalExtensionType, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IMessageService } from 'vs/platform/message/common/message'; import { ToggleViewletAction } from 'vs/workbench/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -29,7 +29,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IWindowService } from 'vs/platform/windows/common/windows'; import { IExtensionService, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import URI from 'vs/base/common/uri'; - +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; export class InstallAction extends Action { @@ -1290,4 +1290,15 @@ export class EnableAllWorkpsaceAction extends Action { super.dispose(); this.disposables = dispose(this.disposables); } -} \ No newline at end of file +} + +CommandsRegistry.registerCommand('workbench.extensions.action.showLanguageExtensions', function (accessor: ServicesAccessor, fileExtension: string) { + const viewletService = accessor.get(IViewletService); + + return viewletService.openViewlet(VIEWLET_ID, true) + .then(viewlet => viewlet as IExtensionsViewlet) + .then(viewlet => { + viewlet.search(`ext:${fileExtension.replace(/^\./, '')}`); + viewlet.focus(); + }); +}); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts index e0ea965c2dd..2dd869dcd19 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts @@ -149,7 +149,7 @@ const enableAllWorkspaceAction = new SyncActionDescriptor(EnableAllWorkpsaceActi actionRegistry.registerWorkbenchAction(enableAllWorkspaceAction, 'Extensions: Enable All (Workspace)', ExtensionsLabel); const checkForUpdatesAction = new SyncActionDescriptor(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL); -actionRegistry.registerWorkbenchAction(checkForUpdatesAction, `Extensions: ${CheckForUpdatesAction.LABEL}`, ExtensionsLabel); +actionRegistry.registerWorkbenchAction(checkForUpdatesAction, `Extensions: Check for Updates`, ExtensionsLabel); Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css b/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css index 309f8da8801..42b78301463 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css +++ b/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css @@ -27,6 +27,12 @@ height: calc(100% - 38px); } +.extensions-viewlet > .extensions.hidden, +.extensions-viewlet > .message.hidden { + display: none; + visibility: hidden; +} + .extensions-viewlet > .message { padding: 5px 9px 5px 16px; cursor: default; diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 7dc3eb0d8a6..d7aeefd8da3 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -841,7 +841,14 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { } const extension = result.firstPage[0]; - this.open(extension).done(null, error => this.onError(error)); + const promises = [this.open(extension)]; + + if (this.local.every(local => local.identifier !== extension.identifier)) { + promises.push(this.install(extension)); + } + + TPromise.join(promises) + .done(null, error => this.onError(error)); }); } diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts index a8dae077849..9867c033843 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts @@ -24,7 +24,8 @@ import { IURLService } from 'vs/platform/url/common/url'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { Emitter } from 'vs/base/common/event'; import { IPager } from 'vs/base/common/paging'; -import { ITelemetryService, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { IWorkspaceContextService, WorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 7147b524286..38756fc05f3 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -25,7 +25,8 @@ import { IURLService } from 'vs/platform/url/common/url'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import Event, { Emitter } from 'vs/base/common/event'; import { IPager } from 'vs/base/common/paging'; -import { ITelemetryService, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService, WorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { IChoiceService } from 'vs/platform/message/common/message'; diff --git a/src/vs/workbench/parts/files/browser/media/explorerviewlet.css b/src/vs/workbench/parts/files/browser/media/explorerviewlet.css index 7429a126d46..f4eb9b1fdd1 100644 --- a/src/vs/workbench/parts/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/parts/files/browser/media/explorerviewlet.css @@ -48,6 +48,11 @@ visibility: hidden; } +.explorer-viewlet .header .monaco-count-badge.hidden { + display: none; + visibility: hidden; +} + .explorer-folders-view .monaco-tree-row > .content { display: inline-block; } diff --git a/src/vs/workbench/parts/files/browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/browser/views/explorerViewer.ts index 0679e33c368..e7ddeb72683 100644 --- a/src/vs/workbench/parts/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/browser/views/explorerViewer.ts @@ -652,10 +652,6 @@ export class FileSorter implements ISorter { return 1; } - if (statA.isDirectory && statB.isDirectory) { - return statA.name.toLowerCase().localeCompare(statB.name.toLowerCase()); - } - if (statA instanceof NewStatPlaceholder) { return -1; } diff --git a/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts b/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts index fe00a431112..a9b5b4215cd 100644 --- a/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts @@ -17,7 +17,7 @@ import { ITextFileService, AutoSaveMode, ModelState, TextFileModelChangeEvent } import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetry'; +import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetryUtils'; /** * A file editor input is the input type for the file editor of file system resources. diff --git a/src/vs/workbench/parts/html/browser/webview-pre.js b/src/vs/workbench/parts/html/browser/webview-pre.js index 94175dc2e91..d19b93a51ec 100644 --- a/src/vs/workbench/parts/html/browser/webview-pre.js +++ b/src/vs/workbench/parts/html/browser/webview-pre.js @@ -87,7 +87,7 @@ document.addEventListener("DOMContentLoaded", function (event) { ' while (node) {', ' if (node.tagName === "A" && node.href) {', ' let baseElement = window.document.getElementsByTagName("base")[0];', - ' if (baseElement && node.href.indexOf(baseElement.href) >= 0 && node.hash) {', + ' if (node.hash && (node.getAttribute("href") === node.hash || (baseElement && node.href.indexOf(baseElement.href) >= 0))) {', ' let scrollTarget = window.document.getElementById(node.hash.substr(1, node.hash.length - 1));', ' if (scrollTarget) {', ' scrollTarget.scrollIntoView();', diff --git a/src/vs/workbench/parts/indentation/common/indentationCommands.ts b/src/vs/workbench/parts/indentation/common/indentationCommands.ts deleted file mode 100644 index 4c2d164fc66..00000000000 --- a/src/vs/workbench/parts/indentation/common/indentationCommands.ts +++ /dev/null @@ -1,66 +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 { Range } from 'vs/editor/common/core/range'; -import { ICommand, ICursorStateComputerData, IEditOperationBuilder, ITokenizedModel } from 'vs/editor/common/editorCommon'; -import { Selection } from 'vs/editor/common/core/selection'; - -function getIndentationEditOperations(model: ITokenizedModel, builder: IEditOperationBuilder, tabSize: number, tabsToSpaces: boolean): void { - if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) { - // Model is empty - return; - } - - let spaces = ''; - for (let i = 0; i < tabSize; i++) { - spaces += ' '; - } - - const content = model.getLinesContent(); - for (let i = 0; i < content.length; i++) { - let lastIndentationColumn = model.getLineFirstNonWhitespaceColumn(i + 1); - if (lastIndentationColumn === 0) { - lastIndentationColumn = model.getLineMaxColumn(i + 1); - } - - const text = (tabsToSpaces ? content[i].substr(0, lastIndentationColumn).replace(/\t/ig, spaces) : - content[i].substr(0, lastIndentationColumn).replace(new RegExp(spaces, 'gi'), '\t')) + - content[i].substr(lastIndentationColumn); - - builder.addEditOperation(new Range(i + 1, 1, i + 1, model.getLineMaxColumn(i + 1)), text); - } -} - -export class IndentationToSpacesCommand implements ICommand { - - private selectionId: string; - - constructor(private selection: Selection, private tabSize: number) { } - - public getEditOperations(model: ITokenizedModel, builder: IEditOperationBuilder): void { - this.selectionId = builder.trackSelection(this.selection); - getIndentationEditOperations(model, builder, this.tabSize, true); - } - - public computeCursorState(model: ITokenizedModel, helper: ICursorStateComputerData): Selection { - return helper.getTrackedSelection(this.selectionId); - } -} - -export class IndentationToTabsCommand implements ICommand { - - private selectionId: string; - - constructor(private selection: Selection, private tabSize: number) { } - - public getEditOperations(model: ITokenizedModel, builder: IEditOperationBuilder): void { - this.selectionId = builder.trackSelection(this.selection); - getIndentationEditOperations(model, builder, this.tabSize, false); - } - - public computeCursorState(model: ITokenizedModel, helper: ICursorStateComputerData): Selection { - return helper.getTrackedSelection(this.selectionId); - } -} diff --git a/src/vs/workbench/parts/markers/browser/media/markers.css b/src/vs/workbench/parts/markers/browser/media/markers.css index 2cdcded90d2..b270a4d3e81 100644 --- a/src/vs/workbench/parts/markers/browser/media/markers.css +++ b/src/vs/workbench/parts/markers/browser/media/markers.css @@ -41,6 +41,12 @@ height: 100%; } +.markers-panel .markers-panel-container .tree-container.hidden, +.markers-panel .markers-panel-container .message-box-container.hidden { + display: none; + visibility: hidden; +} + .markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry { display: flex; line-height: 22px; diff --git a/src/vs/workbench/parts/preferences/browser/media/preferences.css b/src/vs/workbench/parts/preferences/browser/media/preferences.css index 6c31b27d5e7..b4c41817652 100644 --- a/src/vs/workbench/parts/preferences/browser/media/preferences.css +++ b/src/vs/workbench/parts/preferences/browser/media/preferences.css @@ -156,6 +156,11 @@ cursor: pointer; } +.monaco-editor .edit-preferences-widget.hidden { + display: none; + visibility: hidden; +} + .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/search/test/common/searchModel.test.ts b/src/vs/workbench/parts/search/test/common/searchModel.test.ts index ca52028f261..82990a8d2dd 100644 --- a/src/vs/workbench/parts/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/parts/search/test/common/searchModel.test.ts @@ -12,7 +12,8 @@ import { PPromise } from 'vs/base/common/winjs.base'; 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'; -import { ITelemetryService, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { Range } from 'vs/editor/common/core/range'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; diff --git a/src/vs/workbench/parts/search/test/common/searchResult.test.ts b/src/vs/workbench/parts/search/test/common/searchResult.test.ts index 15ad9a43fb7..07b7f09b73d 100644 --- a/src/vs/workbench/parts/search/test/common/searchResult.test.ts +++ b/src/vs/workbench/parts/search/test/common/searchResult.test.ts @@ -10,7 +10,8 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { Match, FileMatch, SearchResult, SearchModel } from 'vs/workbench/parts/search/common/searchModel'; import URI from 'vs/base/common/uri'; import { IFileMatch, ILineMatch } from 'vs/platform/search/common/search'; -import { ITelemetryService, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { Range } from 'vs/editor/common/core/range'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; diff --git a/src/vs/editor/contrib/suggest/common/snippetCompletion.ts b/src/vs/workbench/parts/snippets/common/snippetCompletion.ts similarity index 99% rename from src/vs/editor/contrib/suggest/common/snippetCompletion.ts rename to src/vs/workbench/parts/snippets/common/snippetCompletion.ts index 8fb21f6bb21..c3e70c3f794 100644 --- a/src/vs/editor/contrib/suggest/common/snippetCompletion.ts +++ b/src/vs/workbench/parts/snippets/common/snippetCompletion.ts @@ -120,4 +120,4 @@ class InsertSnippetAction extends EditorAction { } }); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts b/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts index 9c05c47a36c..135f5abc346 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts +++ b/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import 'vs/workbench/parts/snippets/common/snippetCompletion'; import nls = require('vs/nls'); import winjs = require('vs/base/common/winjs.base'); import paths = require('vs/base/common/paths'); diff --git a/src/vs/workbench/parts/tasks/common/languageServiceTaskSystem.ts b/src/vs/workbench/parts/tasks/common/languageServiceTaskSystem.ts deleted file mode 100644 index 3fb0a2240df..00000000000 --- a/src/vs/workbench/parts/tasks/common/languageServiceTaskSystem.ts +++ /dev/null @@ -1,115 +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 { TPromise, Promise } from 'vs/base/common/winjs.base'; -import { TerminateResponse } from 'vs/base/common/processes'; - -import { IMode } from 'vs/editor/common/modes'; -import { EventEmitter } from 'vs/base/common/eventEmitter'; - -import { ITaskSystem, ITaskSummary, TaskDescription, TelemetryEvent, Triggers, TaskConfiguration, ITaskExecuteResult, TaskExecuteKind } from 'vs/workbench/parts/tasks/common/taskSystem'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IModeService } from 'vs/editor/common/services/modeService'; - -export interface LanguageServiceTaskConfiguration extends TaskConfiguration { - modes: string[]; -} - -export class LanguageServiceTaskSystem extends EventEmitter implements ITaskSystem { - - public static TelemetryEventName: string = 'taskService'; - - private configuration: LanguageServiceTaskConfiguration; - private telemetryService: ITelemetryService; - private modeService: IModeService; - - constructor(configuration: LanguageServiceTaskConfiguration, telemetryService: ITelemetryService, modeService: IModeService) { - super(); - this.configuration = configuration; - this.telemetryService = telemetryService; - this.modeService = modeService; - } - - public build(): ITaskExecuteResult { - return this.processMode((mode) => { - return null; - }, 'build', Triggers.shortcut); - } - - public rebuild(): ITaskExecuteResult { - return this.processMode((mode) => { - return null; - }, 'rebuild', Triggers.shortcut); - } - - public clean(): ITaskExecuteResult { - return this.processMode((mode) => { - return null; - }, 'clean', Triggers.shortcut); - } - - public runTest(): ITaskExecuteResult { - return { kind: TaskExecuteKind.Started, promise: TPromise.wrapError('Not implemented yet.') }; - } - - public run(taskIdentifier: string): ITaskExecuteResult { - return { kind: TaskExecuteKind.Started, promise: TPromise.wrapError('Not implemented yet.') }; - } - - public isActive(): TPromise { - return TPromise.as(false); - } - - public isActiveSync(): boolean { - return false; - } - - public canAutoTerminate(): boolean { - return false; - } - - public terminate(): TPromise { - return TPromise.as({ success: true }); - } - - public terminateSync(): TerminateResponse { - return { success: true }; - } - - public tasks(): TPromise { - let result: TaskDescription[] = []; - return TPromise.as(result); - } - - private processMode(fn: (mode: IMode) => Promise, taskName: string, trigger: string): ITaskExecuteResult { - let telemetryEvent: TelemetryEvent = { - trigger: trigger, - command: 'languageService', - success: true - }; - return { - kind: TaskExecuteKind.Started, started: {}, promise: Promise.join(this.configuration.modes.map((mode) => { - return this.modeService.getOrCreateMode(mode); - })).then((modes: IMode[]) => { - let promises: Promise[] = []; - modes.forEach((mode) => { - let promise = fn(mode); - if (promise) { - promises.push(promise); - } - }); - return Promise.join(promises); - }).then((value) => { - this.telemetryService.publicLog(LanguageServiceTaskSystem.TelemetryEventName, telemetryEvent); - return value; - }, (err) => { - telemetryEvent.success = false; - this.telemetryService.publicLog(LanguageServiceTaskSystem.TelemetryEventName, telemetryEvent); - return Promise.wrapError(err); - }) - }; - } -} diff --git a/src/vs/workbench/parts/tasks/common/taskSystem.ts b/src/vs/workbench/parts/tasks/common/taskSystem.ts index 420f753685c..186e190fbe8 100644 --- a/src/vs/workbench/parts/tasks/common/taskSystem.ts +++ b/src/vs/workbench/parts/tasks/common/taskSystem.ts @@ -98,9 +98,9 @@ export interface TaskDescription { args?: string[]; /** - * Whether the task is running in watching mode or not. + * Whether the task is a background task or not. */ - isWatching?: boolean; + isBackground?: boolean; /** * Whether the task should prompt on close for confirmation if running. @@ -201,7 +201,7 @@ export interface ITaskExecuteResult { }; active?: { same: boolean; - watching: boolean; + background: boolean; }; } @@ -242,5 +242,5 @@ export interface TaskConfiguration { /** * The build system to use. If omitted program is used. */ - buildSystem?: string; + _runner?: string; } \ No newline at end of file diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 57bbc37b561..282718bc334 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -59,13 +59,15 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IOutputService, IOutputChannelRegistry, Extensions as OutputExt, IOutputChannel } from 'vs/workbench/parts/output/common/output'; +import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal'; + import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TaskConfiguration, TaskDescription, TaskSystemEvents } from 'vs/workbench/parts/tasks/common/taskSystem'; import { ITaskService, TaskServiceEvents } from 'vs/workbench/parts/tasks/common/taskService'; import { templates as taskTemplates } from 'vs/workbench/parts/tasks/common/taskTemplates'; -import { LanguageServiceTaskSystem, LanguageServiceTaskConfiguration } from 'vs/workbench/parts/tasks/common/languageServiceTaskSystem'; import * as FileConfig from 'vs/workbench/parts/tasks/node/processRunnerConfiguration'; import { ProcessRunnerSystem } from 'vs/workbench/parts/tasks/node/processRunnerSystem'; +import { TerminalTaskSystem } from './terminalTaskSystem'; import { ProcessRunnerDetector } from 'vs/workbench/parts/tasks/node/processRunnerDetector'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -631,7 +633,9 @@ class TaskService extends EventEmitter implements ITaskService { @IModelService modelService: IModelService, @IExtensionService extensionService: IExtensionService, @IQuickOpenService quickOpenService: IQuickOpenService, @IEnvironmentService private environmentService: IEnvironmentService, - @IConfigurationResolverService private configurationResolverService: IConfigurationResolverService) { + @IConfigurationResolverService private configurationResolverService: IConfigurationResolverService, + @ITerminalService private terminalService: ITerminalService + ) { super(); this.modeService = modeService; @@ -740,10 +744,15 @@ class TaskService extends EventEmitter implements ITaskService { throw new TaskError(Severity.Info, nls.localize('TaskSystem.noConfiguration', 'No task runner configured.'), TaskErrors.NotConfigured); } let result: ITaskSystem = null; - if (config.buildSystem === 'service') { - result = new LanguageServiceTaskSystem(config, this.telemetryService, this.modeService); - } else if (this.isRunnerConfig(config)) { + if (this.isRunnerConfig(config)) { result = new ProcessRunnerSystem(config, this.markerService, this.modelService, this.telemetryService, this.outputService, this.configurationResolverService, TaskService.OutputChannelId, clearOutput); + } else if (this.isTerminalConfig(config)) { + result = new TerminalTaskSystem( + config, + this.terminalService, this.outputService, this.markerService, + this.modelService, this.configurationResolverService, this.telemetryService, + TaskService.OutputChannelId + ); } if (result === null) { this._taskSystemPromise = null; @@ -776,7 +785,11 @@ class TaskService extends EventEmitter implements ITaskService { } private isRunnerConfig(config: TaskConfiguration): boolean { - return !config.buildSystem || config.buildSystem === 'program'; + return !config._runner || config._runner === 'program'; + } + + private isTerminalConfig(config: TaskConfiguration): boolean { + return config._runner === 'terminal'; } private hasDetectorSupport(config: FileConfig.ExternalTaskRunnerConfiguration): boolean { @@ -819,14 +832,14 @@ class TaskService extends EventEmitter implements ITaskService { } private executeTarget(fn: (taskSystem: ITaskSystem) => ITaskExecuteResult): TPromise { - return this.textFileService.saveAll().then((value) => { // make sure all dirty files are saved - return this.configurationService.reloadConfiguration().then(() => { // make sure configuration is up to date + return this.textFileService.saveAll().then((value) => { // make sure all dirty files are saved + return this.configurationService.reloadConfiguration().then(() => { // make sure configuration is up to date return this.taskSystemPromise. then((taskSystem) => { let executeResult = fn(taskSystem); if (executeResult.kind === TaskExecuteKind.Active) { let active = executeResult.active; - if (active.same && active.watching) { + if (active.same && active.background) { this.messageService.show(Severity.Info, nls.localize('TaskSystem.activeSame', 'The task is already active and in watch mode. To terminate the task use `F1 > terminate task`')); } else { throw new TaskError(Severity.Warning, nls.localize('TaskSystem.active', 'There is an active running task right now. Terminate it first before executing another task.'), TaskErrors.RunningTask); diff --git a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts new file mode 100644 index 00000000000..5b54dec1da2 --- /dev/null +++ b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts @@ -0,0 +1,464 @@ +/*--------------------------------------------------------------------------------------------- + * 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 path = require('path'); + +import * as nls from 'vs/nls'; +import * as Objects from 'vs/base/common/objects'; +import { CharCode } from 'vs/base/common/charCode'; +import * as Platform from 'vs/base/common/platform'; +import * as Async from 'vs/base/common/async'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IStringDictionary } from 'vs/base/common/collections'; +import Severity from 'vs/base/common/severity'; +import { EventEmitter } from 'vs/base/common/eventEmitter'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { TerminateResponse } from 'vs/base/common/processes'; + +import { IMarkerService } from 'vs/platform/markers/common/markers'; +import { ValidationStatus } from 'vs/base/common/parsers'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { ProblemMatcher } from 'vs/platform/markers/common/problemMatcher'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; + +import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { ITerminalService, ITerminalInstance, IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal'; +import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; +import { IOutputService, IOutputChannel } from 'vs/workbench/parts/output/common/output'; +import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEvents } from 'vs/workbench/parts/tasks/common/problemCollectors'; +import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TaskRunnerConfiguration, TaskDescription, ShowOutput, TelemetryEvent, Triggers, TaskSystemEvents, TaskEvent, TaskType } from 'vs/workbench/parts/tasks/common/taskSystem'; +import * as FileConfig from '../node/processRunnerConfiguration'; + +interface TerminalData { + terminal: ITerminalInstance; + promise: TPromise; +} + +class TerminalDecoder { + // See https://en.wikipedia.org/wiki/ANSI_escape_code & http://stackoverflow.com/questions/25189651/how-to-remove-ansi-control-chars-vt100-from-a-java-string & + // https://www.npmjs.com/package/strip-ansi + private static ANSI_CONTROL_SEQUENCE: RegExp = /\x1b[[()#;?]*(?:\d{1,4}(?:;\d{0,4})*)?[0-9A-ORZcf-nqry=><]/g; + + private remaining: string; + + public write(data: string): string[] { + let result: string[] = []; + let value = this.remaining + ? this.remaining + data.replace(TerminalDecoder.ANSI_CONTROL_SEQUENCE, '') + : data.replace(TerminalDecoder.ANSI_CONTROL_SEQUENCE, ''); + + if (value.length < 1) { + return result; + } + let start = 0; + let ch: number; + while (start < value.length && ((ch = value.charCodeAt(start)) === CharCode.CarriageReturn || ch === CharCode.LineFeed)) { + start++; + } + let idx = start; + while (idx < value.length) { + ch = value.charCodeAt(idx); + if (ch === CharCode.CarriageReturn || ch === CharCode.LineFeed) { + result.push(value.substring(start, idx)); + idx++; + while (idx < value.length && ((ch = value.charCodeAt(idx)) === CharCode.CarriageReturn || ch === CharCode.LineFeed)) { + idx++; + } + start = idx; + } else { + idx++; + } + } + this.remaining = start < value.length ? value.substr(start) : null; + return result; + } + + public end(): string { + return this.remaining; + } +} +export class TerminalTaskSystem extends EventEmitter implements ITaskSystem { + + public static TelemetryEventName: string = 'taskService'; + + private validationStatus: ValidationStatus; + private buildTaskIdentifier: string; + private testTaskIdentifier: string; + private configuration: TaskRunnerConfiguration; + + private outputChannel: IOutputChannel; + private activeTasks: IStringDictionary; + + constructor(private fileConfig: FileConfig.ExternalTaskRunnerConfiguration, private terminalService: ITerminalService, private outputService: IOutputService, + private markerService: IMarkerService, private modelService: IModelService, private configurationResolverService: IConfigurationResolverService, + private telemetryService: ITelemetryService, outputChannelId: string) { + super(); + + this.outputChannel = this.outputService.getChannel(outputChannelId); + this.clearOutput(); + this.activeTasks = Object.create(null); + + let parseResult = FileConfig.parse(fileConfig, this); + this.validationStatus = parseResult.validationStatus; + this.configuration = parseResult.configuration; + this.buildTaskIdentifier = parseResult.defaultBuildTaskIdentifier; + this.testTaskIdentifier = parseResult.defaultTestTaskIdentifier; + + if (!this.validationStatus.isOK()) { + this.showOutput(); + } + } + + public log(value: string): void { + this.outputChannel.append(value + '\n'); + } + + private showOutput(): void { + this.outputChannel.show(true); + } + + private clearOutput(): void { + this.outputChannel.clear(); + } + + public build(): ITaskExecuteResult { + if (!this.buildTaskIdentifier) { + throw new TaskError(Severity.Info, nls.localize('TerminalTaskSystem.noBuildTask', 'No build task defined in tasks.json'), TaskErrors.NoBuildTask); + } + return this.run(this.buildTaskIdentifier, Triggers.shortcut); + } + + public rebuild(): ITaskExecuteResult { + throw new Error('Task - Rebuild: not implemented yet'); + } + + public clean(): ITaskExecuteResult { + throw new Error('Task - Clean: not implemented yet'); + } + + public runTest(): ITaskExecuteResult { + if (!this.testTaskIdentifier) { + throw new TaskError(Severity.Info, nls.localize('TerminalTaskSystem.noTestTask', 'No test task defined in tasks.json'), TaskErrors.NoTestTask); + } + return this.run(this.testTaskIdentifier, Triggers.shortcut); + } + + public run(taskIdentifier: string, trigger: string = Triggers.command): ITaskExecuteResult { + let task = this.configuration.tasks[taskIdentifier]; + if (!task) { + throw new TaskError(Severity.Info, nls.localize('TerminalTaskSystem.noTask', 'Task \'{0}\' not found', taskIdentifier), TaskErrors.TaskNotFound); + } + let telemetryEvent: TelemetryEvent = { + trigger: trigger, + command: 'other', + success: true + }; + try { + let result = this.executeTask(task, telemetryEvent); + result.promise = result.promise.then((summary) => { + this.telemetryService.publicLog(TerminalTaskSystem.TelemetryEventName, telemetryEvent); + return summary; + }, (error) => { + telemetryEvent.success = false; + this.telemetryService.publicLog(TerminalTaskSystem.TelemetryEventName, telemetryEvent); + return TPromise.wrapError(error); + }); + return result; + } catch (error) { + telemetryEvent.success = false; + this.telemetryService.publicLog(TerminalTaskSystem.TelemetryEventName, telemetryEvent); + if (error instanceof TaskError) { + throw error; + } else if (error instanceof Error) { + this.log(error.message); + throw new TaskError(Severity.Error, error.message, TaskErrors.UnknownError); + } else { + this.log(error.toString()); + throw new TaskError(Severity.Error, nls.localize('TerminalTaskSystem.unknownError', 'A unknown error has occurred while executing a task. See task output log for details.'), TaskErrors.UnknownError); + } + } + } + + public isActive(): TPromise { + return TPromise.as(this.isActiveSync()); + } + + public isActiveSync(): boolean { + return Object.keys(this.activeTasks).length > 0; + } + + public canAutoTerminate(): boolean { + return Object.keys(this.activeTasks).every(key => this.configuration.tasks[key].isBackground); + } + + public terminate(): TPromise { + Object.keys(this.activeTasks).forEach((key) => { + let data = this.activeTasks[key]; + data.terminal.dispose(); + }); + this.activeTasks = Object.create(null); + return TPromise.as({ success: true }); + } + + public tasks(): TPromise { + let result: TaskDescription[]; + if (!this.configuration || !this.configuration.tasks) { + result = []; + } else { + result = Object.keys(this.configuration.tasks).map(key => this.configuration.tasks[key]); + } + return TPromise.as(result); + } + + private executeTask(task: TaskDescription, telemetryEvent: TelemetryEvent): ITaskExecuteResult { + let terminalData = this.activeTasks[task.id]; + if (terminalData && terminalData.promise) { + if (task.showOutput === ShowOutput.Always) { + terminalData.terminal.setVisible(true); + } + return { kind: TaskExecuteKind.Active, active: { same: true, background: task.isBackground }, promise: terminalData.promise }; + } else { + let terminal: ITerminalInstance = undefined; + let promise: TPromise = undefined; + if (task.isBackground) { + promise = new TPromise((resolve, reject) => { + let watchingProblemMatcher = new WatchingProblemCollector(this.resolveMatchers(task.problemMatchers), this.markerService, this.modelService); + let toUnbind: IDisposable[] = []; + let event: TaskEvent = { taskId: task.id, taskName: task.name, type: TaskType.Watching }; + let eventCounter: number = 0; + toUnbind.push(watchingProblemMatcher.addListener2(ProblemCollectorEvents.WatchingBeginDetected, () => { + eventCounter++; + this.emit(TaskSystemEvents.Active, event); + })); + toUnbind.push(watchingProblemMatcher.addListener2(ProblemCollectorEvents.WatchingEndDetected, () => { + eventCounter--; + this.emit(TaskSystemEvents.Inactive, event); + })); + watchingProblemMatcher.aboutToStart(); + let delayer: Async.Delayer = null; + let decoder = new TerminalDecoder(); + terminal = this.createTerminal(task); + terminal.onData((data: string) => { + decoder.write(data).forEach(line => { + watchingProblemMatcher.processLine(line); + if (delayer === null) { + delayer = new Async.Delayer(3000); + } + delayer.trigger(() => { + watchingProblemMatcher.forceDelivery(); + delayer = null; + }); + }); + }); + terminal.onExit((exitCode) => { + watchingProblemMatcher.dispose(); + toUnbind = dispose(toUnbind); + toUnbind = null; + for (let i = 0; i < eventCounter; i++) { + this.emit(TaskSystemEvents.Inactive, event); + } + eventCounter = 0; + if (exitCode && exitCode === 1 && watchingProblemMatcher.numberOfMatches === 0 && task.showOutput !== ShowOutput.Never) { + this.terminalService.setActiveInstance(terminal); + this.terminalService.showPanel(false); + } + resolve({ exitCode }); + }); + }); + } else { + promise = new TPromise((resolve, reject) => { + terminal = this.createTerminal(task); + this.emit(TaskSystemEvents.Active, event); + let decoder = new TerminalDecoder(); + let startStopProblemMatcher = new StartStopProblemCollector(this.resolveMatchers(task.problemMatchers), this.markerService, this.modelService); + terminal.onData((data: string) => { + decoder.write(data).forEach((line) => { + startStopProblemMatcher.processLine(line); + }); + }); + terminal.onExit((exitCode) => { + startStopProblemMatcher.processLine(decoder.end()); + startStopProblemMatcher.done(); + startStopProblemMatcher.dispose(); + this.emit(TaskSystemEvents.Inactive, event); + delete this.activeTasks[task.id]; + resolve({ exitCode }); + }); + this.terminalService.setActiveInstance(terminal); + if (task.showOutput === ShowOutput.Always) { + this.terminalService.showPanel(false); + } + }); + } + this.terminalService.setActiveInstance(terminal); + if (task.showOutput === ShowOutput.Always) { + this.terminalService.showPanel(false); + } + this.activeTasks[task.id] = { terminal, promise }; + return { kind: TaskExecuteKind.Started, started: {}, promise: promise }; + } + } + + private createTerminal(task: TaskDescription): ITerminalInstance { + let { command, args } = this.resolveCommandAndArgs(task); + let terminalName = nls.localize('TerminalTaskSystem.terminalName', 'Task - {0}', task.name); + let waitOnExit = task.showOutput !== ShowOutput.Never || !task.isBackground; + if (this.configuration.isShellCommand) { + // TODO@dirk: don't we want to use cmd.exe (32- or 64-bit) all the time? Also you can now + // not set IShellLaunchConfig.executable which will grab it from settings. + let shellConfig: IShellLaunchConfig = { executable: null, args: null }; + (this.terminalService.configHelper as TerminalConfigHelper).mergeDefaultShellPathAndArgs(shellConfig); + let shellArgs = shellConfig.args.slice(0); + let toAdd: string[] = []; + let commandLine: string; + if (Platform.isWindows) { + toAdd.push('/d', '/c'); + let quotedCommand: boolean = false; + let quotedArg: boolean = false; + let quoted = this.ensureDoubleQuotes(command); + let commandPieces: string[] = []; + commandPieces.push(quoted.value); + quotedCommand = quoted.quoted; + if (args) { + args.forEach((arg) => { + quoted = this.ensureDoubleQuotes(arg); + commandPieces.push(quoted.value); + quotedArg = quotedArg && quoted.quoted; + }); + } + if (quotedCommand) { + if (quotedArg) { + commandLine = '"' + commandPieces.join(' ') + '"'; + } else { + if (commandPieces.length > 1) { + commandLine = '"' + commandPieces[0] + '"' + ' ' + commandPieces.slice(1).join(' '); + } else { + commandLine = '"' + commandPieces[0] + '"'; + } + } + } else { + commandLine = commandPieces.join(' '); + } + } else { + toAdd.push('-c'); + commandLine = `${command} ${args.join(' ')}`; + } + toAdd.forEach(element => { + if (!shellArgs.some(arg => arg.toLowerCase() === element)) { + shellArgs.push(element); + } + }); + shellArgs.push(commandLine); + const shellLaunchConfig: IShellLaunchConfig = { + name: terminalName, + executable: shellConfig.executable, + args: shellArgs, + waitOnExit + }; + return this.terminalService.createInstance(shellLaunchConfig); + } else { + const shellLaunchConfig: IShellLaunchConfig = { + name: terminalName, + executable: command, + args, + waitOnExit + }; + return this.terminalService.createInstance(shellLaunchConfig); + } + } + + private resolveCommandAndArgs(task: TaskDescription): { command: string, args: string[] } { + let args: string[] = this.configuration.args ? this.configuration.args.slice() : []; + // We need to first pass the task name + if (!task.suppressTaskName) { + if (this.fileConfig.taskSelector) { + args.push(this.fileConfig.taskSelector + task.name); + } else { + args.push(task.name); + } + } + // And then additional arguments + if (task.args) { + args = args.concat(task.args); + } + args = this.resolveVariables(args); + let command: string = this.resolveVariable(this.configuration.command); + return { command, args }; + } + + + private resolveVariables(value: string[]): string[] { + return value.map(s => this.resolveVariable(s)); + } + + private resolveMatchers(values: T[]): T[] { + if (values.length === 0) { + return values; + } + let result: T[] = []; + values.forEach((matcher) => { + if (!matcher.filePrefix) { + result.push(matcher); + } else { + let copy = Objects.clone(matcher); + copy.filePrefix = this.resolveVariable(copy.filePrefix); + result.push(copy); + } + }); + return result; + } + + private resolveVariable(value: string): string { + return this.configurationResolverService.resolve(value); + } + + private static doubleQuotes = /^[^"].* .*[^"]$/; + private ensureDoubleQuotes(value: string) { + if (TerminalTaskSystem.doubleQuotes.test(value)) { + return { + value: '"' + value + '"', + quoted: true + }; + } else { + return { + value: value, + quoted: value.length > 0 && value[0] === '"' && value[value.length - 1] === '"' + }; + } + } + + private static WellKnowCommands: IStringDictionary = { + 'ant': true, + 'cmake': true, + 'eslint': true, + 'gradle': true, + 'grunt': true, + 'gulp': true, + 'jake': true, + 'jenkins': true, + 'jshint': true, + 'make': true, + 'maven': true, + 'msbuild': true, + 'msc': true, + 'nmake': true, + 'npm': true, + 'rake': true, + 'tsc': true, + 'xbuild': true + }; + public getSanitizedCommand(cmd: string): string { + let result = cmd.toLowerCase(); + let index = result.lastIndexOf(path.sep); + if (index !== -1) { + result = result.substring(index + 1); + } + if (TerminalTaskSystem.WellKnowCommands[result]) { + return result; + } + return 'other'; + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/tasks/node/processRunnerConfiguration.ts b/src/vs/workbench/parts/tasks/node/processRunnerConfiguration.ts index 54ed1f79ee5..6634b8455fe 100644 --- a/src/vs/workbench/parts/tasks/node/processRunnerConfiguration.ts +++ b/src/vs/workbench/parts/tasks/node/processRunnerConfiguration.ts @@ -451,7 +451,7 @@ class ConfigurationParser { name: globals.command, showOutput: globals.showOutput, suppressTaskName: true, - isWatching: isWatching, + isBackground: isWatching, promptOnClose: promptOnClose, echoCommand: globals.echoCommand, }; @@ -539,15 +539,15 @@ class ConfigurationParser { if (Types.isStringArray(externalTask.args)) { task.args = externalTask.args.slice(); } - task.isWatching = false; + task.isBackground = false; if (!Types.isUndefined(externalTask.isWatching)) { - task.isWatching = !!externalTask.isWatching; + task.isBackground = !!externalTask.isWatching; } task.promptOnClose = true; if (!Types.isUndefined(externalTask.promptOnClose)) { task.promptOnClose = !!externalTask.promptOnClose; } else { - task.promptOnClose = !task.isWatching; + task.promptOnClose = !task.isBackground; } if (Types.isString(externalTask.showOutput)) { task.showOutput = TaskSystem.ShowOutput.fromString(externalTask.showOutput); diff --git a/src/vs/workbench/parts/tasks/node/processRunnerSystem.ts b/src/vs/workbench/parts/tasks/node/processRunnerSystem.ts index 9a90c03d842..643d5bcc4b5 100644 --- a/src/vs/workbench/parts/tasks/node/processRunnerSystem.ts +++ b/src/vs/workbench/parts/tasks/node/processRunnerSystem.ts @@ -90,7 +90,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { public build(): ITaskExecuteResult { if (this.activeTaskIdentifier) { let task = this.configuration.tasks[this.activeTaskIdentifier]; - return { kind: TaskExecuteKind.Active, active: { same: this.activeTaskIdentifier === this.defaultBuildTaskIdentifier, watching: task.isWatching }, promise: this.activeTaskPromise }; + return { kind: TaskExecuteKind.Active, active: { same: this.activeTaskIdentifier === this.defaultBuildTaskIdentifier, background: task.isBackground }, promise: this.activeTaskPromise }; } if (!this.defaultBuildTaskIdentifier) { throw new TaskError(Severity.Info, nls.localize('TaskRunnerSystem.noBuildTask', 'No task is marked as a build task in the tasks.json. Mark a task with \'isBuildCommand\'.'), TaskErrors.NoBuildTask); @@ -109,7 +109,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { public runTest(): ITaskExecuteResult { if (this.activeTaskIdentifier) { let task = this.configuration.tasks[this.activeTaskIdentifier]; - return { kind: TaskExecuteKind.Active, active: { same: this.activeTaskIdentifier === this.defaultTestTaskIdentifier, watching: task.isWatching }, promise: this.activeTaskPromise }; + return { kind: TaskExecuteKind.Active, active: { same: this.activeTaskIdentifier === this.defaultTestTaskIdentifier, background: task.isBackground }, promise: this.activeTaskPromise }; } if (!this.defaultTestTaskIdentifier) { throw new TaskError(Severity.Info, nls.localize('TaskRunnerSystem.noTestTask', 'No test task configured.'), TaskErrors.NoTestTask); @@ -120,7 +120,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { public run(taskIdentifier: string): ITaskExecuteResult { if (this.activeTaskIdentifier) { let task = this.configuration.tasks[this.activeTaskIdentifier]; - return { kind: TaskExecuteKind.Active, active: { same: this.activeTaskIdentifier === taskIdentifier, watching: task.isWatching }, promise: this.activeTaskPromise }; + return { kind: TaskExecuteKind.Active, active: { same: this.activeTaskIdentifier === taskIdentifier, background: task.isBackground }, promise: this.activeTaskPromise }; } return this.executeTask(taskIdentifier); } @@ -239,7 +239,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { let prompt: string = Platform.isWindows ? '>' : '$'; this.log(`running command${prompt} ${command} ${args.join(' ')}`); } - if (task.isWatching) { + if (task.isBackground) { let watchingProblemMatcher = new WatchingProblemCollector(this.resolveMatchers(task.problemMatchers), this.markerService, this.modelService); let toUnbind: IDisposable[] = []; let event: TaskEvent = { taskId: task.id, taskName: task.name, type: TaskType.Watching }; diff --git a/src/vs/workbench/parts/tasks/test/node/configuration.test.ts b/src/vs/workbench/parts/tasks/test/node/configuration.test.ts index 2cd2709a8d8..4b83891a7b0 100644 --- a/src/vs/workbench/parts/tasks/test/node/configuration.test.ts +++ b/src/vs/workbench/parts/tasks/test/node/configuration.test.ts @@ -73,7 +73,7 @@ class TaskBuilder { showOutput: TaskSystem.ShowOutput.Always, suppressTaskName: false, echoCommand: false, - isWatching: false, + isBackground: false, promptOnClose: true, problemMatchers: [] }; @@ -99,8 +99,8 @@ class TaskBuilder { return this; } - public isWatching(value: boolean): TaskBuilder { - this.result.isWatching = value; + public isBackground(value: boolean): TaskBuilder { + this.result.isBackground = value; return this; } @@ -307,7 +307,7 @@ suite('Tasks Configuration parsing tests', () => { let builder = new ConfiguationBuilder('tsc'); builder.task('tsc'). suppressTaskName(true). - isWatching(true). + isBackground(true). promptOnClose(false); testGobalCommand( { @@ -627,7 +627,7 @@ suite('Tasks Configuration parsing tests', () => { showOutput(TaskSystem.ShowOutput.Never). echoCommand(true). args(['--p']). - isWatching(true). + isBackground(true). promptOnClose(false); let result = testConfiguration(external, builder); @@ -833,7 +833,7 @@ suite('Tasks Configuration parsing tests', () => { ] }; let builder = new ConfiguationBuilder('tsc'); - builder.task('taskName').isWatching(true).promptOnClose(false); + builder.task('taskName').isBackground(true).promptOnClose(false); testConfiguration(external, builder); }); @@ -943,7 +943,7 @@ suite('Tasks Configuration parsing tests', () => { assert.strictEqual(actual.showOutput, expected.showOutput, 'showOutput'); assert.strictEqual(actual.suppressTaskName, expected.suppressTaskName, 'suppressTaskName'); assert.strictEqual(actual.echoCommand, expected.echoCommand, 'echoCommand'); - assert.strictEqual(actual.isWatching, expected.isWatching, 'isWatching'); + assert.strictEqual(actual.isBackground, expected.isBackground, 'isBackground'); assert.strictEqual(actual.promptOnClose, expected.promptOnClose, 'promptOnClose'); assert.strictEqual(typeof actual.problemMatchers, typeof expected.problemMatchers); if (actual.problemMatchers && expected.problemMatchers) { diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index e03abc7ba4d..aaff277951a 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -6,7 +6,6 @@ import Event from 'vs/base/common/event'; import platform = require('vs/base/common/platform'); -import processes = require('vs/base/node/processes'); import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { TPromise } from 'vs/base/common/winjs.base'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -17,7 +16,6 @@ export const TERMINAL_SERVICE_ID = 'terminalService'; export const TERMINAL_DEFAULT_SHELL_LINUX = !platform.isWindows ? (process.env.SHELL || 'sh') : 'sh'; export const TERMINAL_DEFAULT_SHELL_OSX = !platform.isWindows ? (process.env.SHELL || 'sh') : 'sh'; -export const TERMINAL_DEFAULT_SHELL_WINDOWS = processes.getWindowsShell(); export const TERMINAL_DEFAULT_RIGHT_CLICK_COPY_PASTE = platform.isWindows; @@ -80,11 +78,28 @@ export interface ITerminalFont { } export interface IShellLaunchConfig { - executable: string; - args: string[]; - /** Whether to ignore a custom cwd (if the shell is being launched by an extension) */ - ignoreCustomCwd?: boolean; - /** Whether to wait for a key press before closing the terminal */ + /** The name of the terminal, this this is not set the name of the process will be used. */ + name?: string; + /** The shell executable (bash, cmd, etc.). */ + executable?: string; + /** The CLI arguments to use with executable. */ + args?: string[]; + /** + * The current working directory of the terminal, this overrides the `terminal.integrated.cwd` + * settings key. + */ + cwd?: string; + /** + * A custom environment for the terminal, if this is not set the environment will be inherited + * from the VS Code process. + */ + env?: { [key: string]: string }; + /** + * Whether to ignore a custom cwd from the `terminal.integrated.cwd` settings key (eg. if the + * shell is being launched by an extension). + */ + ignoreConfigurationCwd?: boolean; + /** Whether to wait for a key press before closing the terminal. */ waitOnExit?: boolean; } @@ -100,7 +115,7 @@ export interface ITerminalService { onInstanceTitleChanged: Event; terminalInstances: ITerminalInstance[]; - createInstance(name?: string, shellPath?: string, shellArgs?: string[], waitOnExit?: boolean, ignoreCustomCwd?: boolean): ITerminalInstance; + createInstance(shell?: IShellLaunchConfig): ITerminalInstance; getInstanceFromId(terminalId: number): ITerminalInstance; getInstanceLabels(): string[]; getActiveInstance(): ITerminalInstance; diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css b/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css index 031c18fbda5..5fbbe5a1485 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css @@ -40,7 +40,7 @@ .monaco-workbench .panel.integrated-terminal .xterm-viewport { /* Align the viewport to the bottom of the panel, just like the terminal */ position: absolute; - right: 0; + right: -20px; bottom: 0; left: 0; } 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 f6c97a0c0b4..1f92ef33775 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css @@ -82,14 +82,14 @@ outline-offset: -1px; } -.monaco-workbench .panel.integrated-terminal .xterm.focus .terminal-cursor.blinking, -.monaco-workbench .panel.integrated-terminal .xterm:focus .terminal-cursor.blinking { animation: blink-cursor 1.2s infinite step-end; } -.vs-dark .monaco-workbench .panel.integrated-terminal .xterm.focus .terminal-cursor.blinking, -.vs-dark .monaco-workbench .panel.integrated-terminal .xterm:focus .terminal-cursor.blinking { animation: blink-cursor-dark 1.2s infinite step-end; } -.hc-black .monaco-workbench .panel.integrated-terminal .xterm.focus .terminal-cursor.blinking, -.hc-black .monaco-workbench .panel.integrated-terminal .xterm:focus .terminal-cursor.blinking { animation: blink-cursor-hc-black 1.2s infinite step-end; } +.monaco-workbench .panel.integrated-terminal .xterm.xterm-cursor-blink.focus .terminal-cursor, +.monaco-workbench .panel.integrated-terminal .xterm.xterm-cursor-blink:focus .terminal-cursor { animation: cursor-blink 1.2s infinite step-end; } +.vs-dark .monaco-workbench .panel.integrated-terminal .xterm.xterm-cursor-blink.focus .terminal-cursor, +.vs-dark .monaco-workbench .panel.integrated-terminal .xterm.xterm-cursor-blink:focus .terminal-cursor { animation: cursor-blink-dark 1.2s infinite step-end; } +.hc-black .monaco-workbench .panel.integrated-terminal .xterm.xterm-cursor-blink.focus .terminal-cursor, +.hc-black .monaco-workbench .panel.integrated-terminal .xterm.xterm-cursor-blink:focus .terminal-cursor { animation: cursor-blink-hc-black 1.2s infinite step-end; } -@keyframes blink-cursor { +@keyframes cursor-blink { 0% { background-color: #333; color: #CCC; @@ -100,7 +100,7 @@ } } -@keyframes blink-cursor-dark { +@keyframes cursor-blink-dark { 0% { background-color: #CCC; color: #1e1e1e; @@ -111,7 +111,7 @@ } } -@keyframes blink-cursor-hc-black { +@keyframes cursor-blink-hc-black { 0% { background-color: #fff; color: #000; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts index f4fee11ede3..f2993220872 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts @@ -11,7 +11,8 @@ import * as platform from 'vs/base/common/platform'; import nls = require('vs/nls'); import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { GlobalQuickOpenAction } from 'vs/workbench/browser/parts/quickopen/quickopen.contribution'; -import { ITerminalService, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, TERMINAL_DEFAULT_SHELL_LINUX, TERMINAL_DEFAULT_SHELL_OSX, TERMINAL_DEFAULT_SHELL_WINDOWS, TERMINAL_DEFAULT_RIGHT_CLICK_COPY_PASTE } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, TERMINAL_DEFAULT_SHELL_LINUX, TERMINAL_DEFAULT_SHELL_OSX, TERMINAL_DEFAULT_RIGHT_CLICK_COPY_PASTE } from 'vs/workbench/parts/terminal/common/terminal'; +import { TERMINAL_DEFAULT_SHELL_WINDOWS } from 'vs/workbench/parts/terminal/electron-browser/terminal'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.ts new file mode 100644 index 00000000000..8b19744bc10 --- /dev/null +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as os from 'os'; +import platform = require('vs/base/common/platform'); +import processes = require('vs/base/node/processes'); + +const powerShellExePath = + !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') + ? `${process.env.windir}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe` + : `${process.env.windir}\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe`; + +const isAtLeastWindows10 = platform.isWindows && parseFloat(os.release()) >= 10; + +export const TERMINAL_DEFAULT_SHELL_WINDOWS = isAtLeastWindows10 ? powerShellExePath : processes.getWindowsShell(); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts index e729c45dada..40fc991dbdf 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts @@ -152,12 +152,12 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { return config.terminal.integrated.commandsToSkipShell; } - public getShell(): IShellLaunchConfig { + public mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig): IShellLaunchConfig { const config = this._configurationService.getConfiguration(); - const shell: IShellLaunchConfig = { - executable: '', - args: [] - }; + + shell.executable = ''; + shell.args = []; + const integrated = config && config.terminal && config.terminal.integrated; if (integrated && integrated.shell && integrated.shellArgs) { if (this._platform === Platform.Windows) { diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index de119ecdc7f..97eb1459215 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -63,7 +63,6 @@ export class TerminalInstance implements ITerminalInstance { private _terminalFocusContextKey: IContextKey, private _configHelper: TerminalConfigHelper, private _container: HTMLElement, - name: string, private _shellLaunchConfig: IShellLaunchConfig, @IContextKeyService private _contextKeyService: IContextKeyService, @IKeybindingService private _keybindingService: IKeybindingService, @@ -84,7 +83,7 @@ export class TerminalInstance implements ITerminalInstance { this._onProcessIdReady = new Emitter(); this._onTitleChanged = new Emitter(); - this._createProcess(this._contextService.getWorkspace(), name, this._shellLaunchConfig); + this._createProcess(this._contextService.getWorkspace(), this._shellLaunchConfig); if (_container) { this.attachToElement(_container); @@ -105,7 +104,9 @@ export class TerminalInstance implements ITerminalInstance { DOM.addClass(this._wrapperElement, 'terminal-wrapper'); this._xtermElement = document.createElement('div'); - this._xterm = xterm(); + this._xterm = xterm({ + scrollback: this._configHelper.getScrollback() + }); this._xterm.open(this._xtermElement); this._process.on('message', (message) => { @@ -309,11 +310,15 @@ export class TerminalInstance implements ITerminalInstance { return typeof data === 'string' ? data.replace(TerminalInstance.EOL_REGEX, os.EOL) : data; } - protected _getCwd(workspace: IWorkspace, ignoreCustomCwd: boolean): string { + protected _getCwd(shell: IShellLaunchConfig, workspace: IWorkspace): string { + if (shell.cwd) { + return shell.cwd; + } + let cwd: string; // TODO: Handle non-existent customCwd - if (!ignoreCustomCwd) { + if (!shell.ignoreConfigurationCwd) { // Evaluate custom cwd first const customCwd = this._configHelper.getCwd(); if (customCwd) { @@ -333,18 +338,18 @@ export class TerminalInstance implements ITerminalInstance { return TerminalInstance._sanitizeCwd(cwd); } - protected _createProcess(workspace: IWorkspace, name: string, shell: IShellLaunchConfig) { + protected _createProcess(workspace: IWorkspace, shell: IShellLaunchConfig) { const locale = this._configHelper.isSetLocaleVariables() ? platform.locale : undefined; if (!shell.executable) { - shell = this._configHelper.getShell(); + this._configHelper.mergeDefaultShellPathAndArgs(shell); } - const env = TerminalInstance.createTerminalEnv(process.env, shell, this._getCwd(workspace, shell.ignoreCustomCwd), locale); - this._title = name ? name : ''; + const env = TerminalInstance.createTerminalEnv(process.env, shell, this._getCwd(shell, workspace), locale); + this._title = shell.name || ''; this._process = cp.fork('./terminalProcess', [], { env: env, cwd: URI.parse(path.dirname(require.toUrl('./terminalProcess'))).fsPath }); - if (!name) { + if (!shell.name) { // Only listen for process title changes when a name is not provided this._process.on('message', (message) => { if (message.type === 'title') { @@ -377,7 +382,11 @@ export class TerminalInstance implements ITerminalInstance { exitCodeMessage = nls.localize('terminal.integrated.exitedWithCode', 'The terminal process terminated with exit code: {0}', exitCode); } - if (this._shellLaunchConfig.waitOnExit) { + // Only trigger wait on exit when the exit was triggered by the process, not through the + // `workbench.action.terminal.kill` command + const triggeredByProcess = exitCode !== null; + + if (triggeredByProcess && this._shellLaunchConfig.waitOnExit) { if (exitCode) { this._xterm.writeln(exitCodeMessage); } @@ -411,7 +420,7 @@ export class TerminalInstance implements ITerminalInstance { // TODO: This should be private/protected // TODO: locale should not be optional public static createTerminalEnv(parentEnv: IStringDictionary, shell: IShellLaunchConfig, cwd: string, locale?: string): IStringDictionary { - const env = TerminalInstance._cloneEnv(parentEnv); + const env = shell.env ? shell.env : TerminalInstance._cloneEnv(parentEnv); env['PTYPID'] = process.pid.toString(); env['PTYSHELL'] = shell.executable; if (shell.args) { @@ -482,6 +491,7 @@ export class TerminalInstance implements ITerminalInstance { private _setScrollback(lineCount: number): void { if (this._xterm && this._xterm.getOption('scrollback') !== lineCount) { + console.log('set scrollback to: ' + lineCount); this._xterm.setOption('scrollback', lineCount); } } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index da6adf7cca8..c7b494dc79f 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -61,18 +61,11 @@ export class TerminalService implements ITerminalService { this.onInstanceDisposed((terminalInstance) => { this._removeInstance(terminalInstance); }); } - public createInstance(name?: string, shellPath?: string, shellArgs?: string[], waitOnExit?: boolean, ignoreCustomCwd?: boolean): ITerminalInstance { - let shell: IShellLaunchConfig = { - executable: shellPath, - args: shellArgs, - waitOnExit, - ignoreCustomCwd - }; + public createInstance(shell: IShellLaunchConfig = {}): ITerminalInstance { let terminalInstance = this._instantiationService.createInstance(TerminalInstance, this._terminalFocusContextKey, this._configHelper, this._terminalContainer, - name, shell); terminalInstance.addDisposable(terminalInstance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged)); terminalInstance.addDisposable(terminalInstance.onClosed(this._onInstanceDisposed.fire, this._onInstanceDisposed)); diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts b/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts index 7a71a7777db..ae338b2f906 100644 --- a/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts +++ b/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts @@ -10,6 +10,7 @@ import { IConfigurationService, getConfigurationValue } from 'vs/platform/config import { Platform } from 'vs/base/common/platform'; import { TPromise } from 'vs/base/common/winjs.base'; import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; +import { IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal'; import { DefaultConfig } from 'vs/editor/common/config/defaultConfig'; @@ -156,6 +157,7 @@ suite('Workbench - TerminalConfigHelper', () => { test('TerminalConfigHelper - getShell', function () { let configurationService: IConfigurationService; let configHelper: TerminalConfigHelper; + let shellConfig: IShellLaunchConfig; configurationService = new MockConfigurationService({ terminal: { @@ -171,7 +173,9 @@ suite('Workbench - TerminalConfigHelper', () => { }); configHelper = new TerminalConfigHelper(Platform.Linux, configurationService); configHelper.panelContainer = fixture; - assert.equal(configHelper.getShell().executable, 'foo', 'terminal.integrated.shell.linux should be selected on Linux'); + shellConfig = { executable: null, args: [] }; + configHelper.mergeDefaultShellPathAndArgs(shellConfig); + assert.equal(shellConfig.executable, 'foo', 'terminal.integrated.shell.linux should be selected on Linux'); configurationService = new MockConfigurationService({ terminal: { @@ -187,7 +191,9 @@ suite('Workbench - TerminalConfigHelper', () => { }); configHelper = new TerminalConfigHelper(Platform.Mac, configurationService); configHelper.panelContainer = fixture; - assert.equal(configHelper.getShell().executable, 'foo', 'terminal.integrated.shell.osx should be selected on OS X'); + shellConfig = { executable: null, args: [] }; + configHelper.mergeDefaultShellPathAndArgs(shellConfig); + assert.equal(shellConfig.executable, 'foo', 'terminal.integrated.shell.osx should be selected on OS X'); configurationService = new MockConfigurationService({ terminal: { @@ -203,7 +209,9 @@ suite('Workbench - TerminalConfigHelper', () => { }); configHelper = new TerminalConfigHelper(Platform.Windows, configurationService); configHelper.panelContainer = fixture; - assert.equal(configHelper.getShell().executable, 'foo', 'terminal.integrated.shell.windows should be selected on Windows'); + shellConfig = { executable: null, args: [] }; + configHelper.mergeDefaultShellPathAndArgs(shellConfig); + assert.equal(shellConfig.executable, 'foo', 'terminal.integrated.shell.windows should be selected on Windows'); }); test('TerminalConfigHelper - getTheme', function () { diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalInstance.test.ts b/src/vs/workbench/parts/terminal/test/electron-browser/terminalInstance.test.ts index 29dbc9fdd7c..ddd67e93ef9 100644 --- a/src/vs/workbench/parts/terminal/test/electron-browser/terminalInstance.test.ts +++ b/src/vs/workbench/parts/terminal/test/electron-browser/terminalInstance.test.ts @@ -20,11 +20,11 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; class TestTerminalInstance extends TerminalInstance { - public _getCwd(workspace: IWorkspace, ignoreCustomCwd: boolean): string { - return super._getCwd(workspace, ignoreCustomCwd); + public _getCwd(shell: IShellLaunchConfig, workspace: IWorkspace): string { + return super._getCwd(shell, workspace); } - protected _createProcess(workspace: IWorkspace, name: string, shell: IShellLaunchConfig): void { } + protected _createProcess(workspace: IWorkspace, shell: IShellLaunchConfig): void { } } suite('Workbench - TerminalInstance', () => { @@ -91,7 +91,7 @@ suite('Workbench - TerminalInstance', () => { configHelper = { getCwd: () => null }; - instance = instantiationService.createInstance(TestTerminalInstance, terminalFocusContextKey, configHelper, null, null, null); + instance = instantiationService.createInstance(TestTerminalInstance, terminalFocusContextKey, configHelper, null, null); }); // This helper checks the paths in a cross-platform friendly manner @@ -100,39 +100,39 @@ suite('Workbench - TerminalInstance', () => { } test('should default to os.homedir() for an empty workspace', () => { - assertPathsMatch(instance._getCwd(null, false), os.homedir()); + assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir()); }); test('should use to the workspace if it exists', () => { - assertPathsMatch(instance._getCwd({ resource: Uri.file('/foo') }, false), '/foo'); + assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/foo') }), '/foo'); }); test('should use an absolute custom cwd as is', () => { configHelper.getCwd = () => '/foo'; - assertPathsMatch(instance._getCwd(null, false), '/foo'); + assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), '/foo'); }); test('should normalize a relative custom cwd against the workspace path', () => { configHelper.getCwd = () => 'foo'; - assertPathsMatch(instance._getCwd({ resource: Uri.file('/bar') }, false), '/bar/foo'); + assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar') }), '/bar/foo'); configHelper.getCwd = () => './foo'; - assertPathsMatch(instance._getCwd({ resource: Uri.file('/bar') }, false), '/bar/foo'); + assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar') }), '/bar/foo'); configHelper.getCwd = () => '../foo'; - assertPathsMatch(instance._getCwd({ resource: Uri.file('/bar') }, false), '/foo'); + assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar') }, ), '/foo'); }); test('should fall back for relative a custom cwd that doesn\'t have a workspace', () => { configHelper.getCwd = () => 'foo'; - assertPathsMatch(instance._getCwd(null, false), os.homedir()); + assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir()); configHelper.getCwd = () => './foo'; - assertPathsMatch(instance._getCwd(null, false), os.homedir()); + assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir()); configHelper.getCwd = () => '../foo'; - assertPathsMatch(instance._getCwd(null, false), os.homedir()); + assertPathsMatch(instance._getCwd({ executable: null, args: [] }, null), os.homedir()); }); test('should ignore custom cwd when told to ignore', () => { configHelper.getCwd = () => '/foo'; - assertPathsMatch(instance._getCwd({ resource: Uri.file('/bar') }, true), '/bar'); + assertPathsMatch(instance._getCwd({ executable: null, args: [], ignoreConfigurationCwd: true }, { resource: Uri.file('/bar') }), '/bar'); }); }); }); \ No newline at end of file diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalService.test.ts b/src/vs/workbench/parts/terminal/test/electron-browser/terminalService.test.ts index bbd6384ee8e..b88447bd5f2 100644 --- a/src/vs/workbench/parts/terminal/test/electron-browser/terminalService.test.ts +++ b/src/vs/workbench/parts/terminal/test/electron-browser/terminalService.test.ts @@ -5,14 +5,13 @@ 'use strict'; -//import * as assert from 'assert'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -//import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance'; import { TerminalService } from 'vs/workbench/parts/terminal/electron-browser/terminalService'; -import { TERMINAL_DEFAULT_SHELL_LINUX, TERMINAL_DEFAULT_SHELL_OSX, TERMINAL_DEFAULT_SHELL_WINDOWS } from 'vs/workbench/parts/terminal/common/terminal'; +import { TERMINAL_DEFAULT_SHELL_LINUX, TERMINAL_DEFAULT_SHELL_OSX } from 'vs/workbench/parts/terminal/common/terminal'; +import { TERMINAL_DEFAULT_SHELL_WINDOWS } from 'vs/workbench/parts/terminal/electron-browser/terminal'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TPromise } from 'vs/base/common/winjs.base'; diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts index 60dd1fab10e..cd616e9cb83 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts @@ -21,7 +21,8 @@ import { IKeybindingEvent, IKeybindingItem, IUserFriendlyKeybinding, KeybindingS import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingRule, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/platform'; -import { ITelemetryService, keybindingsTelemetry } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { keybindingsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { getNativeLabelProvider, getNativeAriaLabelProvider } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymap'; import { IMessageService } from 'vs/platform/message/common/message'; import { ConfigWatcher } from 'vs/base/node/config'; diff --git a/src/vs/workbench/services/mode/common/workbenchModeService.ts b/src/vs/workbench/services/mode/common/workbenchModeService.ts new file mode 100644 index 00000000000..fc65de33c91 --- /dev/null +++ b/src/vs/workbench/services/mode/common/workbenchModeService.ts @@ -0,0 +1,147 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as nls from 'vs/nls'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import * as paths from 'vs/base/common/paths'; +import { TPromise } from 'vs/base/common/winjs.base'; +import mime = require('vs/base/common/mime'); +import { IFilesConfiguration } from 'vs/platform/files/common/files'; +import { IExtensionService } from 'vs/platform/extensions/common/extensions'; +import { IExtensionPointUser, ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry'; +import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; +import { ILanguageExtensionPoint, IValidLanguageExtensionPoint } from 'vs/editor/common/services/modeService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { languagesExtPoint, ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; + +export class WorkbenchModeServiceImpl extends ModeServiceImpl { + private _configurationService: IConfigurationService; + private _extensionService: IExtensionService; + private _onReadyPromise: TPromise; + + constructor( + @IExtensionService extensionService: IExtensionService, + @IConfigurationService configurationService: IConfigurationService + ) { + super(); + this._configurationService = configurationService; + this._extensionService = extensionService; + + languagesExtPoint.setHandler((extensions: IExtensionPointUser[]) => { + let allValidLanguages: IValidLanguageExtensionPoint[] = []; + + for (let i = 0, len = extensions.length; i < len; i++) { + let extension = extensions[i]; + + if (!Array.isArray(extension.value)) { + extension.collector.error(nls.localize('invalid', "Invalid `contributes.{0}`. Expected an array.", languagesExtPoint.name)); + continue; + } + + for (let j = 0, lenJ = extension.value.length; j < lenJ; j++) { + let ext = extension.value[j]; + if (isValidLanguageExtensionPoint(ext, extension.collector)) { + let configuration = (ext.configuration ? paths.join(extension.description.extensionFolderPath, ext.configuration) : ext.configuration); + allValidLanguages.push({ + id: ext.id, + extensions: ext.extensions, + filenames: ext.filenames, + filenamePatterns: ext.filenamePatterns, + firstLine: ext.firstLine, + aliases: ext.aliases, + mimetypes: ext.mimetypes, + configuration: configuration + }); + } + } + } + + ModesRegistry.registerLanguages(allValidLanguages); + + }); + + this._configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(e.config)); + + this.onDidCreateMode((mode) => { + this._extensionService.activateByEvent(`onLanguage:${mode.getId()}`).done(null, onUnexpectedError); + }); + } + + protected _onReady(): TPromise { + if (!this._onReadyPromise) { + const configuration = this._configurationService.getConfiguration(); + this._onReadyPromise = this._extensionService.onReady().then(() => { + this.onConfigurationChange(configuration); + + return true; + }); + } + + return this._onReadyPromise; + } + + private onConfigurationChange(configuration: IFilesConfiguration): void { + + // Clear user configured mime associations + mime.clearTextMimes(true /* user configured */); + + // Register based on settings + if (configuration.files && configuration.files.associations) { + Object.keys(configuration.files.associations).forEach(pattern => { + const langId = configuration.files.associations[pattern]; + const mimetype = this.getMimeForMode(langId) || `text/x-${langId}`; + + mime.registerTextMime({ id: langId, mime: mimetype, filepattern: pattern, userConfigured: true }); + }); + } + } +} + +function isUndefinedOrStringArray(value: string[]): boolean { + if (typeof value === 'undefined') { + return true; + } + if (!Array.isArray(value)) { + return false; + } + return value.every(item => typeof item === 'string'); +} + +function isValidLanguageExtensionPoint(value: ILanguageExtensionPoint, collector: ExtensionMessageCollector): boolean { + if (!value) { + collector.error(nls.localize('invalid.empty', "Empty value for `contributes.{0}`", languagesExtPoint.name)); + return false; + } + if (typeof value.id !== 'string') { + collector.error(nls.localize('require.id', "property `{0}` is mandatory and must be of type `string`", 'id')); + return false; + } + if (!isUndefinedOrStringArray(value.extensions)) { + collector.error(nls.localize('opt.extensions', "property `{0}` can be omitted and must be of type `string[]`", 'extensions')); + return false; + } + if (!isUndefinedOrStringArray(value.filenames)) { + collector.error(nls.localize('opt.filenames', "property `{0}` can be omitted and must be of type `string[]`", 'filenames')); + return false; + } + if (typeof value.firstLine !== 'undefined' && typeof value.firstLine !== 'string') { + collector.error(nls.localize('opt.firstLine', "property `{0}` can be omitted and must be of type `string`", 'firstLine')); + return false; + } + if (typeof value.configuration !== 'undefined' && typeof value.configuration !== 'string') { + collector.error(nls.localize('opt.configuration', "property `{0}` can be omitted and must be of type `string`", 'configuration')); + return false; + } + if (!isUndefinedOrStringArray(value.aliases)) { + collector.error(nls.localize('opt.aliases', "property `{0}` can be omitted and must be of type `string[]`", 'aliases')); + return false; + } + if (!isUndefinedOrStringArray(value.mimetypes)) { + collector.error(nls.localize('opt.mimetypes', "property `{0}` can be omitted and must be of type `string[]`", 'mimetypes')); + return false; + } + return true; +} diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 9a0f4d24a8b..4410f3891a3 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -55,7 +55,7 @@ export class SearchService implements IRawSearchService { }), this.textSearchWorkerProvider); - return this.doSearchWithBatchTimeout(engine, SearchService.BATCH_SIZE); + return this.doTextSearch(engine, SearchService.BATCH_SIZE); } public doFileSearch(EngineClass: { new (config: IRawSearch): ISearchEngine; }, config: IRawSearch, batchSize?: number): PPromise { @@ -278,12 +278,13 @@ export class SearchService implements IRawSearchService { }); } - private doSearchWithBatchTimeout(engine: ISearchEngine, batchSize: number): PPromise> { + private doTextSearch(engine: TextSearchEngine, batchSize: number): PPromise> { return new PPromise>((c, e, p) => { // Use BatchedCollector to get new results to the frontend every 2s at least, until 50 results have been returned - const collector = new BatchedCollector(batchSize, p); - engine.search((match) => { - collector.addItem(match, match.numMatches); + const collector = new BatchedCollector(batchSize, p); + engine.search((matches) => { + const totalMatches = matches.reduce((acc, m) => acc + m.numMatches, 0); + collector.addItems(matches, totalMatches); }, (progress) => { p(progress); }, (error, stats) => { @@ -378,57 +379,51 @@ interface CacheStats { } /** - * Collects a batch of items that each have a size. When the cumulative size of the batch reaches 'maxBatchSize', it calls the callback. + * Collects items that have a size - before the cumulative size of collected items reaches START_BATCH_AFTER_COUNT, the callback is called for every + * set of items collected. + * But after that point, the callback is called with batches of maxBatchSize. * If the batch isn't filled within some time, the callback is also called. - * And after 'runTimeoutUntilCount' items, the timeout is ignored, and the callback is called only when the batch is full. */ class BatchedCollector { - // Use INIT_TIMEOUT for INIT_TIMEOUT_DURATION ms, then switch to LONGER_TIMEOUT - private static INIT_TIMEOUT = 500; - private static INIT_TIMEOUT_DURATION = 5000; - private static LONGER_TIMEOUT = 2000; + private static TIMEOUT = 4000; // After RUN_TIMEOUT_UNTIL_COUNT items have been collected, stop flushing on timeout - private static RUN_TIMEOUT_UNTIL_COUNT = 50; + private static START_BATCH_AFTER_COUNT = 50; private totalNumberCompleted = 0; private batch: T[] = []; private batchSize = 0; private timeoutHandle: number; - private startTime: number; - constructor(private maxBatchSize: number, private cb: (items: T | T[]) => void) { } - addItem(item: T, size: number): void { - if (!item) { + addItems(items: T[], size: number): void { + if (!items) { return; } if (this.maxBatchSize > 0) { - this.addItemToBatch(item, size); + this.addItemsToBatch(items, size); } else { - this.cb(item); + this.cb(items); } } - private addItemToBatch(item: T, size: number): void { - if (!this.startTime) { - this.startTime = Date.now(); - } - - this.batch.push(item); + private addItemsToBatch(items: T[], size: number): void { + this.batch = this.batch.concat(items); this.batchSize += size; - if (this.batchSize >= this.maxBatchSize) { + if (this.totalNumberCompleted < BatchedCollector.START_BATCH_AFTER_COUNT) { + // Flush because we aren't batching yet + this.flush(); + } else if (this.batchSize >= this.maxBatchSize) { // Flush because the batch is full this.flush(); - } else if (!this.timeoutHandle && this.totalNumberCompleted < BatchedCollector.RUN_TIMEOUT_UNTIL_COUNT) { + } else if (!this.timeoutHandle) { // No timeout running, start a timeout to flush - const t = this.getTimeout(); this.timeoutHandle = setTimeout(() => { this.flush(); - }, t); + }, BatchedCollector.TIMEOUT); } } @@ -445,10 +440,4 @@ class BatchedCollector { } } } - - private getTimeout(): number { - return Date.now() - this.startTime < BatchedCollector.INIT_TIMEOUT_DURATION ? - BatchedCollector.INIT_TIMEOUT : - BatchedCollector.LONGER_TIMEOUT; - } } diff --git a/src/vs/workbench/services/search/node/search.ts b/src/vs/workbench/services/search/node/search.ts index 0bfd0556b4c..1ea0d55aa0b 100644 --- a/src/vs/workbench/services/search/node/search.ts +++ b/src/vs/workbench/services/search/node/search.ts @@ -37,7 +37,7 @@ export interface IRawFileMatch { } export interface ISearchEngine { - search: (onResult: (match: T) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchComplete) => void) => void; + search: (onResult: (matches: T) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchComplete) => void) => void; cancel: () => void; } diff --git a/src/vs/workbench/services/search/node/textSearch.ts b/src/vs/workbench/services/search/node/textSearch.ts index eb82da7bf10..cb30c222466 100644 --- a/src/vs/workbench/services/search/node/textSearch.ts +++ b/src/vs/workbench/services/search/node/textSearch.ts @@ -15,7 +15,7 @@ import { ISerializedFileMatch, ISerializedSearchComplete, IRawSearch, ISearchEng import { ISearchWorker } from './worker/searchWorkerIpc'; import { ITextSearchWorkerProvider } from './textSearchWorkerProvider'; -export class Engine implements ISearchEngine { +export class Engine implements ISearchEngine { private static PROGRESS_FLUSH_CHUNK_SIZE = 50; // optimization: number of files to process before emitting progress event @@ -60,7 +60,7 @@ export class Engine implements ISearchEngine { }); } - search(onResult: (match: ISerializedFileMatch) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchComplete) => void): void { + search(onResult: (match: ISerializedFileMatch[]) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchComplete) => void): void { this.workers = this.workerProvider.getWorkers(); this.initializeWorkers(); @@ -100,10 +100,8 @@ export class Engine implements ISearchEngine { } const matches = result.matches; + onResult(matches); this.numResults += result.numMatches; - matches.forEach(m => { - onResult(m); - }); if (this.config.maxResults && this.numResults >= this.config.maxResults) { // It's possible to go over maxResults like this, but it's much simpler than trying to extract the exact number diff --git a/src/vs/workbench/services/search/node/worker/searchWorker.ts b/src/vs/workbench/services/search/node/worker/searchWorker.ts index 4f845374d15..03b9e38957c 100644 --- a/src/vs/workbench/services/search/node/worker/searchWorker.ts +++ b/src/vs/workbench/services/search/node/worker/searchWorker.ts @@ -167,7 +167,7 @@ export class SearchWorkerEngine { return new TPromise((resolve, reject) => { fs.open(filename, 'r', null, (error: Error, fd: number) => { if (error) { - return reject(error); + return resolve(null); } let buffer = new Buffer(options.bufferLength); @@ -275,7 +275,7 @@ export class SearchWorkerEngine { readFile(/*isFirstRead=*/true, (error: Error) => { if (error) { - return reject(error); + return resolve(null); } if (line.length) { @@ -283,11 +283,7 @@ export class SearchWorkerEngine { } fs.close(fd, (error: Error) => { - if (error) { - reject(error); - } else { - resolve(null); - } + resolve(null); }); }); }); diff --git a/src/vs/workbench/services/search/test/node/search.test.ts b/src/vs/workbench/services/search/test/node/search.test.ts index bf7e87287e8..a03f939b350 100644 --- a/src/vs/workbench/services/search/test/node/search.test.ts +++ b/src/vs/workbench/services/search/test/node/search.test.ts @@ -14,10 +14,14 @@ import * as platform from 'vs/base/common/platform'; import { LineMatch } from 'vs/platform/search/common/search'; import { FileWalker, Engine as FileSearchEngine } from 'vs/workbench/services/search/node/fileSearch'; -import { IRawFileMatch } from 'vs/workbench/services/search/node/search'; +import { IRawFileMatch, ISerializedFileMatch } from 'vs/workbench/services/search/node/search'; import { Engine as TextSearchEngine } from 'vs/workbench/services/search/node/textSearch'; import { TextSearchWorkerProvider } from 'vs/workbench/services/search/node/textSearchWorkerProvider'; +function countAll(matches: ISerializedFileMatch[]): number { + return matches.reduce((acc, m) => acc + count(m.lineMatches), 0); +} + function count(lineMatches: LineMatch[]): number { let count = 0; if (lineMatches) { @@ -628,8 +632,8 @@ suite('Search', () => { let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider); engine.search((result) => { - if (result && result.lineMatches) { - c += count(result.lineMatches); + if (result) { + c += countAll(result); } }, () => { }, (error) => { assert.ok(!error); @@ -649,8 +653,8 @@ suite('Search', () => { let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider); engine.search((result) => { - if (result && result.lineMatches) { - c += count(result.lineMatches); + if (result) { + c += countAll(result); } }, () => { }, (error) => { assert.ok(!error); @@ -670,8 +674,8 @@ suite('Search', () => { let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider); engine.search((result) => { - if (result && result.lineMatches) { - c += count(result.lineMatches); + if (result) { + c += countAll(result); } }, () => { }, (error) => { assert.ok(!error); @@ -691,8 +695,8 @@ suite('Search', () => { let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider); engine.search((result) => { - if (result && result.lineMatches) { - c += count(result.lineMatches); + if (result) { + c += countAll(result); } }, () => { }, (error) => { assert.ok(!error); @@ -712,8 +716,8 @@ suite('Search', () => { let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider); engine.search((result) => { - if (result && result.lineMatches) { - c += count(result.lineMatches); + if (result) { + c += countAll(result); } }, (result) => { }, (error) => { assert.ok(!error); @@ -734,8 +738,8 @@ suite('Search', () => { let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider); engine.search((result) => { - if (result && result.lineMatches) { - c += count(result.lineMatches); + if (result) { + c += countAll(result); } }, (result) => { }, (error) => { assert.ok(!error); @@ -756,8 +760,8 @@ suite('Search', () => { let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider); engine.search((result) => { - if (result && result.lineMatches) { - c += count(result.lineMatches); + if (result) { + c += countAll(result); } }, (result) => { }, (error) => { assert.ok(!error); @@ -779,8 +783,8 @@ suite('Search', () => { let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider); engine.search((result) => { - if (result && result.lineMatches) { - c += count(result.lineMatches); + if (result) { + c += countAll(result); } }, (result) => { }, (error) => { assert.ok(!error); @@ -801,8 +805,8 @@ suite('Search', () => { let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider); engine.search((result) => { - if (result && result.lineMatches) { - c += count(result.lineMatches); + if (result) { + c += countAll(result); } }, (result) => { }, (error) => { assert.ok(!error); @@ -825,8 +829,8 @@ suite('Search', () => { let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider); engine.search((result) => { - if (result && result.lineMatches) { - c += count(result.lineMatches); + if (result) { + c += countAll(result); } }, (result) => { }, (error) => { assert.ok(!error); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 1353b624115..07ec93b6a1e 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -28,7 +28,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { ITelemetryService, anonymize } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { anonymize } from 'vs/platform/telemetry/common/telemetryUtils'; import { RunOnceScheduler } from 'vs/base/common/async'; /** diff --git a/src/vs/workbench/test/browser/editorStacksModel.test.ts b/src/vs/workbench/test/browser/editorStacksModel.test.ts index bf0099788ef..04dc3c4993b 100644 --- a/src/vs/workbench/test/browser/editorStacksModel.test.ts +++ b/src/vs/workbench/test/browser/editorStacksModel.test.ts @@ -19,7 +19,8 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { Registry } from 'vs/platform/platform'; import { Position, Direction } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ITelemetryService, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import 'vs/workbench/browser/parts/editor/baseEditor'; diff --git a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts index 8607a049eb2..b60e9ff06db 100644 --- a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts @@ -13,7 +13,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import * as Platform from 'vs/platform/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { StringEditorInput } from 'vs/workbench/common/editor/stringEditorInput'; -import { ITelemetryService, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; diff --git a/src/vs/workbench/parts/indentation/test/common/indentationCommands.test.ts b/src/vs/workbench/test/common/editor/indentation.test.ts similarity index 98% rename from src/vs/workbench/parts/indentation/test/common/indentationCommands.test.ts rename to src/vs/workbench/test/common/editor/indentation.test.ts index d3d3c6951f6..397af6513ad 100644 --- a/src/vs/workbench/parts/indentation/test/common/indentationCommands.test.ts +++ b/src/vs/workbench/test/common/editor/indentation.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Selection } from 'vs/editor/common/core/selection'; -import { IndentationToSpacesCommand, IndentationToTabsCommand } from 'vs/workbench/parts/indentation/common/indentationCommands'; +import { IndentationToSpacesCommand, IndentationToTabsCommand } from 'vs/workbench/common/editor/indentation'; import { testCommand } from 'vs/editor/test/common/commands/commandTestUtils'; function testIndentationToSpacesCommand(lines: string[], selection: Selection, tabSize: number, expectedLines: string[], expectedSelection: Selection): void { diff --git a/src/vs/workbench/test/common/editor/rangeDecorations.test.ts b/src/vs/workbench/test/common/editor/rangeDecorations.test.ts index bde6f79405e..eee0aa2c901 100644 --- a/src/vs/workbench/test/common/editor/rangeDecorations.test.ts +++ b/src/vs/workbench/test/common/editor/rangeDecorations.test.ts @@ -151,7 +151,7 @@ suite('Editor - Range decorations', () => { } function mockEditorService(editorInput: IEditorInput); - function mockEditorService(resource: URI) + function mockEditorService(resource: URI); function mockEditorService(arg: any) { let editorInput: IEditorInput = arg instanceof URI ? instantiationService.createInstance(FileEditorInput, arg, void 0) : arg; instantiationService.stub(WorkbenchEditorService.IWorkbenchEditorService, 'getActiveEditorInput', editorInput); 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 1d325306e0a..9757c297a15 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.test.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.test.ts @@ -11,7 +11,8 @@ import { WorkspaceContextService, IWorkspaceContextService } from 'vs/platform/w import { createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { ISearchService } from 'vs/platform/search/common/search'; -import { ITelemetryService, ITelemetryInfo, ITelemetryExperiments, defaultExperiments } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService, ITelemetryInfo, ITelemetryExperiments } from 'vs/platform/telemetry/common/telemetry'; +import { defaultExperiments } from 'vs/platform/telemetry/common/telemetryUtils'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import * as minimist from 'minimist'; @@ -71,7 +72,7 @@ suite('QuickOpen performance', () => { const instantiationService = new InstantiationService(new ServiceCollection( [ITelemetryService, telemetryService], [IConfigurationService, new SimpleConfigurationService()], - [IModelService, new ModelServiceImpl(null, configurationService, null)], + [IModelService, new ModelServiceImpl(null, configurationService)], [IWorkspaceContextService, new WorkspaceContextService({ resource: URI.file(testWorkspacePath) })], [IWorkbenchEditorService, new TestEditorService()], [IEditorGroupService, new TestEditorGroupService()], diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.test.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.test.ts index ef12739a2c9..07be8c951d0 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.test.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.test.ts @@ -12,7 +12,8 @@ import { WorkspaceContextService, IWorkspaceContextService } from 'vs/platform/w import { createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { ISearchService, IQueryOptions } from 'vs/platform/search/common/search'; -import { ITelemetryService, ITelemetryInfo, ITelemetryExperiments, defaultExperiments } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService, ITelemetryInfo, ITelemetryExperiments } from 'vs/platform/telemetry/common/telemetry'; +import { defaultExperiments } from 'vs/platform/telemetry/common/telemetryUtils'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import * as minimist from 'minimist'; @@ -60,7 +61,7 @@ suite('TextSearch performance', () => { const instantiationService = new InstantiationService(new ServiceCollection( [ITelemetryService, telemetryService], [IConfigurationService, new SimpleConfigurationService()], - [IModelService, new ModelServiceImpl(null, configurationService, null)], + [IModelService, new ModelServiceImpl(null, configurationService)], [IWorkspaceContextService, new WorkspaceContextService({ resource: URI.file(testWorkspacePath) })], [IWorkbenchEditorService, new TestEditorService()], [IEditorGroupService, new TestEditorGroupService()], diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index ed4025be1e4..3fca6f90c18 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -12,7 +12,8 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { EventEmitter } from 'vs/base/common/eventEmitter'; import * as paths from 'vs/base/common/paths'; import URI from 'vs/base/common/uri'; -import { ITelemetryService, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { StorageService, InMemoryLocalStorage } from 'vs/platform/storage/common/storageService'; import { IEditorGroup, ConfirmResult } from 'vs/workbench/common/editor'; import Event, { Emitter } from 'vs/base/common/event';