diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index 4fd18a48619..4feeb68b762 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -1,7 +1,7 @@ [ { "name": "ms-vscode.node-debug", - "version": "node-debug@1.44.1", + "version": "1.44.1", "repo": "https://github.com/Microsoft/vscode-node-debug", "metadata": { "id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6", diff --git a/extensions/json-language-features/package.nls.json b/extensions/json-language-features/package.nls.json index 5d132ccd776..17bc29f9372 100644 --- a/extensions/json-language-features/package.nls.json +++ b/extensions/json-language-features/package.nls.json @@ -3,7 +3,7 @@ "description": "Provides rich language support for JSON files.", "json.schemas.desc": "Associate schemas to JSON files in the current project", "json.schemas.url.desc": "A URL to a schema or a relative path to a schema in the current directory", - "json.schemas.fileMatch.desc": "An array of file patterns to match against when resolving JSON files to schemas.", + "json.schemas.fileMatch.desc": "An array of file patterns to match against when resolving JSON files to schemas. `*` can be used as a wildcard. Exclusion patterns can also be defined and start with '!'. A file matches when there at least one matching pattern and the last matching pattern is not an exclusion pattern.", "json.schemas.fileMatch.item.desc": "A file pattern that can contain '*' to match against when resolving JSON files to schemas.", "json.schemas.schema.desc": "The schema definition for the given URL. The schema only needs to be provided to avoid accesses to the schema URL.", "json.format.enable.desc": "Enable/disable default JSON formatter", diff --git a/extensions/json-language-features/server/README.md b/extensions/json-language-features/server/README.md index 43cc04837d5..18e397c5336 100644 --- a/extensions/json-language-features/server/README.md +++ b/extensions/json-language-features/server/README.md @@ -62,7 +62,7 @@ The server supports the following settings: - `format` - `enable`: Whether the server should register the formatting support. This option is only applicable if the client supports *dynamicRegistration* for *rangeFormatting* and `initializationOptions.provideFormatter` is not defined. - `schema`: Configures association of file names to schema URL or schemas and/or associations of schema URL to schema content. - - `fileMatch`: an array of file names or paths (separated by `/`). `*` can be used as a wildcard. + - `fileMatch`: an array of file names or paths (separated by `/`). `*` can be used as a wildcard. Exclusion patterns can also be defined and start with '!'. A file matches when there at least one matching pattern and the last matching pattern is not an exclusion pattern. - `url`: The URL of the schema, optional when also a schema is provided. - `schema`: The schema content. - `resultLimit`: The max number foldig ranges and otline symbols to be computed (for performance reasons) diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index 352dd6b8e34..1132d9ecfa2 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -114,16 +114,16 @@ export class PagedList implements IDisposable { return this.list.onDidDispose; } - get onFocusChange(): Event> { - return Event.map(this.list.onFocusChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); + get onDidChangeFocus(): Event> { + return Event.map(this.list.onDidChangeFocus, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); } - get onOpen(): Event> { + get onDidOpen(): Event> { return Event.map(this.list.onDidOpen, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent })); } - get onSelectionChange(): Event> { - return Event.map(this.list.onSelectionChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); + get onDidChangeSelection(): Event> { + return Event.map(this.list.onDidChangeSelection, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); } get onPin(): Event> { @@ -191,8 +191,8 @@ export class PagedList implements IDisposable { return this.list.getFocus(); } - setSelection(indexes: number[]): void { - this.list.setSelection(indexes); + setSelection(indexes: number[], browserEvent?: UIEvent): void { + this.list.setSelection(indexes, browserEvent); } getSelection(): number[] { diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index b37c69e125a..b17620ef35f 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -1123,11 +1123,11 @@ export class List implements ISpliceable, IDisposable { protected readonly disposables = new DisposableStore(); - @memoize get onFocusChange(): Event> { + @memoize get onDidChangeFocus(): Event> { return Event.map(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e)); } - @memoize get onSelectionChange(): Event> { + @memoize get onDidChangeSelection(): Event> { return Event.map(this.eventBufferer.wrapEvent(this.selection.onChange), e => this.toListEvent(e)); } @@ -1266,8 +1266,8 @@ export class List implements ISpliceable, IDisposable { this.disposables.add(this.createMouseController(_options)); - this.onFocusChange(this._onFocusChange, this, this.disposables); - this.onSelectionChange(this._onSelectionChange, this, this.disposables); + this.onDidChangeFocus(this._onFocusChange, this, this.disposables); + this.onDidChangeSelection(this._onSelectionChange, this, this.disposables); if (_options.ariaLabel) { this.view.domNode.setAttribute('aria-label', localize('aria list', "{0}. Use the navigation keys to navigate.", _options.ariaLabel)); diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 07258ce3bef..1867910f0bb 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -750,7 +750,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi .on(e => this.onMouseUp(e), this)); this._register(this.selectList.onMouseOver(e => typeof e.index !== 'undefined' && this.selectList.setFocus([e.index]))); - this._register(this.selectList.onFocusChange(e => this.onListFocus(e))); + this._register(this.selectList.onDidChangeFocus(e => this.onListFocus(e))); this._register(dom.addDisposableListener(this.selectDropDownContainer, dom.EventType.FOCUS_OUT, e => { if (!this._isVisible || dom.isAncestor(e.relatedTarget as HTMLElement, this.selectDropDownContainer)) { diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 4d07f50b6b8..4b924b7a133 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { CharCode } from 'vs/base/common/charCode'; -import { Iterator, IteratorResult, FIN } from './iterator'; +import { FIN } from './iterator'; /** * @deprecated ES6: use `[...SetOrMap.values()]` diff --git a/src/vs/base/common/normalization.ts b/src/vs/base/common/normalization.ts index 9438f72b5ba..3a94fb716ec 100644 --- a/src/vs/base/common/normalization.ts +++ b/src/vs/base/common/normalization.ts @@ -46,3 +46,17 @@ function normalize(str: string, form: string, normalizedCache: LRUCache string = (function () { + if (!canNormalize) { + // no ES6 features... + return function (str: string) { return str; }; + } else { + // transform into NFD form and remove accents + // see: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463#37511463 + const regex = /[\u0300-\u036f]/g; + return function (str: string) { + return normalizeNFD(str).replace(regex, ''); + }; + } +})(); diff --git a/src/vs/base/common/resources.ts b/src/vs/base/common/resources.ts index 05610a4750b..d42666ebcc0 100644 --- a/src/vs/base/common/resources.ts +++ b/src/vs/base/common/resources.ts @@ -5,7 +5,7 @@ import * as extpath from 'vs/base/common/extpath'; import * as paths from 'vs/base/common/path'; -import { URI } from 'vs/base/common/uri'; +import { URI, originalFSPath as uriOriginalFSPath } from 'vs/base/common/uri'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; import { isLinux, isWindows } from 'vs/base/common/platform'; @@ -13,6 +13,8 @@ import { CharCode } from 'vs/base/common/charCode'; import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob'; import { TernarySearchTree } from 'vs/base/common/map'; +export const originalFSPath = uriOriginalFSPath; + export function getComparisonKey(resource: URI): string { return hasToIgnoreCase(resource) ? resource.toString().toLowerCase() : resource.toString(); } @@ -107,15 +109,7 @@ export function dirname(resource: URI): URI { * @returns The resulting URI. */ export function joinPath(resource: URI, ...pathFragment: string[]): URI { - let joinedPath: string; - if (resource.scheme === Schemas.file) { - joinedPath = URI.file(paths.join(originalFSPath(resource), ...pathFragment)).path; - } else { - joinedPath = paths.posix.join(resource.path || '/', ...pathFragment); - } - return resource.with({ - path: joinedPath - }); + return URI.joinPaths(resource, ...pathFragment); } /** @@ -139,33 +133,6 @@ export function normalizePath(resource: URI): URI { }); } -/** - * Returns the fsPath of an URI where the drive letter is not normalized. - * See #56403. - */ -export function originalFSPath(uri: URI): string { - let value: string; - const uriPath = uri.path; - if (uri.authority && uriPath.length > 1 && uri.scheme === Schemas.file) { - // unc path: file://shares/c$/far/boo - value = `//${uri.authority}${uriPath}`; - } else if ( - isWindows - && uriPath.charCodeAt(0) === CharCode.Slash - && extpath.isWindowsDriveLetter(uriPath.charCodeAt(1)) - && uriPath.charCodeAt(2) === CharCode.Colon - ) { - value = uriPath.substr(1); - } else { - // other path - value = uriPath; - } - if (isWindows) { - value = value.replace(/\//g, '\\'); - } - return value; -} - /** * Returns true if the URI path is absolute. */ diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 51336f3eb99..1b1e72f9c3a 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -5,7 +5,6 @@ import { CharCode } from 'vs/base/common/charCode'; import { Constants } from 'vs/base/common/uint'; -import { canNormalize, normalizeNFD } from 'vs/base/common/normalization'; export function isFalsyOrWhitespace(str: string | undefined): boolean { if (!str || typeof str !== 'string') { @@ -853,21 +852,6 @@ export function removeAnsiEscapeCodes(str: string): string { return str; } -export const removeAccents: (str: string) => string = (function () { - if (!canNormalize) { - // no ES6 features... - return function (str: string) { return str; }; - } else { - // transform into NFD form and remove accents - // see: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463#37511463 - const regex = /[\u0300-\u036f]/g; - return function (str: string) { - return normalizeNFD(str).replace(regex, ''); - }; - } -})(); - - // -- UTF-8 BOM export const UTF8_BOM_CHARACTER = String.fromCharCode(CharCode.UTF8_BOM); diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index 78c9fda1d71..bdc79116318 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -5,6 +5,8 @@ import { isWindows } from 'vs/base/common/platform'; import { CharCode } from 'vs/base/common/charCode'; +import * as paths from 'vs/base/common/path'; +import * as extpath from 'vs/base/common/extpath'; const _schemePattern = /^\w[\w\d+.-]*$/; const _singleSlashStart = /^\//; @@ -333,6 +335,25 @@ export class URI implements UriComponents { ); } + /** + * Join a URI path with path fragments and normalizes the resulting path. + * + * @param resource The input URI. + * @param pathFragment The path fragment to add to the URI path. + * @returns The resulting URI. + */ + static joinPaths(resource: URI, ...pathFragment: string[]): URI { + let joinedPath: string; + if (resource.scheme === 'file') { + joinedPath = URI.file(paths.join(originalFSPath(resource), ...pathFragment)).path; + } else { + joinedPath = paths.posix.join(resource.path || '/', ...pathFragment); + } + return resource.with({ + path: joinedPath + }); + } + // ---- printing/externalize --------------------------- /** @@ -671,3 +692,29 @@ function percentDecode(str: string): string { } return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match)); } + + +// --- utils + +export function originalFSPath(uri: URI): string { + let value: string; + const uriPath = uri.path; + if (uri.authority && uriPath.length > 1 && uri.scheme === 'file') { + // unc path: file://shares/c$/far/boo + value = `//${uri.authority}${uriPath}`; + } else if ( + isWindows + && uriPath.charCodeAt(0) === CharCode.Slash + && extpath.isWindowsDriveLetter(uriPath.charCodeAt(1)) + && uriPath.charCodeAt(2) === CharCode.Colon + ) { + value = uriPath.substr(1); + } else { + // other path + value = uriPath; + } + if (isWindows) { + value = value.replace(/\//g, '\\'); + } + return value; +} diff --git a/src/vs/base/common/uuid.ts b/src/vs/base/common/uuid.ts index 680c70a5132..57d9db69de1 100644 --- a/src/vs/base/common/uuid.ts +++ b/src/vs/base/common/uuid.ts @@ -17,14 +17,14 @@ for (let i = 0; i < 256; i++) { _hex.push(i.toString(16).padStart(2, '0')); } -const _fillRandomValues = typeof crypto === 'object' - ? crypto.getRandomValues.bind(crypto) - : function (bucket: Uint8Array): Uint8Array { // todo@jrieken node nodejs use `crypto#randomBytes`, see: https://nodejs.org/docs/latest/api/crypto.html#crypto_crypto_randombytes_size_callback - for (let i = 0; i < bucket.length; i++) { - bucket[i] = Math.floor(Math.random() * 256); - } - return bucket; - }; +// todo@joh node nodejs use `crypto#randomBytes`, see: https://nodejs.org/docs/latest/api/crypto.html#crypto_crypto_randombytes_size_callback +// todo@joh use browser-crypto +const _fillRandomValues = function (bucket: Uint8Array): Uint8Array { + for (let i = 0; i < bucket.length; i++) { + bucket[i] = Math.floor(Math.random() * 256); + } + return bucket; +}; export function generateUuid(): string { // get data diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index e824a69d127..051f7f56333 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -295,12 +295,12 @@ export class QuickInputList { @memoize get onDidChangeFocus() { - return Event.map(this.list.onFocusChange, e => e.elements.map(e => e.item)); + return Event.map(this.list.onDidChangeFocus, e => e.elements.map(e => e.item)); } @memoize get onDidChangeSelection() { - return Event.map(this.list.onSelectionChange, e => e.elements.map(e => e.item)); + return Event.map(this.list.onDidChangeSelection, e => e.elements.map(e => e.item)); } getAllVisibleChecked() { diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index 8afd0496f24..11f264c7732 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -6,7 +6,6 @@ import { ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache, mapToSerializable, serializableToMap } from 'vs/base/common/map'; import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { IteratorResult } from 'vs/base/common/iterator'; suite('Map', () => { diff --git a/src/vs/base/test/common/normalization.test.ts b/src/vs/base/test/common/normalization.test.ts new file mode 100644 index 00000000000..aebe84a0c6e --- /dev/null +++ b/src/vs/base/test/common/normalization.test.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { removeAccents } from 'vs/base/common/normalization'; + +suite('Normalization', () => { + + test('removeAccents', function () { + assert.equal(removeAccents('joào'), 'joao'); + assert.equal(removeAccents('joáo'), 'joao'); + assert.equal(removeAccents('joâo'), 'joao'); + assert.equal(removeAccents('joäo'), 'joao'); + // assert.equal(strings.removeAccents('joæo'), 'joao'); // not an accent + assert.equal(removeAccents('joão'), 'joao'); + assert.equal(removeAccents('joåo'), 'joao'); + assert.equal(removeAccents('joåo'), 'joao'); + assert.equal(removeAccents('joāo'), 'joao'); + + assert.equal(removeAccents('fôo'), 'foo'); + assert.equal(removeAccents('föo'), 'foo'); + assert.equal(removeAccents('fòo'), 'foo'); + assert.equal(removeAccents('fóo'), 'foo'); + // assert.equal(strings.removeAccents('fœo'), 'foo'); + // assert.equal(strings.removeAccents('føo'), 'foo'); + assert.equal(removeAccents('fōo'), 'foo'); + assert.equal(removeAccents('fõo'), 'foo'); + + assert.equal(removeAccents('andrè'), 'andre'); + assert.equal(removeAccents('andré'), 'andre'); + assert.equal(removeAccents('andrê'), 'andre'); + assert.equal(removeAccents('andrë'), 'andre'); + assert.equal(removeAccents('andrē'), 'andre'); + assert.equal(removeAccents('andrė'), 'andre'); + assert.equal(removeAccents('andrę'), 'andre'); + + assert.equal(removeAccents('hvîc'), 'hvic'); + assert.equal(removeAccents('hvïc'), 'hvic'); + assert.equal(removeAccents('hvíc'), 'hvic'); + assert.equal(removeAccents('hvīc'), 'hvic'); + assert.equal(removeAccents('hvįc'), 'hvic'); + assert.equal(removeAccents('hvìc'), 'hvic'); + + assert.equal(removeAccents('ûdo'), 'udo'); + assert.equal(removeAccents('üdo'), 'udo'); + assert.equal(removeAccents('ùdo'), 'udo'); + assert.equal(removeAccents('údo'), 'udo'); + assert.equal(removeAccents('ūdo'), 'udo'); + + assert.equal(removeAccents('heÿ'), 'hey'); + + // assert.equal(strings.removeAccents('gruß'), 'grus'); + assert.equal(removeAccents('gruś'), 'grus'); + assert.equal(removeAccents('gruš'), 'grus'); + + assert.equal(removeAccents('çool'), 'cool'); + assert.equal(removeAccents('ćool'), 'cool'); + assert.equal(removeAccents('čool'), 'cool'); + + assert.equal(removeAccents('ñice'), 'nice'); + assert.equal(removeAccents('ńice'), 'nice'); + }); +}); diff --git a/src/vs/base/test/common/strings.test.ts b/src/vs/base/test/common/strings.test.ts index 600df87cfca..7fb1a140236 100644 --- a/src/vs/base/test/common/strings.test.ts +++ b/src/vs/base/test/common/strings.test.ts @@ -404,61 +404,6 @@ suite('Strings', () => { assert.equal(strings.getNLines('foo', 0), ''); }); - test('removeAccents', function () { - assert.equal(strings.removeAccents('joào'), 'joao'); - assert.equal(strings.removeAccents('joáo'), 'joao'); - assert.equal(strings.removeAccents('joâo'), 'joao'); - assert.equal(strings.removeAccents('joäo'), 'joao'); - // assert.equal(strings.removeAccents('joæo'), 'joao'); // not an accent - assert.equal(strings.removeAccents('joão'), 'joao'); - assert.equal(strings.removeAccents('joåo'), 'joao'); - assert.equal(strings.removeAccents('joåo'), 'joao'); - assert.equal(strings.removeAccents('joāo'), 'joao'); - - assert.equal(strings.removeAccents('fôo'), 'foo'); - assert.equal(strings.removeAccents('föo'), 'foo'); - assert.equal(strings.removeAccents('fòo'), 'foo'); - assert.equal(strings.removeAccents('fóo'), 'foo'); - // assert.equal(strings.removeAccents('fœo'), 'foo'); - // assert.equal(strings.removeAccents('føo'), 'foo'); - assert.equal(strings.removeAccents('fōo'), 'foo'); - assert.equal(strings.removeAccents('fõo'), 'foo'); - - assert.equal(strings.removeAccents('andrè'), 'andre'); - assert.equal(strings.removeAccents('andré'), 'andre'); - assert.equal(strings.removeAccents('andrê'), 'andre'); - assert.equal(strings.removeAccents('andrë'), 'andre'); - assert.equal(strings.removeAccents('andrē'), 'andre'); - assert.equal(strings.removeAccents('andrė'), 'andre'); - assert.equal(strings.removeAccents('andrę'), 'andre'); - - assert.equal(strings.removeAccents('hvîc'), 'hvic'); - assert.equal(strings.removeAccents('hvïc'), 'hvic'); - assert.equal(strings.removeAccents('hvíc'), 'hvic'); - assert.equal(strings.removeAccents('hvīc'), 'hvic'); - assert.equal(strings.removeAccents('hvįc'), 'hvic'); - assert.equal(strings.removeAccents('hvìc'), 'hvic'); - - assert.equal(strings.removeAccents('ûdo'), 'udo'); - assert.equal(strings.removeAccents('üdo'), 'udo'); - assert.equal(strings.removeAccents('ùdo'), 'udo'); - assert.equal(strings.removeAccents('údo'), 'udo'); - assert.equal(strings.removeAccents('ūdo'), 'udo'); - - assert.equal(strings.removeAccents('heÿ'), 'hey'); - - // assert.equal(strings.removeAccents('gruß'), 'grus'); - assert.equal(strings.removeAccents('gruś'), 'grus'); - assert.equal(strings.removeAccents('gruš'), 'grus'); - - assert.equal(strings.removeAccents('çool'), 'cool'); - assert.equal(strings.removeAccents('ćool'), 'cool'); - assert.equal(strings.removeAccents('čool'), 'cool'); - - assert.equal(strings.removeAccents('ñice'), 'nice'); - assert.equal(strings.removeAccents('ńice'), 'nice'); - }); - test('encodeUTF8', function () { function assertEncodeUTF8(str: string, expected: number[]): void { const actual = strings.encodeUTF8(str); diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 3e5361ce764..5a8391e91af 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -5,7 +5,6 @@ import { mergeSort } from 'vs/base/common/arrays'; import { stringDiff } from 'vs/base/common/diff/diff'; -import { FIN, Iterator, IteratorResult } from 'vs/base/common/iterator'; import { IDisposable } from 'vs/base/common/lifecycle'; import { globals } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; @@ -65,7 +64,7 @@ export interface ICommonModel extends ILinkComputerTarget, IMirrorModel { getLineCount(): number; getLineContent(lineNumber: number): string; getLineWords(lineNumber: number, wordDefinition: RegExp): IWordAtPosition[]; - createWordIterator(wordDefinition: RegExp): Iterator; + words(wordDefinition: RegExp): Iterable; getWordUntilPosition(position: IPosition, wordDefinition: RegExp): IWordAtPosition; getValueInRange(range: IRange): string; getWordAtPosition(position: IPosition, wordDefinition: RegExp): Range | null; @@ -153,36 +152,37 @@ class MirrorModel extends BaseMirrorModel implements ICommonModel { }; } - public createWordIterator(wordDefinition: RegExp): Iterator { - let obj: { done: false; value: string; }; + + public words(wordDefinition: RegExp): Iterable { + + const lines = this._lines; + const wordenize = this._wordenize.bind(this); + let lineNumber = 0; - let lineText: string; + let lineText = ''; let wordRangesIdx = 0; let wordRanges: IWordRange[] = []; - let next = (): IteratorResult => { - if (wordRangesIdx < wordRanges.length) { - const value = lineText.substring(wordRanges[wordRangesIdx].start, wordRanges[wordRangesIdx].end); - wordRangesIdx += 1; - if (!obj) { - obj = { done: false, value: value }; - } else { - obj.value = value; + return { + *[Symbol.iterator]() { + while (true) { + if (wordRangesIdx < wordRanges.length) { + const value = lineText.substring(wordRanges[wordRangesIdx].start, wordRanges[wordRangesIdx].end); + wordRangesIdx += 1; + yield value; + } else { + if (lineNumber < lines.length) { + lineText = lines[lineNumber]; + wordRanges = wordenize(lineText, wordDefinition); + wordRangesIdx = 0; + lineNumber += 1; + } else { + break; + } + } } - return obj; - - } else if (lineNumber >= this._lines.length) { - return FIN; - - } else { - lineText = this._lines[lineNumber]; - wordRanges = this._wordenize(lineText, wordDefinition); - wordRangesIdx = 0; - lineNumber += 1; - return next(); } }; - return { next }; } public getLineWords(lineNumber: number, wordDefinition: RegExp): IWordAtPosition[] { @@ -545,12 +545,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { seen.add(model.getValueInRange(wordAt)); } - for ( - let iter = model.createWordIterator(wordDefRegExp), e = iter.next(); - !e.done && seen.size <= EditorSimpleWorker._suggestionsLimit; - e = iter.next() - ) { - const word = e.value; + for (let word of model.words(wordDefRegExp)) { if (seen.has(word)) { continue; } @@ -559,6 +554,9 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { continue; } words.push(word); + if (seen.size > EditorSimpleWorker._suggestionsLimit) { + break; + } } return words; } diff --git a/src/vs/editor/common/services/modeService.ts b/src/vs/editor/common/services/modeService.ts index a6d2a6bc9e2..678b15836af 100644 --- a/src/vs/editor/common/services/modeService.ts +++ b/src/vs/editor/common/services/modeService.ts @@ -31,6 +31,7 @@ export interface IModeService { _serviceBrand: undefined; onDidCreateMode: Event; + onLanguagesMaybeChanged: Event; // --- reading isRegisteredMode(mimetypeOrModeId: string): boolean; diff --git a/src/vs/editor/common/services/modeServiceImpl.ts b/src/vs/editor/common/services/modeServiceImpl.ts index 083d387118b..6b2fd6f80f8 100644 --- a/src/vs/editor/common/services/modeServiceImpl.ts +++ b/src/vs/editor/common/services/modeServiceImpl.ts @@ -50,7 +50,7 @@ export class ModeServiceImpl implements IModeService { public readonly onDidCreateMode: Event = this._onDidCreateMode.event; protected readonly _onLanguagesMaybeChanged = new Emitter(); - private readonly onLanguagesMaybeChanged: Event = this._onLanguagesMaybeChanged.event; + public readonly onLanguagesMaybeChanged: Event = this._onLanguagesMaybeChanged.event; constructor(warnOnOverwrite = false) { this._instantiatedModes = {}; diff --git a/src/vs/editor/contrib/suggest/suggestMemory.ts b/src/vs/editor/contrib/suggest/suggestMemory.ts index 20f3b4b352d..9fff61fe466 100644 --- a/src/vs/editor/contrib/suggest/suggestMemory.ts +++ b/src/vs/editor/contrib/suggest/suggestMemory.ts @@ -9,15 +9,18 @@ import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/ import { ITextModel } from 'vs/editor/common/model'; import { IPosition } from 'vs/editor/common/core/position'; import { CompletionItemKind, completionKindFromString } from 'vs/editor/common/modes'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { CompletionItem } from 'vs/editor/contrib/suggest/suggest'; +import { IModeService } from 'vs/editor/common/services/modeService'; export abstract class Memory { + constructor(readonly name: MemMode) { } + select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number { if (items.length === 0) { return 0; @@ -46,6 +49,10 @@ export abstract class Memory { export class NoMemory extends Memory { + constructor() { + super('first'); + } + memorize(model: ITextModel, pos: IPosition, item: CompletionItem): void { // no-op } @@ -67,6 +74,10 @@ export interface MemItem { export class LRUMemory extends Memory { + constructor() { + super('recentlyUsed'); + } + private _cache = new LRUCache(300, 0.66); private _seq = 0; @@ -143,6 +154,10 @@ export class LRUMemory extends Memory { export class PrefixMemory extends Memory { + constructor() { + super('recentlyUsedByPrefix'); + } + private _trie = TernarySearchTree.forStrings(); private _seq = 0; @@ -206,85 +221,86 @@ export class PrefixMemory extends Memory { export type MemMode = 'first' | 'recentlyUsed' | 'recentlyUsedByPrefix'; -export class SuggestMemoryService extends Disposable implements ISuggestMemoryService { +export class SuggestMemoryService implements ISuggestMemoryService { + + private static readonly _strategyCtors = new Map([ + ['recentlyUsedByPrefix', PrefixMemory], + ['recentlyUsed', LRUMemory], + ['first', NoMemory] + ]); + + private static readonly _storagePrefix = 'suggest/memories'; readonly _serviceBrand: undefined; - private readonly _storagePrefix = 'suggest/memories'; private readonly _persistSoon: RunOnceScheduler; - private _mode!: MemMode; - private _shareMem!: boolean; - private _strategy!: Memory; + private readonly _disposables = new DisposableStore(); + + private _strategy?: Memory; constructor( @IStorageService private readonly _storageService: IStorageService, + @IModeService private readonly _modeService: IModeService, @IConfigurationService private readonly _configService: IConfigurationService, ) { - super(); - - const update = () => { - const mode = this._configService.getValue('editor.suggestSelection'); - const share = this._configService.getValue('editor.suggest.shareSuggestSelections'); - this._update(mode, share, false); - }; - - this._persistSoon = this._register(new RunOnceScheduler(() => this._saveState(), 500)); - this._register(_storageService.onWillSaveState(e => { + this._persistSoon = new RunOnceScheduler(() => this._saveState(), 500); + this._disposables.add(_storageService.onWillSaveState(e => { if (e.reason === WillSaveStateReason.SHUTDOWN) { this._saveState(); } })); - - this._register(this._configService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('editor.suggestSelection') || e.affectsConfiguration('editor.suggest.shareSuggestSelections')) { - update(); - } - })); - this._register(this._storageService.onDidChangeStorage(e => { - if (e.scope === StorageScope.GLOBAL && e.key.indexOf(this._storagePrefix) === 0) { - if (!document.hasFocus()) { - // windows that aren't focused have to drop their current - // storage value and accept what's stored now - this._update(this._mode, this._shareMem, true); - } - } - })); - update(); } - private _update(mode: MemMode, shareMem: boolean, force: boolean): void { - if (!force && this._mode === mode && this._shareMem === shareMem) { - return; - } - this._shareMem = shareMem; - this._mode = mode; - this._strategy = mode === 'recentlyUsedByPrefix' ? new PrefixMemory() : mode === 'recentlyUsed' ? new LRUMemory() : new NoMemory(); - - try { - const scope = shareMem ? StorageScope.GLOBAL : StorageScope.WORKSPACE; - const raw = this._storageService.get(`${this._storagePrefix}/${this._mode}`, scope); - if (raw) { - this._strategy.fromJSON(JSON.parse(raw)); - } - } catch (e) { - // things can go wrong with JSON... - } + dispose(): void { + this._disposables.dispose(); + this._persistSoon.dispose(); } memorize(model: ITextModel, pos: IPosition, item: CompletionItem): void { - this._strategy.memorize(model, pos, item); + this._withStrategy(model, pos).memorize(model, pos, item); this._persistSoon.schedule(); } select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number { - return this._strategy.select(model, pos, items); + return this._withStrategy(model, pos).select(model, pos, items); + } + + private _withStrategy(model: ITextModel, pos: IPosition): Memory { + + const mode = this._configService.getValue('editor.suggestSelection', { + overrideIdentifier: this._modeService.getLanguageIdentifier(model.getLanguageIdAtPosition(pos.lineNumber, pos.column))?.language, + resource: model.uri + }); + + if (this._strategy?.name !== mode) { + + this._saveState(); + const ctor = SuggestMemoryService._strategyCtors.get(mode) || NoMemory; + this._strategy = new ctor(); + + try { + const share = this._configService.getValue('editor.suggest.shareSuggestSelections'); + const scope = share ? StorageScope.GLOBAL : StorageScope.WORKSPACE; + const raw = this._storageService.get(`${SuggestMemoryService._storagePrefix}/${mode}`, scope); + if (raw) { + this._strategy.fromJSON(JSON.parse(raw)); + } + } catch (e) { + // things can go wrong with JSON... + } + } + + return this._strategy; } private _saveState() { - const raw = JSON.stringify(this._strategy); - const scope = this._shareMem ? StorageScope.GLOBAL : StorageScope.WORKSPACE; - this._storageService.store(`${this._storagePrefix}/${this._mode}`, raw, scope); + if (this._strategy) { + const share = this._configService.getValue('editor.suggest.shareSuggestSelections'); + const scope = share ? StorageScope.GLOBAL : StorageScope.WORKSPACE; + const raw = JSON.stringify(this._strategy); + this._storageService.store(`${SuggestMemoryService._storagePrefix}/${this._strategy.name}`, raw, scope); + } } } diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 90c630a1303..ee21c74350c 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -631,8 +631,8 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate this.onEditorLayoutChange())); this.toDispose.add(this.list.onMouseDown(e => this.onListMouseDownOrTap(e))); this.toDispose.add(this.list.onTap(e => this.onListMouseDownOrTap(e))); - this.toDispose.add(this.list.onSelectionChange(e => this.onListSelection(e))); - this.toDispose.add(this.list.onFocusChange(e => this.onListFocus(e))); + this.toDispose.add(this.list.onDidChangeSelection(e => this.onListSelection(e))); + this.toDispose.add(this.list.onDidChangeFocus(e => this.onListFocus(e))); this.toDispose.add(this.editor.onDidChangeCursorSelection(() => this.onCursorSelectionChanged())); this.toDispose.add(this.editor.onDidChangeConfiguration(e => { if (e.hasChanged(EditorOption.suggest)) { diff --git a/src/vs/editor/contrib/suggest/test/suggestMemory.test.ts b/src/vs/editor/contrib/suggest/test/suggestMemory.test.ts index 28deea319ab..5554233c938 100644 --- a/src/vs/editor/contrib/suggest/test/suggestMemory.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggestMemory.test.ts @@ -29,6 +29,9 @@ suite('SuggestMemories', function () { test('AbstractMemory, select', function () { const mem = new class extends Memory { + constructor() { + super('first'); + } memorize(model: ITextModel, pos: IPosition, item: CompletionItem): void { throw new Error('Method not implemented.'); } toJSON(): object { diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index c7b2fed89ac..634fad15a96 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -24,7 +24,7 @@ import { TextEdit, WorkspaceEdit, WorkspaceTextEdit } from 'vs/editor/common/mod import { IModelService } from 'vs/editor/common/services/modelService'; import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextResourceConfigurationService, ITextResourcePropertiesService, ITextResourceConfigurationChangeEvent } from 'vs/editor/common/services/textResourceConfigurationService'; -import { CommandsRegistry, ICommand, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationModel, IConfigurationValue, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { Configuration, ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; @@ -254,7 +254,6 @@ export class StandaloneCommandService implements ICommandService { _serviceBrand: undefined; private readonly _instantiationService: IInstantiationService; - private readonly _dynamicCommands: { [id: string]: ICommand; }; private readonly _onWillExecuteCommand = new Emitter(); private readonly _onDidExecuteCommand = new Emitter(); @@ -263,19 +262,10 @@ export class StandaloneCommandService implements ICommandService { constructor(instantiationService: IInstantiationService) { this._instantiationService = instantiationService; - this._dynamicCommands = Object.create(null); - } - - public addCommand(command: ICommand): IDisposable { - const { id } = command; - this._dynamicCommands[id] = command; - return toDisposable(() => { - delete this._dynamicCommands[id]; - }); } public executeCommand(id: string, ...args: any[]): Promise { - const command = (CommandsRegistry.getCommand(id) || this._dynamicCommands[id]); + const command = CommandsRegistry.getCommand(id); if (!command) { return Promise.reject(new Error(`command '${id}' not found`)); } @@ -344,15 +334,8 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { })); } - let commandService = this._commandService; - if (commandService instanceof StandaloneCommandService) { - toDispose.add(commandService.addCommand({ - id: commandId, - handler: handler - })); - } else { - throw new Error('Unknown command service!'); - } + toDispose.add(CommandsRegistry.registerCommand(commandId, handler)); + this.updateResolver({ source: KeybindingSource.Default }); return toDispose; diff --git a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts index d2aa3675d36..da25cab90cb 100644 --- a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts +++ b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts @@ -184,11 +184,7 @@ suite('EditorSimpleWorker', () => { 'and now we are done' ]); - let words: string[] = []; - - for (let iter = model.createWordIterator(/[a-z]+/img), e = iter.next(); !e.done; e = iter.next()) { - words.push(e.value); - } + let words: string[] = [...model.words(/[a-z]+/img)]; assert.deepEqual(words, ['one', 'line', 'two', 'line', 'past', 'empty', 'single', 'and', 'now', 'we', 'are', 'done']); }); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index ccdfcfcece0..45e600368f1 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -174,6 +174,14 @@ declare namespace monaco { query?: string; fragment?: string; }): Uri; + /** + * Join a Uri path with path fragments and normalizes the resulting path. + * + * @param resource The input Uri. + * @param pathFragment The path fragment to add to the Uri path. + * @returns The resulting Uri. + */ + static joinPaths(resource: Uri, ...pathFragment: string[]): Uri; /** * Creates a string representation for this Uri. It's guaranteed that calling * `Uri.parse` with the result of this function creates an Uri which is equal diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index bb33f2e1195..7994ac2b060 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -13,12 +13,6 @@ import { IdleValue } from 'vs/base/common/async'; // TRACING const _enableTracing = false; -// PROXY -// Ghetto-declare of the global Proxy object. This isn't the proper way -// but allows us to run this code in the browser without IE11. -declare const Proxy: any; -const _canUseProxy = typeof Proxy === 'function'; - class CyclicDependencyError extends Error { constructor(graph: Graph) { super('cyclic dependency between services'); @@ -211,8 +205,8 @@ export class InstantiationService implements IInstantiationService { } private _createServiceInstance(ctor: any, args: any[] = [], _supportsDelayedInstantiation: boolean, _trace: Trace): T { - if (!_supportsDelayedInstantiation || !_canUseProxy) { - // eager instantiation or no support JS proxies (e.g. IE11) + if (!_supportsDelayedInstantiation) { + // eager instantiation return this._createInstance(ctor, args, _trace); } else { diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 1f9841893d9..0f084e82add 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -26,7 +26,7 @@ import { attachListStyler, computeStyles, defaultListStyles, IColorMapping } fro import { IThemeService } from 'vs/platform/theme/common/themeService'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { ObjectTree, IObjectTreeOptions, ICompressibleTreeRenderer, CompressibleObjectTree, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; -import { ITreeEvent, ITreeRenderer, IAsyncDataSource, IDataSource, ITreeMouseEvent } from 'vs/base/browser/ui/tree/tree'; +import { ITreeRenderer, IAsyncDataSource, IDataSource } from 'vs/base/browser/ui/tree/tree'; import { AsyncDataTree, IAsyncDataTreeOptions, CompressibleAsyncDataTree, ITreeCompressionDelegate, ICompressibleAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree'; import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree'; import { IKeyboardNavigationEventFilter, IAbstractTreeOptions, RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree'; @@ -298,7 +298,7 @@ export class WorkbenchList extends List { this.updateStyles(options.overrideStyles); } - this.disposables.add(this.onSelectionChange(() => { + this.disposables.add(this.onDidChangeSelection(() => { const selection = this.getSelection(); const focus = this.getFocus(); @@ -306,7 +306,7 @@ export class WorkbenchList extends List { this.listMultiSelection.set(selection.length > 1); this.listDoubleSelection.set(selection.length === 2); })); - this.disposables.add(this.onFocusChange(() => { + this.disposables.add(this.onDidChangeFocus(() => { const selection = this.getSelection(); const focus = this.getFocus(); @@ -618,9 +618,10 @@ export interface IOpenEvent { browserEvent?: UIEvent; } -export interface ITreeResourceNavigatorOptions { +export interface IResourceNavigatorOptions { readonly openOnFocus?: boolean; readonly openOnSelection?: boolean; + readonly openOnSingleClick?: boolean; } export interface SelectionKeyboardEvent extends KeyboardEvent { @@ -634,16 +635,45 @@ export function getSelectionKeyboardEvent(typeArg = 'keydown', preserveFocus?: b return e; } -export class TreeResourceNavigator extends Disposable { +export abstract class ResourceNavigator extends Disposable { - private options: ITreeResourceNavigatorOptions; + static createListResourceNavigator(list: WorkbenchList | WorkbenchPagedList, options?: IResourceNavigatorOptions): ResourceNavigator { + return new class extends ResourceNavigator { + constructor() { + super(list, options); + } + }(); + } + + static createTreeResourceNavigator(tree: WorkbenchObjectTree | WorkbenchCompressibleObjectTree | WorkbenchDataTree | WorkbenchAsyncDataTree | WorkbenchCompressibleAsyncDataTree, + options?: IResourceNavigatorOptions): ResourceNavigator { + return new class extends ResourceNavigator { + constructor() { + super(tree, { + ...{ + openOnSingleClick: tree.openOnSingleClick + }, + ...(options || {}) + }); + } + }(); + } + + private readonly options: IResourceNavigatorOptions; private readonly _onDidOpenResource = new Emitter>(); readonly onDidOpenResource: Event> = this._onDidOpenResource.event; constructor( - private tree: WorkbenchObjectTree | WorkbenchCompressibleObjectTree | WorkbenchDataTree | WorkbenchAsyncDataTree | WorkbenchCompressibleAsyncDataTree, - options?: ITreeResourceNavigatorOptions + private readonly treeOrList: { + getFocus(): (T | null)[], + getSelection(): (T | null)[], + setSelection(elements: (T | null)[], browserEvent?: UIEvent): void, + onDidChangeFocus: Event<{ browserEvent?: UIEvent }>, + onDidChangeSelection: Event<{ browserEvent?: UIEvent }>, + onDidOpen: Event<{ browserEvent?: UIEvent }>, + }, + options?: IResourceNavigatorOptions ) { super(); @@ -659,50 +689,50 @@ export class TreeResourceNavigator extends Disposable { private registerListeners(): void { if (this.options && this.options.openOnFocus) { - this._register(this.tree.onDidChangeFocus(e => this.onFocus(e))); + this._register(this.treeOrList.onDidChangeFocus(e => this.onFocus(e.browserEvent))); } if (this.options && this.options.openOnSelection) { - this._register(this.tree.onDidChangeSelection(e => this.onSelection(e))); + this._register(this.treeOrList.onDidChangeSelection(e => this.onSelection(e.browserEvent))); } - this._register(this.tree.onDidOpen(e => this.onSelection(e))); + this._register(this.treeOrList.onDidOpen(e => this.onSelection(e.browserEvent))); } - private onFocus(e: ITreeEvent): void { - const focus = this.tree.getFocus(); - this.tree.setSelection(focus as T[], e.browserEvent); + private onFocus(browserEvent?: UIEvent): void { + const focus = this.treeOrList.getFocus(); + this.treeOrList.setSelection(focus, browserEvent); - if (!e.browserEvent) { + if (!browserEvent) { return; } - const isMouseEvent = e.browserEvent && e.browserEvent instanceof MouseEvent; + const isMouseEvent = browserEvent && browserEvent instanceof MouseEvent; if (!isMouseEvent) { - const preserveFocus = (e.browserEvent instanceof KeyboardEvent && typeof (e.browserEvent).preserveFocus === 'boolean') ? - !!(e.browserEvent).preserveFocus : + const preserveFocus = (browserEvent instanceof KeyboardEvent && typeof (browserEvent).preserveFocus === 'boolean') ? + !!(browserEvent).preserveFocus : true; - this.open(preserveFocus, false, false, e.browserEvent); + this.open(preserveFocus, false, false, browserEvent); } } - private onSelection(e: ITreeEvent | ITreeMouseEvent, doubleClick = false): void { - if (!e.browserEvent || e.browserEvent.type === 'contextmenu') { + private onSelection(browserEvent?: MouseEvent | UIEvent): void { + if (!browserEvent || browserEvent.type === 'contextmenu') { return; } - const isKeyboardEvent = e.browserEvent instanceof KeyboardEvent; - const isMiddleClick = e.browserEvent instanceof MouseEvent ? e.browserEvent.button === 1 : false; - const isDoubleClick = e.browserEvent.detail === 2; - const preserveFocus = (e.browserEvent instanceof KeyboardEvent && typeof (e.browserEvent).preserveFocus === 'boolean') ? - !!(e.browserEvent).preserveFocus : + const isKeyboardEvent = browserEvent instanceof KeyboardEvent; + const isMiddleClick = browserEvent instanceof MouseEvent ? browserEvent.button === 1 : false; + const isDoubleClick = browserEvent.detail === 2; + const preserveFocus = (browserEvent instanceof KeyboardEvent && typeof (browserEvent).preserveFocus === 'boolean') ? + !!(browserEvent).preserveFocus : !isDoubleClick; - if (this.tree.openOnSingleClick || isDoubleClick || isKeyboardEvent) { - const sideBySide = e.browserEvent instanceof MouseEvent && (e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey); - this.open(preserveFocus, isDoubleClick || isMiddleClick, sideBySide, e.browserEvent); + if (this.options.openOnSingleClick || isDoubleClick || isKeyboardEvent) { + const sideBySide = browserEvent instanceof MouseEvent && (browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey); + this.open(preserveFocus, isDoubleClick || isMiddleClick, sideBySide, browserEvent); } } @@ -714,7 +744,7 @@ export class TreeResourceNavigator extends Disposable { revealIfVisible: true }, sideBySide, - element: this.tree.getSelection()[0], + element: this.treeOrList.getSelection()[0], browserEvent }); } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index b2911d2a364..db964718718 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -65,7 +65,7 @@ export function registerConfiguration(): IDisposable { description: localize('sync.keybindingsPerPlatform', "Synchronize keybindings per platform."), default: true, scope: ConfigurationScope.APPLICATION, - tags: ['sync'] + tags: ['sync', 'usesOnlineServices'] }, 'sync.ignoredExtensions': { 'type': 'array', @@ -75,7 +75,7 @@ export function registerConfiguration(): IDisposable { 'scope': ConfigurationScope.APPLICATION, uniqueItems: true, disallowSyncIgnore: true, - tags: ['sync'] + tags: ['sync', 'usesOnlineServices'] }, 'sync.ignoredSettings': { 'type': 'array', @@ -86,7 +86,7 @@ export function registerConfiguration(): IDisposable { additionalProperties: true, uniqueItems: true, disallowSyncIgnore: true, - tags: ['sync'] + tags: ['sync', 'usesOnlineServices'] } } }); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index df18db806db..7b72d2cab18 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1790,6 +1790,11 @@ declare module 'vscode' { * @return The uri of the resource. */ asExtensionUri(relativePath: string): Uri; + + /** + * + */ + readonly extensionUri: Uri; } export interface Extension { @@ -1800,6 +1805,11 @@ declare module 'vscode' { * @return The uri of the resource. */ asExtensionUri(relativePath: string): Uri; + + /** + * + */ + readonly extensionUri: Uri; } //#endregion diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 67e71e8b7b9..0952e991d55 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1028,6 +1028,7 @@ class Extension implements vscode.Extension { private _identifier: ExtensionIdentifier; readonly id: string; + readonly extensionUri: URI; readonly extensionPath: string; readonly packageJSON: IExtensionDescription; readonly extensionKind: vscode.ExtensionKind; @@ -1037,6 +1038,7 @@ class Extension implements vscode.Extension { this._originExtensionId = originExtensionId; this._identifier = description.identifier; this.id = description.identifier.value; + this.extensionUri = description.extensionLocation; this.extensionPath = path.normalize(originalFSPath(description.extensionLocation)); this.packageJSON = description; this.extensionKind = kind; diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 197aa88c85c..904c5afd8c8 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -366,6 +366,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio globalState, workspaceState, subscriptions: [], + get extensionUri() { return extensionDescription.extensionLocation; }, get extensionPath() { return extensionDescription.extensionLocation.fsPath; }, get storagePath() { return that._storagePath.workspaceValue(extensionDescription); }, get globalStoragePath() { return that._storagePath.globalValue(extensionDescription); }, diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index a2754c7be3e..c69376cd3a6 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -625,18 +625,18 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }, handler: (accessor) => { const focused = accessor.get(IListService).lastFocusedList; + const fakeKeyboardEvent = getSelectionKeyboardEvent('keydown', false); // List if (focused instanceof List || focused instanceof PagedList) { const list = focused; - list.setSelection(list.getFocus()); - list.open(list.getFocus()); + list.setSelection(list.getFocus(), fakeKeyboardEvent); + list.open(list.getFocus(), fakeKeyboardEvent); } // ObjectTree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; - const fakeKeyboardEvent = getSelectionKeyboardEvent('keydown', false); const focus = list.getFocus(); if (focus.length > 0) { diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 0a311f809b1..a394c7d34a4 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -6,7 +6,6 @@ import { URI } from 'vs/base/common/uri'; import { dirname, isEqual, basenameOrAuthority } from 'vs/base/common/resources'; import { IconLabel, IIconLabelValueOptions, IIconLabelCreationOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -82,9 +81,9 @@ export class ResourceLabels extends Disposable { constructor( container: IResourceLabelsContainer, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IExtensionService private readonly extensionService: IExtensionService, @IConfigurationService private readonly configurationService: IConfigurationService, @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService, @IDecorationsService private readonly decorationsService: IDecorationsService, @IThemeService private readonly themeService: IThemeService, @ILabelService private readonly labelService: ILabelService, @@ -103,7 +102,7 @@ export class ResourceLabels extends Disposable { })); // notify when extensions are registered with potentially new languages - this._register(this.extensionService.onDidRegisterExtensions(() => this._widgets.forEach(widget => widget.notifyExtensionsRegistered()))); + this._register(this.modeService.onLanguagesMaybeChanged(() => this._widgets.forEach(widget => widget.notifyExtensionsRegistered()))); // notify when model mode changes this._register(this.modelService.onModelModeChanged(e => { @@ -206,15 +205,15 @@ export class ResourceLabel extends ResourceLabels { container: HTMLElement, options: IIconLabelCreationOptions | undefined, @IInstantiationService instantiationService: IInstantiationService, - @IExtensionService extensionService: IExtensionService, @IConfigurationService configurationService: IConfigurationService, @IModelService modelService: IModelService, + @IModeService modeService: IModeService, @IDecorationsService decorationsService: IDecorationsService, @IThemeService themeService: IThemeService, @ILabelService labelService: ILabelService, @ITextFileService textFileService: ITextFileService ) { - super(DEFAULT_LABELS_CONTAINER, instantiationService, extensionService, configurationService, modelService, decorationsService, themeService, labelService, textFileService); + super(DEFAULT_LABELS_CONTAINER, instantiationService, configurationService, modelService, modeService, decorationsService, themeService, labelService, textFileService); this._label = this._register(this.create(container, options)); } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index 52f786763ec..8c70978f065 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -125,7 +125,7 @@ export class NotificationsList extends Themable { // Only allow for focus in notifications, as the // selection is too strong over the contents of // the notification - this._register(list.onSelectionChange(e => { + this._register(list.onDidChangeSelection(e => { if (e.indexes.length > 0) { list.setSelection([]); } diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 273542b5109..1fed1a2f75b 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -27,7 +27,7 @@ import { URI } from 'vs/base/common/uri'; import { dirname, basename } from 'vs/base/common/resources'; import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService'; import { FileKind } from 'vs/platform/files/common/files'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree, ResourceNavigator } from 'vs/platform/list/browser/listService'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { localize } from 'vs/nls'; import { timeout } from 'vs/base/common/async'; @@ -403,7 +403,7 @@ export class CustomTreeView extends Disposable implements ITreeView { })); this.tree.setInput(this.root).then(() => this.updateContentAreas()); - const customTreeNavigator = new TreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false }); + const customTreeNavigator = ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false }); this._register(customTreeNavigator); this._register(customTreeNavigator.onDidOpenResource(e => { if (!e.browserEvent) { diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index ef91832242f..59bffa18fca 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./bulkEdit'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator, IOpenEvent } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree, IOpenEvent, ResourceNavigator } from 'vs/platform/list/browser/listService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, BulkEditAriaProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement, BulkEditSorter } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; import { FuzzyScore } from 'vs/base/common/filters'; @@ -143,7 +143,7 @@ export class BulkEditPane extends ViewPane { this._disposables.add(this._tree.onContextMenu(this._onContextMenu, this)); - const navigator = new TreeResourceNavigator(this._tree, { openOnFocus: true }); + const navigator = ResourceNavigator.createTreeResourceNavigator(this._tree, { openOnFocus: true }); this._disposables.add(navigator); this._disposables.add(navigator.onDidOpenResource(e => this._openElementAsEditor(e))); diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index 75a58b0e9c4..31d066a3e35 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -10,7 +10,6 @@ import { IAction, Action } from 'vs/base/common/actions'; import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TreeResourceNavigator } from 'vs/platform/list/browser/listService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CommentNode, CommentsModel, ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; import { CommentController } from 'vs/workbench/contrib/comments/browser/commentsEditorContribution'; @@ -28,6 +27,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ResourceNavigator } from 'vs/platform/list/browser/listService'; export class CommentsPanel extends ViewPane { @@ -151,7 +151,7 @@ export class CommentsPanel extends ViewPane { this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); this.tree = this._register(this.instantiationService.createInstance(CommentsList, this.treeLabels, this.treeContainer)); - const commentsNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true })); + const commentsNavigator = this._register(ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: true })); this._register(commentsNavigator.onDidOpenResource(e => { this.openFile(e.element, e.editorOptions.pinned, e.editorOptions.preserveFocus, e.sideBySide); })); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 377041b25f9..d8af8fb5a29 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -24,7 +24,7 @@ import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; -import { TreeResourceNavigator, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { ResourceNavigator, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import { Event } from 'vs/base/common/event'; @@ -213,7 +213,7 @@ export class CallStackView extends ViewPane { this.tree.setInput(this.debugService.getModel()); - const callstackNavigator = new TreeResourceNavigator(this.tree); + const callstackNavigator = ResourceNavigator.createTreeResourceNavigator(this.tree); this._register(callstackNavigator); this._register(callstackNavigator.onDidOpenResource(e => { if (this.ignoreSelectionChangedEvent) { diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 06f84bae24b..4dcff55440e 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -55,7 +55,7 @@ import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneCont class OpenDebugViewletAction extends ShowViewletAction { public static readonly ID = VIEWLET_ID; - public static readonly LABEL = nls.localize('toggleDebugViewlet', "Show Debug"); + public static readonly LABEL = nls.localize('toggleDebugViewlet', "Show Run and Debug"); constructor( id: string, @@ -123,12 +123,13 @@ Registry.as(WorkbenchExtensions.Workbench).regi Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(StatusBarColorProvider, LifecyclePhase.Eventually); const debugCategory = nls.localize('debugCategory', "Debug"); +const runCategroy = nls.localize('runCategory', "Run"); registry.registerWorkbenchAction(SyncActionDescriptor.create(StartAction, StartAction.ID, StartAction.LABEL, { primary: KeyCode.F5 }, CONTEXT_IN_DEBUG_MODE.toNegated()), 'Debug: Start Debugging', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(ConfigureAction, ConfigureAction.ID, ConfigureAction.LABEL), 'Debug: Open launch.json', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL), 'Debug: Add Function Breakpoint', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL), 'Debug: Reapply All Breakpoints', debugCategory); -registry.registerWorkbenchAction(SyncActionDescriptor.create(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5, mac: { primary: KeyMod.WinCtrl | KeyCode.F5 } }), 'Debug: Run (Start Without Debugging)', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5, mac: { primary: KeyMod.WinCtrl | KeyCode.F5 } }), 'Run: Start Without Debugging', runCategroy); registry.registerWorkbenchAction(SyncActionDescriptor.create(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL), 'Debug: Remove All Breakpoints', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(EnableAllBreakpointsAction, EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL), 'Debug: Enable All Breakpoints', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL), 'Debug: Disable All Breakpoints', debugCategory); diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index 3883e76be0a..ca025ac0c76 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -160,7 +160,7 @@ export class StartAction extends AbstractDebugAction { export class RunAction extends StartAction { static readonly ID = 'workbench.action.debug.run'; - static LABEL = nls.localize('startWithoutDebugging', "Run (Start Without Debugging)"); + static LABEL = nls.localize('startWithoutDebugging', "Start Without Debugging"); protected isNoDebug(): boolean { return true; diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 7fb045f3b60..ca526214360 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -46,6 +46,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { TaskRunResult, DebugTaskRunner } from 'vs/workbench/contrib/debug/browser/debugTaskRunner'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IViewsService } from 'vs/workbench/common/views'; +import { generateUuid } from 'vs/base/common/uuid'; const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint'; @@ -73,7 +74,7 @@ export class DebugService implements IDebugService { private breakpointsToSendOnResourceSaved: Set; private initializing = false; private previousState: State | undefined; - private initCancellationToken: CancellationTokenSource | undefined; + private sessionCancellationTokens = new Map(); private activity: IDisposable | undefined; constructor( @@ -206,24 +207,33 @@ export class DebugService implements IDebugService { return this.initializing ? State.Initializing : State.Inactive; } - private startInitializingState() { + private startInitializingState(): void { if (!this.initializing) { this.initializing = true; this.onStateChange(); } } - private endInitializingState() { - if (this.initCancellationToken) { - this.initCancellationToken.cancel(); - this.initCancellationToken = undefined; - } + private endInitializingState(): void { if (this.initializing) { this.initializing = false; this.onStateChange(); } } + private cancelTokens(id: string | undefined): void { + if (id) { + const token = this.sessionCancellationTokens.get(id); + if (token) { + token.cancel(); + this.sessionCancellationTokens.delete(id); + } + } else { + this.sessionCancellationTokens.forEach(t => t.cancel()); + this.sessionCancellationTokens.clear(); + } + } + private onStateChange(): void { const state = this.state; if (this.previousState !== state) { @@ -380,8 +390,11 @@ export class DebugService implements IDebugService { } } - this.initCancellationToken = new CancellationTokenSource(); - const configByProviders = await this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!, this.initCancellationToken.token); + const initCancellationToken = new CancellationTokenSource(); + const sessionId = generateUuid(); + this.sessionCancellationTokens.set(sessionId, initCancellationToken); + + const configByProviders = await this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!, initCancellationToken.token); // a falsy config indicates an aborted launch if (configByProviders && configByProviders.type) { try { @@ -391,15 +404,15 @@ export class DebugService implements IDebugService { return false; } - if (!this.initCancellationToken) { + if (initCancellationToken.token.isCancellationRequested) { // User cancelled, silently return return false; } - const cfg = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, type, resolvedConfig, this.initCancellationToken.token); + const cfg = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, type, resolvedConfig, initCancellationToken.token); if (!cfg) { - if (launch && type && cfg === null && this.initCancellationToken) { // show launch.json only for "config" being "null". - await launch.openConfigFile(false, true, type, this.initCancellationToken.token); + if (launch && type && cfg === null && !initCancellationToken.token.isCancellationRequested) { // show launch.json only for "config" being "null". + await launch.openConfigFile(false, true, type, initCancellationToken.token); } return false; } @@ -423,7 +436,7 @@ export class DebugService implements IDebugService { const workspace = launch?.workspace || this.contextService.getWorkspace(); const taskResult = await this.taskRunner.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask, (msg, actions) => this.showError(msg, actions)); if (taskResult === TaskRunResult.Success) { - return this.doCreateSession(launch?.workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options); + return this.doCreateSession(sessionId, launch?.workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options); } return false; } catch (err) { @@ -432,16 +445,16 @@ export class DebugService implements IDebugService { } else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { await this.showError(nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved and that you have a debug extension installed for that file type.")); } - if (launch && this.initCancellationToken) { - await launch.openConfigFile(false, true, undefined, this.initCancellationToken.token); + if (launch && !initCancellationToken.token.isCancellationRequested) { + await launch.openConfigFile(false, true, undefined, initCancellationToken.token); } return false; } } - if (launch && type && configByProviders === null && this.initCancellationToken) { // show launch.json only for "config" being "null". - await launch.openConfigFile(false, true, type, this.initCancellationToken.token); + if (launch && type && configByProviders === null && !initCancellationToken.token.isCancellationRequested) { // show launch.json only for "config" being "null". + await launch.openConfigFile(false, true, type, initCancellationToken.token); } return false; @@ -450,9 +463,9 @@ export class DebugService implements IDebugService { /** * instantiates the new session, initializes the session, registers session listeners and reports telemetry */ - private async doCreateSession(root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }, options?: IDebugSessionOptions): Promise { + private async doCreateSession(sessionId: string, root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }, options?: IDebugSessionOptions): Promise { - const session = this.instantiationService.createInstance(DebugSession, configuration, root, this.model, options); + const session = this.instantiationService.createInstance(DebugSession, sessionId, configuration, root, this.model, options); this.model.addSession(session); // register listeners as the very first thing! this.registerSessionListeners(session); @@ -566,6 +579,7 @@ export class DebugService implements IDebugService { } } this.endInitializingState(); + this.cancelTokens(session.getId()); this._onDidEndSession.fire(session); const focusedSession = this.viewModel.focusedSession; @@ -656,12 +670,13 @@ export class DebugService implements IDebugService { let resolved: IConfig | undefined | null = session.configuration; if (launch && needsToSubstitute && unresolved) { - this.initCancellationToken = new CancellationTokenSource(); - const resolvedByProviders = await this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, this.initCancellationToken.token); + const initCancellationToken = new CancellationTokenSource(); + this.sessionCancellationTokens.set(session.getId(), initCancellationToken); + const resolvedByProviders = await this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, initCancellationToken.token); if (resolvedByProviders) { resolved = await this.substituteVariables(launch, resolvedByProviders); - if (resolved && this.initCancellationToken) { - resolved = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, unresolved.type, resolved, this.initCancellationToken.token); + if (resolved && !initCancellationToken.token.isCancellationRequested) { + resolved = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, unresolved.type, resolved, initCancellationToken.token); } } else { resolved = resolvedByProviders; @@ -695,6 +710,7 @@ export class DebugService implements IDebugService { if (sessions.length === 0) { this.taskRunner.cancel(); this.endInitializingState(); + this.cancelTokens(undefined); } return Promise.all(sessions.map(s => s.terminate())); diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 214d3763448..63833c44c8b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -37,7 +37,6 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; export class DebugSession implements IDebugSession { - private id: string; private _subId: string | undefined; private raw: RawDebugSession | undefined; private initialized = false; @@ -62,6 +61,7 @@ export class DebugSession implements IDebugSession { private readonly _onDidChangeName = new Emitter(); constructor( + private id: string, private _configuration: { resolved: IConfig, unresolved: IConfig | undefined }, public root: IWorkspaceFolder | undefined, private model: DebugModel, @@ -78,7 +78,6 @@ export class DebugSession implements IDebugSession { @INotificationService private readonly notificationService: INotificationService, @ILifecycleService lifecycleService: ILifecycleService ) { - this.id = generateUuid(); this._options = options || {}; if (this.hasSeparateRepl()) { this.repl = new ReplModel(); diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index af223d0a938..4bfd98599aa 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -29,7 +29,7 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { TreeResourceNavigator, WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; +import { ResourceNavigator, WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; import { dispose } from 'vs/base/common/lifecycle'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; @@ -491,7 +491,7 @@ export class LoadedScriptsView extends ViewPane { }, 300); this._register(this.changeScheduler); - const loadedScriptsNavigator = new TreeResourceNavigator(this.tree); + const loadedScriptsNavigator = ResourceNavigator.createTreeResourceNavigator(this.tree); this._register(loadedScriptsNavigator); this._register(loadedScriptsNavigator.onDidOpenResource(e => { if (e.element instanceof BaseTreeItem) { diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 8a1dd5969f9..518e7f07d7e 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -153,6 +153,7 @@ .debug-pane .debug-call-stack .monaco-list-row .monaco-action-bar { display: none; + flex-shrink: 0; } .debug-pane .debug-call-stack .monaco-list-row:hover .monaco-action-bar { diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index 83b69ec0ed7..732e78847c8 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -18,9 +18,10 @@ import { createBreakpointDecorations } from 'vs/workbench/contrib/debug/browser/ import { OverviewRulerLane } from 'vs/editor/common/model'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; +import { generateUuid } from 'vs/base/common/uuid'; function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession { - return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); + return new DebugSession(generateUuid(), { resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); } function addBreakpointsAndCheckEvents(model: DebugModel, uri: uri, data: IBreakpointData[]): void { diff --git a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts index 84b31372c66..608c0eac4f9 100644 --- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -16,9 +16,10 @@ import { createDecorationsForStackFrame } from 'vs/workbench/contrib/debug/brows import { Constants } from 'vs/base/common/uint'; import { getContext, getContextForContributedActions } from 'vs/workbench/contrib/debug/browser/callStackView'; import { getStackFrameThreadAndSessionToFocus } from 'vs/workbench/contrib/debug/browser/debugService'; +import { generateUuid } from 'vs/base/common/uuid'; export function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession { - return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); + return new DebugSession(generateUuid(), { resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); } function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFrame, secondStackFrame: StackFrame } { @@ -363,7 +364,7 @@ suite('Debug - CallStack', () => { get state(): State { return State.Stopped; } - }({ resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); + }(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); const runningSession = createMockSession(model); model.addSession(runningSession); diff --git a/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts index 90cbc9a869f..2433e63552a 100644 --- a/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts @@ -30,7 +30,7 @@ suite('Debug - ANSI Handling', () => { */ setup(() => { model = new DebugModel([], [], [], [], [], { isDirty: (e: any) => false }); - session = new DebugSession({ resolved: { name: 'test', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); + session = new DebugSession(generateUuid(), { resolved: { name: 'test', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); const instantiationService: TestInstantiationService = workbenchInstantiationService(); linkDetector = instantiationService.createInstance(LinkDetector); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 5d851558c95..8e09560a2d3 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -83,7 +83,9 @@ class NavBar extends Disposable { private _onChange = this._register(new Emitter<{ id: string | null, focus: boolean }>()); get onChange(): Event<{ id: string | null, focus: boolean }> { return this._onChange.event; } - private currentId: string | null = null; + private _currentId: string | null = null; + get currentId(): string | null { return this._currentId; } + private actions: Action[]; private actionbar: ActionBar; @@ -113,11 +115,11 @@ class NavBar extends Disposable { } update(): void { - this._update(this.currentId); + this._update(this._currentId); } - _update(id: string | null = this.currentId, focus?: boolean): Promise { - this.currentId = id; + _update(id: string | null = this._currentId, focus?: boolean): Promise { + this._currentId = id; this._onChange.fire({ id, focus: !!focus }); this.actions.forEach(a => a.checked = a.id === id); return Promise.resolve(undefined); @@ -308,12 +310,12 @@ export class ExtensionEditor extends BaseEditor { async setInput(input: ExtensionsInput, options: EditorOptions | undefined, token: CancellationToken): Promise { if (this.template) { - await this.updateTemplate(input, this.template); + await this.updateTemplate(input, this.template, !!options?.preserveFocus); } return super.setInput(input, options, token); } - private async updateTemplate(input: ExtensionsInput, template: IExtensionEditorTemplate): Promise { + private async updateTemplate(input: ExtensionsInput, template: IExtensionEditorTemplate, preserveFocus: boolean): Promise { const runningExtensions = await this.extensionService.getExtensions(); const colorThemes = await this.workbenchThemeService.getColorThemes(); const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); @@ -423,31 +425,34 @@ export class ExtensionEditor extends BaseEditor { template.content.innerHTML = ''; // Clear content before setting navbar actions. template.navbar.clear(); - template.navbar.onChange(e => this.onNavbarChange(extension, e, template), this, this.transientDisposables); if (extension.hasReadme()) { template.navbar.push(NavbarSection.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file")); } - this.extensionManifest.get() - .promise - .then(manifest => { - if (manifest) { - combinedInstallAction.manifest = manifest; - } - if (extension.extensionPack.length) { - template.navbar.push(NavbarSection.ExtensionPack, localize('extensionPack', "Extension Pack"), localize('extensionsPack', "Set of extensions that can be installed together")); - } - if (manifest && manifest.contributes) { - template.navbar.push(NavbarSection.Contributions, localize('contributions', "Feature Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension")); - } - if (extension.hasChangelog()) { - template.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"), localize('changelogtooltip', "Extension update history, rendered from the extension's 'CHANGELOG.md' file")); - } - if (extension.dependencies.length) { - template.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"), localize('dependenciestooltip', "Lists extensions this extension depends on")); - } - this.editorLoadComplete = true; - }); + + const manifest = await this.extensionManifest.get().promise; + if (manifest) { + combinedInstallAction.manifest = manifest; + } + if (extension.extensionPack.length) { + template.navbar.push(NavbarSection.ExtensionPack, localize('extensionPack', "Extension Pack"), localize('extensionsPack', "Set of extensions that can be installed together")); + } + if (manifest && manifest.contributes) { + template.navbar.push(NavbarSection.Contributions, localize('contributions', "Feature Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension")); + } + if (extension.hasChangelog()) { + template.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"), localize('changelogtooltip', "Extension update history, rendered from the extension's 'CHANGELOG.md' file")); + } + if (extension.dependencies.length) { + template.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"), localize('dependenciestooltip', "Lists extensions this extension depends on")); + } + + if (template.navbar.currentId) { + this.onNavbarChange(extension, { id: template.navbar.currentId, focus: !preserveFocus }, template); + } + template.navbar.onChange(e => this.onNavbarChange(extension, e, template), this, this.transientDisposables); + + this.editorLoadComplete = true; } private setSubText(extension: IExtension, reloadAction: ReloadAction, template: IExtensionEditorTemplate): void { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index 42704102383..bf8158fa6e9 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -169,7 +169,7 @@ class OpenExtensionAction extends Action { run(sideByside: boolean): Promise { if (this._extensionData) { - return this.extensionsWorkdbenchService.open(this._extensionData.extension, sideByside); + return this.extensionsWorkdbenchService.open(this._extensionData.extension, { sideByside }); } return Promise.resolve(); } @@ -218,7 +218,7 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree { if (event.browserEvent && event.browserEvent instanceof KeyboardEvent) { - extensionsWorkdbenchService.open(event.elements[0].extension, false); + extensionsWorkdbenchService.open(event.elements[0].extension, { sideByside: false }); } })); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 43dc5e0327b..856cb6f48c5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -29,7 +29,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction, InstallLocalExtensionsInRemoteAction, getContextMenuActions } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; -import { WorkbenchPagedList } from 'vs/platform/list/browser/listService'; +import { WorkbenchPagedList, ResourceNavigator } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -146,14 +146,14 @@ export class ExtensionsListView extends ViewPane { } }); this._register(this.list.onContextMenu(e => this.onContextMenu(e), this)); - this._register(this.list.onFocusChange(e => extensionsViewState.onFocusChange(coalesce(e.elements)), this)); + this._register(this.list.onDidChangeFocus(e => extensionsViewState.onFocusChange(coalesce(e.elements)), this)); this._register(this.list); this._register(extensionsViewState); - this._register(Event.chain(this.list.onOpen) - .map(e => e.elements[0]) - .filter(e => !!e) - .on(this.openExtension, this)); + const resourceNavigator = this._register(ResourceNavigator.createListResourceNavigator(this.list, { openOnFocus: false, openOnSelection: true, openOnSingleClick: true })); + this._register(Event.debounce(Event.filter(resourceNavigator.onDidOpenResource, e => e.element !== null), (last, event) => event, 75, true)(options => { + this.openExtension(this.list!.model.get(options.element!), { sideByside: options.sideBySide, ...options.editorOptions }); + })); this._register(Event.chain(this.list.onPin) .map(e => e.elements[0]) @@ -750,9 +750,9 @@ export class ExtensionsListView extends ViewPane { } } - private openExtension(extension: IExtension): void { + private openExtension(extension: IExtension, options: { sideByside?: boolean, preserveFocus?: boolean, pinned?: boolean }): void { extension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, extension.identifier))[0] || extension; - this.extensionsWorkbenchService.open(extension).then(undefined, err => this.onError(err)); + this.extensionsWorkbenchService.open(extension, options).then(undefined, err => this.onError(err)); } private pin(): void { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index f512f88db55..158c1444c27 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -630,8 +630,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return text.substr(0, 350); } - open(extension: IExtension, sideByside: boolean = false): Promise { - return Promise.resolve(this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), undefined, sideByside ? SIDE_GROUP : ACTIVE_GROUP)); + open(extension: IExtension, { sideByside, preserveFocus, pinned }: { sideByside?: boolean, preserveFocus?: boolean, pinned?: boolean } = { sideByside: false, preserveFocus: false, pinned: false }): Promise { + return Promise.resolve(this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), { preserveFocus, pinned }, sideByside ? SIDE_GROUP : ACTIVE_GROUP)); } private getPrimaryExtension(extensions: IExtension[]): IExtension { diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index ade46093ea2..fae5943aaf6 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -86,7 +86,7 @@ export interface IExtensionsWorkbenchService { installVersion(extension: IExtension, version: string): Promise; reinstall(extension: IExtension): Promise; setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise; - open(extension: IExtension, sideByside?: boolean): Promise; + open(extension: IExtension, options?: { sideByside?: boolean, preserveFocus?: boolean, pinned?: boolean }): Promise; checkForUpdates(): Promise; } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css b/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css index 47a318e4411..12d9ceac77a 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css @@ -40,3 +40,53 @@ .runtime-extensions-editor .monaco-action-bar .actions-container { justify-content: left; } + +.runtime-extensions-editor .extension > .icon-container { + position: relative; +} + +.runtime-extensions-editor .extension > .icon-container > .icon { + width: 42px; + height: 42px; + padding: 10px 14px 10px 0; + flex-shrink: 0; + object-fit: contain; +} + +.runtime-extensions-editor .extension > .icon-container .extension-remote-badge .codicon { + color: currentColor; +} + +.vs .runtime-extensions-editor .extension > .icon-container > .icon, +.vs-dark .runtime-extensions-editor .extension > .icon-container > .icon { + opacity: 0.5; +} + +.runtime-extensions-editor .extension > .desc > .header-container { + display: flex; + overflow: hidden; +} + +.runtime-extensions-editor .extension > .desc > .header-container > .header { + display: flex; + align-items: baseline; + flex-wrap: nowrap; + overflow: hidden; + flex: 1; + min-width: 0; +} + +.runtime-extensions-editor .extension > .desc > .header-container > .header > .name { + font-weight: bold; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.runtime-extensions-editor .extension > .desc > .header-container > .header > .version { + opacity: 0.85; + font-size: 80%; + padding-left: 6px; + min-width: fit-content; + min-width: -moz-fit-content; +} diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index e3450b958f5..826c40b7848 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -11,7 +11,7 @@ import { Action, IAction } from 'vs/base/common/actions'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtensionsWorkbenchService, IExtension, ExtensionContainers } from 'vs/workbench/contrib/extensions/common/extensions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService, IExtensionsStatus, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions'; @@ -47,6 +47,8 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; +import { domEvent } from 'vs/base/browser/event'; +import { Label } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; export const IExtensionHostProfileService = createDecorator('extensionHostProfileService'); export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey('profileSessionState', 'none'); @@ -257,6 +259,7 @@ export class RuntimeExtensionsEditor extends BaseEditor { interface IRuntimeExtensionTemplateData { root: HTMLElement; element: HTMLElement; + icon: HTMLImageElement; name: HTMLElement; msgContainer: HTMLElement; actionbar: ActionBar; @@ -264,15 +267,21 @@ export class RuntimeExtensionsEditor extends BaseEditor { profileTime: HTMLElement; disposables: IDisposable[]; elementDisposables: IDisposable[]; + extension: IExtension | null; } const renderer: IListRenderer = { templateId: TEMPLATE_ID, renderTemplate: (root: HTMLElement): IRuntimeExtensionTemplateData => { const element = append(root, $('.extension')); + const iconContainer = append(element, $('.icon-container')); + const icon = append(iconContainer, $('img.icon')); const desc = append(element, $('div.desc')); - const name = append(desc, $('div.name')); + const headerContainer = append(desc, $('.header-container')); + const header = append(headerContainer, $('.header')); + const name = append(header, $('div.name')); + const version = append(header, $('span.version')); const msgContainer = append(desc, $('div.msg')); @@ -284,18 +293,27 @@ export class RuntimeExtensionsEditor extends BaseEditor { const activationTime = append(timeContainer, $('div.activation-time')); const profileTime = append(timeContainer, $('div.profile-time')); - const disposables = [actionbar]; + const widgets = [ + this._instantiationService.createInstance(Label, version, (e: IExtension) => e.version) + ]; + + const extensionContainers: ExtensionContainers = this._instantiationService.createInstance(ExtensionContainers, [...widgets]); + const disposables = [actionbar, ...widgets]; return { root, element, + icon, name, actionbar, activationTime, profileTime, msgContainer, disposables, - elementDisposables: [] + elementDisposables: [], + set extension(extension: IExtension) { + extensionContainers.extension = extension; + } }; }, @@ -305,6 +323,16 @@ export class RuntimeExtensionsEditor extends BaseEditor { toggleClass(data.root, 'odd', index % 2 === 1); + const onError = Event.once(domEvent(data.icon, 'error')); + onError(() => data.icon.src = element.marketplaceInfo.iconUrlFallback, null, data.elementDisposables); + data.icon.src = element.marketplaceInfo.iconUrl; + + if (!data.icon.complete) { + data.icon.style.visibility = 'hidden'; + data.icon.onload = () => data.icon.style.visibility = 'inherit'; + } else { + data.icon.style.visibility = 'inherit'; + } data.name.textContent = element.marketplaceInfo ? element.marketplaceInfo.displayName : element.description.displayName || ''; const activationTimes = element.status.activationTimes!; @@ -318,6 +346,7 @@ export class RuntimeExtensionsEditor extends BaseEditor { if (isNonEmptyArray(element.status.runtimeErrors)) { data.actionbar.push(new ReportExtensionIssueAction(element, this._openerService), { icon: true, label: true }); } + data.extension = element.marketplaceInfo; let title: string; const activationId = activationTimes.activationReason.extensionId.value; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 3e5fc89ec88..2a72ae6c655 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -24,7 +24,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations'; -import { TreeResourceNavigator, WorkbenchCompressibleAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { ResourceNavigator, WorkbenchCompressibleAsyncDataTree } from 'vs/platform/list/browser/listService'; import { DelayedDragHandler } from 'vs/base/browser/dnd'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -418,7 +418,7 @@ export class ExplorerView extends ViewPane { // Update resource context based on focused element this._register(this.tree.onDidChangeFocus(e => this.onFocusChanged(e.elements))); this.onFocusChanged([]); - const explorerNavigator = new TreeResourceNavigator(this.tree); + const explorerNavigator = ResourceNavigator.createTreeResourceNavigator(this.tree); this._register(explorerNavigator); // Open when selecting via keyboard this._register(explorerNavigator.onDidOpenResource(async e => { diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 6192d0f8705..ff9ec14a6af 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -247,7 +247,7 @@ export class OpenEditorsView extends ViewPane { this.readonlyEditorFocusedContext = ReadonlyEditorContext.bindTo(this.contextKeyService); this._register(this.list.onContextMenu(e => this.onListContextMenu(e))); - this.list.onFocusChange(e => { + this.list.onDidChangeFocus(e => { this.resourceContext.reset(); this.groupFocusedContext.reset(); this.dirtyEditorFocusedContext.reset(); diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index cb45ee0c1bf..aa7d6318e91 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -26,7 +26,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { Iterator } from 'vs/base/common/iterator'; import { ITreeElement, ITreeNode, ITreeContextMenuEvent, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Relay, Event, Emitter } from 'vs/base/common/event'; -import { WorkbenchObjectTree, TreeResourceNavigator, IListService, IWorkbenchObjectTreeOptions } from 'vs/platform/list/browser/listService'; +import { WorkbenchObjectTree, ResourceNavigator, IListService, IWorkbenchObjectTreeOptions } from 'vs/platform/list/browser/listService'; import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; import { IExpression } from 'vs/base/common/glob'; import { deepClone } from 'vs/base/common/objects'; @@ -396,7 +396,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { relatedInformationFocusContextKey.set(focus.elements.some(e => e instanceof RelatedInformation)); })); - const markersNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true })); + const markersNavigator = this._register(ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: true })); this._register(Event.debounce(markersNavigator.onDidOpenResource, (last, event) => event, 75, true)(options => { this.openFileAtElement(options.element, !!options.editorOptions.preserveFocus, options.sideBySide, !!options.editorOptions.pinned); })); diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 2a3641de6d9..bdd7e5e3b26 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -463,7 +463,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditorP } })) as WorkbenchList; this._register(this.keybindingsList.onContextMenu(e => this.onContextMenu(e))); - this._register(this.keybindingsList.onFocusChange(e => this.onFocusChange(e))); + this._register(this.keybindingsList.onDidChangeFocus(e => this.onFocusChange(e))); this._register(this.keybindingsList.onDidFocus(() => { DOM.addClass(this.keybindingsList.getHTMLElement(), 'focused'); })); diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 47dd085867b..7d69b78bc83 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -50,7 +50,7 @@ import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views' import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree, ResourceNavigator } from 'vs/platform/list/browser/listService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Event } from 'vs/base/common/event'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -402,7 +402,7 @@ class HelpPanel extends ViewPane { this.tree.setInput(model); - const helpItemNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false })); + const helpItemNavigator = this._register(ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false })); this._register(Event.debounce(helpItemNavigator.onDidOpenResource, (last, event) => event, 75, true)(e => { e.element.handleClick(); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index f4768fd7a15..14382fea1c0 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/tunnelView'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IViewDescriptor, IEditableData, IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree, ResourceNavigator } from 'vs/platform/list/browser/listService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService, IContextKey, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -528,7 +528,7 @@ export class TunnelPanel extends ViewPane { this.tree.updateChildren(undefined, true); })); - const navigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false })); + const navigator = this._register(ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false })); this._register(Event.debounce(navigator.onDidOpenResource, (last, event) => event, 75, true)(e => { if (e.element && (e.element.tunnelType === TunnelType.Add)) { diff --git a/src/vs/workbench/contrib/scm/browser/mainPane.ts b/src/vs/workbench/contrib/scm/browser/mainPane.ts index 60d2d14c614..34208e6a383 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPane.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPane.ts @@ -212,8 +212,8 @@ export class MainPane extends ViewPane { }); this._register(renderer.onDidRenderElement(e => this.list.updateWidth(this.viewModel.repositories.indexOf(e)), null)); - this._register(this.list.onSelectionChange(this.onListSelectionChange, this)); - this._register(this.list.onFocusChange(this.onListFocusChange, this)); + this._register(this.list.onDidChangeSelection(this.onListSelectionChange, this)); + this._register(this.list.onDidChangeFocus(this.onListFocusChange, this)); this._register(this.list.onContextMenu(this.onListContextMenu, this)); this._register(this.viewModel.onDidChangeVisibleRepositories(this.updateListSelection, this)); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 8def9b2ba7d..80e1e2ac099 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -31,7 +31,7 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie import { IConfirmation, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TreeResourceNavigator, WorkbenchObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; +import { ResourceNavigator, WorkbenchObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IProgressService, IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, VIEW_ID, SearchSortOrder, SearchCompletionExitCode } from 'vs/workbench/services/search/common/search'; @@ -732,7 +732,7 @@ export class SearchView extends ViewPane { this.toggleCollapseStateDelayer.trigger(() => this.toggleCollapseAction.onTreeCollapseStateChange()) )); - const resourceNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true, openOnSelection: false })); + const resourceNavigator = this._register(ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: true, openOnSelection: false })); this._register(Event.debounce(resourceNavigator.onDidOpenResource, (last, event) => event, 75, true)(options => { if (options.element instanceof Match) { const selectedMatch: Match = options.element; diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index 2e834ea32dc..e9a0b7e5388 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { MarkdownString } from 'vs/base/common/htmlContent'; -import { compare, startsWith } from 'vs/base/common/strings'; +import { compare } from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; @@ -55,8 +55,6 @@ export class SnippetCompletion implements CompletionItem { export class SnippetCompletionProvider implements CompletionItemProvider { - private static readonly _maxPrefix = 10000; - readonly _debugDisplayName = 'snippetCompletions'; constructor( @@ -66,92 +64,84 @@ export class SnippetCompletionProvider implements CompletionItemProvider { // } - provideCompletionItems(model: ITextModel, position: Position, context: CompletionContext): Promise | undefined { - - if (position.column >= SnippetCompletionProvider._maxPrefix) { - return undefined; - } + async provideCompletionItems(model: ITextModel, position: Position, context: CompletionContext): Promise { if (context.triggerKind === CompletionTriggerKind.TriggerCharacter && context.triggerCharacter === ' ') { // no snippets when suggestions have been triggered by space - return undefined; + return { suggestions: [] }; } const languageId = this._getLanguageIdAtPosition(model, position); - return this._snippets.getSnippets(languageId).then(snippets => { + const snippets = await this._snippets.getSnippets(languageId); - let suggestions: SnippetCompletion[]; - let pos = { lineNumber: position.lineNumber, column: 1 }; - let lineOffsets: number[] = []; - const lineContent = model.getLineContent(position.lineNumber); - const linePrefixLow = lineContent.substr(0, position.column - 1).toLowerCase(); - let endsInWhitespace = linePrefixLow.match(/\s$/); + let pos = { lineNumber: position.lineNumber, column: 1 }; + let lineOffsets: number[] = []; + const lineContent = model.getLineContent(position.lineNumber).toLowerCase(); + const endsInWhitespace = /\s/.test(lineContent[position.column - 2]); - while (pos.column < position.column) { - let word = model.getWordAtPosition(pos); - if (word) { - // at a word - lineOffsets.push(word.startColumn - 1); - pos.column = word.endColumn + 1; - if (word.endColumn - 1 < linePrefixLow.length && !/\s/.test(linePrefixLow[word.endColumn - 1])) { - lineOffsets.push(word.endColumn - 1); - } - } - else if (!/\s/.test(linePrefixLow[pos.column - 1])) { - // at a none-whitespace character - lineOffsets.push(pos.column - 1); - pos.column += 1; - } - else { - // always advance! - pos.column += 1; + while (pos.column < position.column) { + let word = model.getWordAtPosition(pos); + if (word) { + // at a word + lineOffsets.push(word.startColumn - 1); + pos.column = word.endColumn + 1; + if (word.endColumn < position.column && !/\s/.test(lineContent[word.endColumn - 1])) { + lineOffsets.push(word.endColumn - 1); } } - - const lineSuffixLow = lineContent.substr(position.column - 1).toLowerCase(); - let availableSnippets = new Set(); - snippets.forEach(availableSnippets.add, availableSnippets); - suggestions = []; - for (let start of lineOffsets) { - availableSnippets.forEach(snippet => { - if (isPatternInWord(linePrefixLow, start, linePrefixLow.length, snippet.prefixLow, 0, snippet.prefixLow.length)) { - const snippetPrefixSubstr = snippet.prefixLow.substr(linePrefixLow.length - start); - const endColumn = startsWith(lineSuffixLow, snippetPrefixSubstr) ? position.column + snippetPrefixSubstr.length : position.column; - const replace = Range.fromPositions(position.delta(0, -(linePrefixLow.length - start)), { lineNumber: position.lineNumber, column: endColumn }); - const insert = replace.setEndPosition(position.lineNumber, position.column); - - suggestions.push(new SnippetCompletion(snippet, { replace, insert })); - availableSnippets.delete(snippet); - } - }); + else if (!/\s/.test(lineContent[pos.column - 1])) { + // at a none-whitespace character + lineOffsets.push(pos.column - 1); + pos.column += 1; } - if (endsInWhitespace || lineOffsets.length === 0) { - // add remaing snippets when the current prefix ends in whitespace or when no - // interesting positions have been found - availableSnippets.forEach(snippet => { - let insert = Range.fromPositions(position); - let replace = startsWith(lineSuffixLow, snippet.prefixLow) ? insert.setEndPosition(position.lineNumber, position.column + snippet.prefixLow.length) : insert; + else { + // always advance! + pos.column += 1; + } + } + + const availableSnippets = new Set(snippets); + const suggestions: SnippetCompletion[] = []; + + for (let start of lineOffsets) { + availableSnippets.forEach(snippet => { + if (isPatternInWord(lineContent, start, position.column - 1, snippet.prefixLow, 0, snippet.prefixLow.length)) { + const snippetPrefixSubstr = snippet.prefixLow.substr(position.column - (1 + start)); + const endColumn = lineContent.indexOf(snippetPrefixSubstr, position.column - 1) >= 0 ? position.column + snippetPrefixSubstr.length : position.column; + const replace = Range.fromPositions(position.delta(0, -(position.column - (1 + start))), { lineNumber: position.lineNumber, column: endColumn }); + const insert = replace.setEndPosition(position.lineNumber, position.column); + suggestions.push(new SnippetCompletion(snippet, { replace, insert })); - }); - } - - - // dismbiguate suggestions with same labels - suggestions.sort(SnippetCompletion.compareByLabel); - for (let i = 0; i < suggestions.length; i++) { - let item = suggestions[i]; - let to = i + 1; - for (; to < suggestions.length && item.label === suggestions[to].label; to++) { - suggestions[to].label.name = localize('snippetSuggest.longLabel', "{0}, {1}", suggestions[to].label.name, suggestions[to].snippet.name); + availableSnippets.delete(snippet); } - if (to > i + 1) { - suggestions[i].label.name = localize('snippetSuggest.longLabel', "{0}, {1}", suggestions[i].label.name, suggestions[i].snippet.name); - i = to; - } - } + }); + } + if (endsInWhitespace || lineOffsets.length === 0) { + // add remaing snippets when the current prefix ends in whitespace or when no + // interesting positions have been found + availableSnippets.forEach(snippet => { + const insert = Range.fromPositions(position); + const replace = lineContent.indexOf(snippet.prefixLow, position.column - 1) >= 0 ? insert.setEndPosition(position.lineNumber, position.column + snippet.prefixLow.length) : insert; + suggestions.push(new SnippetCompletion(snippet, { replace, insert })); + }); + } - return { suggestions }; - }); + + // dismbiguate suggestions with same labels + suggestions.sort(SnippetCompletion.compareByLabel); + for (let i = 0; i < suggestions.length; i++) { + let item = suggestions[i]; + let to = i + 1; + for (; to < suggestions.length && item.label === suggestions[to].label; to++) { + suggestions[to].label.name = localize('snippetSuggest.longLabel', "{0}, {1}", suggestions[to].label.name, suggestions[to].snippet.name); + } + if (to > i + 1) { + suggestions[i].label.name = localize('snippetSuggest.longLabel', "{0}, {1}", suggestions[i].label.name, suggestions[i].snippet.name); + i = to; + } + } + + return { suggestions }; } resolveCompletionItem(_model: ITextModel, _position: Position, item: CompletionItem): CompletionItem { diff --git a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts index a5cbf2fb130..e7860438731 100644 --- a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts +++ b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts @@ -78,7 +78,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, ({ order: 1, command: { id: OpenTimelineAction.ID, - title: localize(OpenTimelineAction.LABEL, "Open Timeline"), + title: OpenTimelineAction.LABEL, icon: { id: 'codicon/history' } }, when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.HasResource) diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 163e735e021..9c5881e6e64 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -15,7 +15,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IListVirtualDelegate, IIdentityProvider, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list'; import { ITreeNode, ITreeRenderer, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { TreeResourceNavigator, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; +import { ResourceNavigator, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -660,7 +660,7 @@ export class TimelinePane extends ViewPane { } }); - const customTreeNavigator = new TreeResourceNavigator(this._tree, { openOnFocus: false, openOnSelection: false }); + const customTreeNavigator = ResourceNavigator.createTreeResourceNavigator(this._tree, { openOnFocus: false, openOnSelection: false }); this._register(customTreeNavigator); this._register(this._tree.onContextMenu(e => this.onContextMenu(this._menus, e))); this._register(this._tree.onDidChangeSelection(e => this.ensureValidItems())); @@ -909,7 +909,7 @@ class TimelinePaneCommands extends Disposable { order: 99, command: { id: TimelinePaneCommands.RefreshCommand, - title: localize(TimelinePaneCommands.RefreshCommand, "Refresh"), + title: localize('refresh', "Refresh"), icon: { id: 'codicon/refresh' } } }))); @@ -920,7 +920,7 @@ class TimelinePaneCommands extends Disposable { order: 2, command: { id: TimelinePaneCommands.ToggleFollowActiveEditorCommand, - title: localize(`${TimelinePaneCommands.ToggleFollowActiveEditorCommand}.stop`, "Stop following the Active Editor"), + title: localize(`ToggleFollowActiveEditorCommand.stop`, "Stop following the Active Editor"), icon: { id: 'codicon/eye' } }, when: TimelineFollowActiveEditorContext @@ -930,7 +930,7 @@ class TimelinePaneCommands extends Disposable { order: 2, command: { id: TimelinePaneCommands.ToggleFollowActiveEditorCommand, - title: localize(`${TimelinePaneCommands.ToggleFollowActiveEditorCommand}.follow`, "Follow the Active Editor"), + title: localize(`ToggleFollowActiveEditorCommand.follow`, "Follow the Active Editor"), icon: { id: 'codicon/eye-closed' } }, when: TimelineFollowActiveEditorContext.toNegated()